Full lesson
Explore the full explanation, examples, and visuals at your own pace.
API replicas around one deploy
A routine kubectl apply cut our API from ten Pods to three, right in the middle of a traffic peak. On the chart, replicas climb to 10, drop to 3 at the apply, then only return to 10 minutes later. Latency jumped in that gap, before the HPA recovered capacity.
Chart values
| Time | Replicas |
|---|---|
| Before peak | 3 |
| Peak | 10 |
| Apply | 3 |
| Minutes later | 10 |
Two writers, one field
Both the HPA and kubectl apply write the same field, spec.replicas. First, the HPA sets it to 10 for the peak. Then apply sets it back to 3, because the manifest still says 3. The Deployment obeys and terminates 7 Pods, and latency jumps. The HPA writes 10 again, but those 7 new Pods must start and become Ready before they help.
With an HPA, replicas is an output, not a setting.
Once an HPA manages a workload, the replica count is computed from metrics. It's an output. So the number in your manifest is just a stale snapshot.
Both declare desired state for one field
Look at spec.replicas in the middle. Two writers feed it. Under high load, the HPA writes 10. The manifest, on apply, writes 3. Kubernetes has no idea which value is right. The Deployment controller just reconciles the Pods to whatever was written last, and right after the apply, that's the manifest's 3.
The dip lasts until new Pods are Ready
Look at the line. Ready Pods drop from 10 to 3 at the apply, then stay flat for a couple of minutes. The HPA has to notice the load, and new Pods must schedule, pull images, start, and pass readiness. Only then do they climb back to 10. These timings are illustrative; yours depend on your app.
Chart values
| Minutes from apply | Ready Pods |
|---|---|
| -1 min | 10 |
| Apply | 3 |
| +1 min | 3 |
| +2 min | 3 |
| +3 min | 7 |
| +4 min | 10 |
GitOps auto-sync: a repeating tug of war
Now add a GitOps tool that syncs continuously. It sets replicas to 3, then the HPA sets it back to 10. The Deployment's live state no longer matches Git, so the tool reports drift. With self-heal on, it resets replicas to 3 again, and the HPA answers with 10 again. The dip isn't a one-time cost of a deploy anymore. It can repeat.
Fix: let the HPA own replicas
The fix is to give the HPA sole ownership of the replica count. Drop spec.replicas from the manifest, and let minReplicas and maxReplicas set the bounds. Then a deploy changes configuration without resizing the Deployment, because only the HPA writes that number.
- Remove replicas from the manifest
- HPA min and max set bounds
- Deploys stop touching the count
Migrate the removal carefully
Removing replicas isn't a casual edit. With client-side apply, simply deleting the field can trigger a one-time scale change. So follow the documented Kubernetes migration steps for your apply mode, because server-side apply tracks field ownership differently.
- Client-side apply may cause one-time scale change
- Follow the documented Kubernetes migration steps
- Server-side apply tracks field ownership differently
GitOps: ignore the replicas field
If a GitOps tool owns your manifests, you can tell it to ignore spec.replicas when it diffs and syncs. Argo CD does this with ignoreDifferences. Exact behavior varies by tool, version, and apply mode, so test it on your own setup.
- Configure the tool to ignore spec.replicas
- Example: Argo CD ignoreDifferences
- Verify behavior for your tool and apply mode
Decide who owns replicas; let only that system set it.
Replicas is one field with two writers, and only one should write it. Audit your HPA-managed workloads for a hardcoded replicas value in the manifest.