Skip to content

3.5. Autoscaling

When to Look at This

  • When a service slows down during peak hours
  • When resources are wasted during quiet hours
  • When you want the service to stay up during node maintenance

Why Automatic Scaling Is Needed

Increasing the pod count by hand when load rises is too slow to respond. Conversely, leaving many pods up during quiet hours wastes resources.

COP provides two automatic scaling methods.

MethodBasisResponse
HPACurrent CPU and memory utilizationScales up after load rises
CronHPAA set timeScales up before load rises

HPA

HPA (HorizontalPodAutoscaler) looks at utilization and increases or decreases the pod count.

Go to Workloads > HPAs.

HPA list
ColumnDescription
NameThe HPA name
TargetThe Deployment or other resource to scale
Min · MaxThe lower and upper bounds of the pod count
ReplicasThe current pod count
MetricsCurrent utilization / target utilization

It works like this.

  1. Measures the average utilization of the target pods.
  2. Compares it with the target utilization.
  3. Calculates required pods = current pods × (current utilization ÷ target utilization).
  4. Adjusts within the minimum and maximum range.

For example, if two pods average 80% and the target is 50%, then 2 × (80 ÷ 50) = 3.2, so it scales to 4.

It moves more slowly when scaling down than up. Scaling down the moment load dips would only require scaling up again, so it scales down only after utilization stays low for a while.

HPA Requires Resource Requests

The target pods must have resource requests configured before you attach an HPA. Without them, the HPA does nothing. This is the most common reason an HPA does not work.

Why It Is Needed

The "utilization" in the formula above is a ratio against the request. It is not against total node capacity or against the limit.

utilization = actual usage ÷ request × 100

The request is the denominator. Without a request there is no denominator, so utilization cannot be computed at all, and the metrics column of the HPA list stays at <unknown>.

What requests and limits are, and how to set them, is in 3.1. Here we only look at how the HPA uses those values.

Worked Example — the COP Sample Application

Let us follow the actual settings of the e-government sample application that COP provides.

ItemValue
CPU limit2 (2 cores)
CPU requestNot set separately, so it is filled in automatically as 2, the same as the limit (see 3.1)
HPA minimum pods2
HPA maximum pods4
HPA target utilization75%

Here, a 75% target means "each pod averages 1.5 cores".

2 cores (request) × 75% = 1.5 cores

Suppose two pods are each using 1.8 cores.

StepCalculation
Current utilization1.8 ÷ 2 × 100 = 90%
Required pods2 × (90 ÷ 75) = 2.4, rounded up to 3
Check against the maximum3 is within the maximum of 4, so it stays at 3

After scaling up, the same load is divided among three pods, giving 1.2 cores each and 60% utilization, which is below the target. If load keeps rising it grows to 4, and if 4 pods still exceed 90% it grows no further. At that point you have to raise the maximum or find another bottleneck.

What Happens If the Request Is Wrong

The request is both the value used for scheduling and the denominator for the HPA. Because one value decides two things, getting it wrong throws both off together.

RequestEffect on schedulingEffect on the HPA
Larger than realityFew pods fit on a nodeUtilization reads low, so it does not scale up when it should
Smaller than realityIt is evicted when crowdedUtilization reads high, so it scales up when it need not
AbsentIt reserves no placeThe HPA does not work

For example, if the request is 2 cores but the application actually uses only 0.2 core, then even when load jumps fivefold to 1 core, utilization is 50% and falls short of the 75% target, so pods do not increase. On screen it looks like "the HPA is fine but the service is slow".

Measure actual usage first, set the request, and then attach the HPA. In the other order, no amount of tuning the target utilization makes it fit.

Scaling on Memory

You can also scale on memory utilization, but it does not fit as well as CPU. Runtimes that reserve a heap in advance, such as Java, do not reduce memory usage even when idle, so utilization does not drop as you add pods. It then keeps scaling to the maximum.

It is better to set memory limits properly and base automatic scaling on CPU.

Creating One — YAML

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75 # 75% of the request
FieldDescription
scaleTargetRefThe target to scale. A wrong name leaves the metric at <unknown>
minReplicasThe floor. 2 or more is recommended — with 1, the service can be interrupted during scaling
maxReplicasThe ceiling. Set it to what the cluster can bear
averageUtilizationThe target utilization. Based on the request (see the section above)

If it moves up and down too often, tune the response speed.

behavior:
scaleDown:
stabilizationWindowSeconds: 300 # scales down only after five minutes of low load
scaleUp:
stabilizationWindowSeconds: 0 # scales up immediately

The trick is to slow down only the scaling-down side. Scaling down the moment load dips means scaling up again soon after, so pods keep oscillating.

When HPA Does Not Work

If the metrics column shows <unknown>, usage cannot be read.

CauseWhat to check
The target pods have no resource requestsThe most common cause. Look at the requests and limits on the pod detail (see the section above)
The metric collector is not runningAsk operations staff
The target name is wrongWhether the target on the detail screen matches the actual Deployment name
The pods just startedIt takes about a minute for the first measurements to accumulate. Look again shortly

Numbers appearing but pods not increasing has a different cause than <unknown>.

SymptomCause
Utilization only ever reads below the targetThe request is set larger than reality (see the section above)
It sits at the maximumThe maximum pod count is too low, or the bottleneck is elsewhere
It scaled up but stays in PendingThere is no room in the cluster. Check node resources (see 2.1)

If it is slow even at the maximum, the problem is not the pod count. The database or an external integration may be the bottleneck, so check the monitoring screen (see 9.2).

CronHPA

CronHPA changes the pod count based on time. Use it to prepare before load rises.

View it under Workloads > CronHPAs.

The schedule notation differs from CronJob. CronJob uses five fields, while CronHPA uses six, including seconds. Using the notation from 3.3 as is shifts every field by one.

CronHPA schedule notation
SymbolMeaningExample
*Every value0 0 * * * * = on the hour
/Interval0 */10 * * * * = every 10 minutes
,List0 0 9,18 * * * = at 09:00 and 18:00
-Range0 0 9-18 * * * = hourly from 09:00 to 18:00

There are also notations that can be used in place of a fixed schedule.

NotationMeaning
@hourly · @daily · @weekly · @monthly · @yearlyOn the hour · daily at midnight · Sunday at midnight · the 1st of each month at midnight · January 1 at midnight
@every 1h30mEvery 1 hour 30 minutes
@date 2026-12-25 09:00:00Once, at that date and time

@date is a non-repeating schedule. Use it for events and occasions with a fixed date.

When to Use It

SituationWhy CronHPA
Load concentrates only during business hoursScale up before people arrive. HPA only moves after load arrives
Startup takes a long timeIf a Java application takes minutes to start, HPA is already too late
Times are fixed, as with settlement or closingScale up for the end of each month or the nightly batch window
You want to reduce resources at night and on weekendsReduce to 0 or 1 at night in development and staging to save resources
An event date is fixedSpecify start and end times with @date
Load cannot be read from metricsLoad that does not show in CPU or memory, such as queue depth

CronHPA fills the gaps where HPA does not fit well. HPA reacts after load rises, so in a sudden surge it scales up only after things have already slowed.

Basic Use — Scaling for Business Hours

This is the most common form. Scale up during weekday business hours and down afterwards.

apiVersion: autoscaling.openmaru.io/v1
kind: CronHPA
metadata:
name: app-cronhpa
namespace: my-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
jobs:
- name: scale-up-morning
schedule: "0 50 8 * * 1-5" # weekdays at 08:50:00
targetSize: 10
- name: scale-down-evening
schedule: "0 0 20 * * 1-5" # weekdays at 20:00:00
targetSize: 3
- name: weekend-minimum
schedule: "0 0 0 * * 6" # Saturday at 00:00:00
targetSize: 2
FieldDescription
scaleTargetRefThe target to scale. Write all three of apiVersion, kind, and name
jobsThe schedule list. At least one is required
jobs[].nameA name unique within this CronHPA. It appears under this name on the status screen
jobs[].scheduleWhen to run
jobs[].targetSizeThe pod count to set at that time. 0 is allowed

targetSize is not "how many to add" but "how many to end up with". It sets that value regardless of the current pod count.

The target can be any resource that supports scale. Deployments, StatefulSets, and ReplicaSets can be used.

Using It Together With HPA

Pointing CronHPA at an HPA rather than a Deployment lets you use both together. In that case CronHPA does not change the pod count directly; it adjusts the HPA's minimum and maximum pod counts.

apiVersion: autoscaling.openmaru.io/v1
kind: CronHPA
metadata:
name: app-cronhpa-with-hpa
namespace: my-app
spec:
scaleTargetRef:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler # points at the HPA, not the Deployment
name: my-app-hpa
jobs:
- name: peak-hours
schedule: "0 50 8 * * 1-5" # weekdays 08:50 — raises the floor to 5
targetSize: 5
- name: off-hours
schedule: "0 0 20 * * 1-5" # weekdays 20:00 — lowers the floor to 2
targetSize: 2

It behaves like this.

TimeWhat CronHPA doesWhat the HPA then does
08:50Raises the HPA minimum pod count to 5Scales up to the maximum on its own if load rises further
20:00Lowers the HPA minimum pod count to 2Scales down to 2 if load is low

Time sets the floor, and above that the HPA adjusts based on actual load. CronHPA takes the predictable part and the HPA takes the unpredictable part.

If targetSize exceeds the current maximum pod count, the maximum is raised along with it. Entering a value beyond the maximum does not fail.

Running Only Once

Adding runOnce: true makes that schedule run once and disappear from the list.

jobs:
- name: one-time-scale-up
schedule: "0 0 9 * * *"
targetSize: 10
runOnce: true

Combined with @date, it can match an event on a particular date.

jobs:
- name: event-start
schedule: "@date 2026-10-03 00:00:00"
targetSize: 20
- name: event-end
schedule: "@date 2026-10-09 00:00:00"
targetSize: 3

Skipping Particular Dates

On dates listed in excludeDates, no schedule runs at all. Use it to stop scaling on holidays and weekends.

spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
excludeDates:
- "* * * * * 6" # every Saturday
- "* * * * * 0" # every Sunday
- "* * * 1 1 *" # January 1
- "* * * 25 12 *" # December 25
jobs:
- name: scale-up
schedule: "0 50 8 * * *"
targetSize: 5
- name: scale-down
schedule: "0 10 18 * * *"
targetSize: 1

excludeDates also uses cron notation. It is not a date format such as 2026-01-01. In the six-field notation, specify only the date fields and leave the rest as *.

On excluded dates it neither scales up nor down. In the example above, after scaling down to 1 at 18:10 on Friday, nothing happens on Saturday or Sunday, so it stays at 1 until 08:50 on Monday. If you need a minimum count on weekends too, add a separate weekend schedule instead of using excludeDates.

Checking Status

View per-schedule status on the CronHPA detail screen in the Console.

StatusMeaning
SubmittedRegistered and waiting for the next run
SucceedThe last run succeeded
FailedThe last run failed. The reason is shown with it

Each schedule shows its last run time and next scheduled run time. If they differ from what you intended, check the time zone item below.

Cautions

ItemContent
Number of schedule fieldsSix. Using CronJob notation (five fields) shifts the fields and runs at the wrong time
Time zoneIt follows the controller's time zone. COP installs with Asia/Seoul by default. If installed with another time zone, schedule times follow that
targetSize: 0Entering 0 takes all pods down and stops the service. Use it only to save resources at night in development and staging
Always include a schedule that restores the countWith only a scale-up schedule, it stays in that state
A hand-set pod count is overwritten at the next scheduleEven if you change the replica count in the Console, it returns to targetSize at the next run time
Attaching it directly to a Deployment conflicts with an HPATo use both, point it at the HPA (see above)
Do not set beyond the cluster's headroomIf the added pods sit in Pending, the situation only gets worse

Choosing Between HPA and CronHPA

SituationRecommendation
You cannot predict when load will riseHPA
It concentrates at the same time every day (commute hours, settlement times)CronHPA
Warm-up is long, so scaling after load arrives is too lateCronHPA
You want to reduce resources at night and on weekendsCronHPA
There is an event with a fixed dateCronHPA (@date)
You need bothPoint CronHPA at the HPA and use them together (see "Using It Together With HPA" above)

PodDisruptionBudget

A PodDisruptionBudget sets how many pods may go down at once when pods are moved, for example during node maintenance.

View them under Workloads > Pod Disruption Budgets.

SettingMeaning
minAvailableThe minimum number of pods that must always remain
maxUnavailableThe maximum number of pods that may be down at once

Set only one of the two. They can be written as a ratio (50%) instead of a count.

Without one, every pod can go down at once during maintenance and interrupt the service. Node maintenance sometimes proceeds without notice, so configure this in advance for important services.

Setting it too strictly stops node maintenance itself. With two replicas and minAvailable: 2, no pod can be moved and maintenance stalls.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-app
spec:
minAvailable: 2 # or maxUnavailable: 1 (only one of the two)
selector:
matchLabels:
app: my-app # the labels of the pods to protect

The selector does not point at a Deployment; it selects pod labels. Use the same value as the Deployment's spec.selector.matchLabels (see 3.2).

If you also use automatic scaling, writing it as a ratio is safer. Written as a count, maintenance is blocked when the pod count drops.

maxUnavailable: 25%

Cautions When Using Them Together

  • Do not attach an HPA and a CronHPA separately to the same Deployment. They instruct different values and the pod count oscillates. To use both, point the CronHPA at the HPA (see above).
  • Changing the replica count by hand on a target with automatic scaling soon reverts.
  • When attaching automatic scaling to a resource managed by ArgoCD, remove the replica count field from the configuration repository. Otherwise ArgoCD reverts the pod count (see 4.7).
  • Setting the minimum to 1 can interrupt the service briefly during scaling. 2 or more is recommended.
  • Set the maximum within what the cluster can bear. With insufficient resources, the added pods sit in Pending and the situation only gets worse.