# cluster-proportional-autoscaler: sizing replicas to cluster size

> The cluster-proportional-autoscaler watches the number of schedulable nodes and cores in a Kubernetes cluster and resizes the replicas of a chosen workload to match, in linear or ladder mode. It is the controller you use for DNS and other services that have to grow with the cluster rather than with load.

**kubernetes-sigs/cluster-proportional-autoscaler** — Kubernetes Cluster Proportional Autoscaler Container

- Repository: https://github.com/kubernetes-sigs/cluster-proportional-autoscaler
- Stars: 759 · Forks: 144
- Language: Go
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kubernetes-sigs-cluster-proportional-autoscaler

## What the controller watches and what it resizes

The container image watches over the number of schedulable nodes and cores of the cluster and resizes the number of replicas for the required resource. The README states that this functionality may be desirable for applications that need to be autoscaled with the size of the cluster, and it names DNS and other services that scale with the number of nodes or pods.

The implementation detail matters for understanding the design. The code is a Kubernetes Go API client that, using the default service account credentials available to Go clients running inside pods, connects to the API server and polls for the number of nodes and cores in the cluster. So it is a poll loop reading cluster state, not a watch-based controller, and it computes a desired replica count and applies it.

The scaling parameters and data points are provided via a ConfigMap, and the autoscaler refreshes its parameters table every poll interval to stay up to date with the latest desired parameters. That detail is what makes the control pattern useful: the parameters can be changed on the fly, including the control mode, without rebuilding or restarting the scaler containers or pods.

The desired number of replicas is computed from the number of cores and nodes as input of the chosen controller. The README notes this may later be extended to more complex interpolation or exponential scaling schemes, and that it currently supports linear and ladder modes.

The repository is a Kubernetes SIG project, hosted under kubernetes-sigs, with an OWNERS file, a code of conduct, a CONTRIBUTING.md, a RELEASE.md, SECURITY_CONTACTS, a CHANGELOG.md and a vendor/ directory.

## The command-line flags that matter

The flag set follows the Kubernetes component conventions, glog for logging and pflag for parsing, and the README prints the full usage list. The flags that shape the behaviour are these.

The target flag selects what to scale, in a format of deployment/*, replicationcontroller/*, replicaset/*, which is not case sensitive and takes a comma delimiter. The configmap flag points at the ConfigMap holding the scaling parameters, and the default params flag supplies default parameters in JSON format, creating or recreating the ConfigMap with those defaults if it is not present.

The poll period flag sets the time in seconds to check cluster status and perform autoscale, and the default is 10. The namespace flag covers all operations, falling back to the namespace of the autoscaler itself through the MY_POD_NAMESPACE environment variable if it is not specified.

Two flags are easy to miss. The nodelabels flag filters the node search and its cores by label selectors, taking a comma separated list of key and value pairs, with an example given as label1=value1,label2=value2. And the max sync failures flag sets how many consecutive polling failures are tolerated before exiting, with a default of 0 meaning unlimited retries.

That last one is the safety valve. A controller that exits after a transient API server blip gets restarted by the kubelet, and a controller that never exits keeps retrying. Which behaviour you want depends on whether you run it as a Deployment with restarts or as a singleton, so the flag is worth setting deliberately.

## Running locally and installing with Helm

The autoscaler is designed to run as a Pod, where it picks up credentials from the in-cluster service account. For local development, testing or running on a host that has a kubeconfig, the README says to pass the kubeconfig flag.

```sh
./cluster-proportional-autoscaler \
  --kubeconfig=$HOME/.kube/config \
  --configmap=my-autoscaler \
  --target=deployment/my-deployment \
  --namespace=kube-system \
  --logtostderr
```

The Helm path is the production route. You add the chart repository, then update the index.

```sh
helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
helm repo update
```

Then you install a release using the chart. The README points at the chart's default values file as providing commented out examples for setting the values, and notes that there are several required values but that helm should fail with messages indicating which value is missing.

The examples folder in the repository holds working configurations, and it is the fastest way to see a complete setup: there are linear.yaml, linear-defaultparams.yaml, ladder.yaml and ladder-defaultparams.yaml, plus an RBAC example and a README.

For the container image itself, the Makefile builds the cluster-proportional-autoscaler binary and targets linux/amd64, linux/arm, linux/arm64, linux/ppc64le and linux/s390x, on a distroless static nonroot base image, and the default registry is the Kubernetes staging one at gcr.io/k8s-staging-cpa.

## Linear mode and the parameters behind it

The ConfigMap holds the configuration parameters, which allows on-the-fly changes including the control mode without rebuilding or restarting the scaler. Two ConfigMap keys are supported, ladder and linear, each corresponding to one of the two control modes.

In linear mode, the parameters in the ConfigMap must be JSON and use linear as the key.

```yaml
  linear: |-
    {
      "coresPerReplica": 2,
      "nodesPerReplica": 1,
      "min": 1,
      "max": 100,
      "preventSinglePointFailure": true,
      "includeUnschedulableNodes": true
    }
```

The equation is three lines, and it is worth reading carefully because the order of the min and max clamping matters. The first line takes the larger of the two computed values, one derived from cores and one from nodes. The second clamps against the configured maximum, and the third clamps against the minimum.

The README's worked example makes the arithmetic concrete. Given a cluster with 4 nodes and 13 cores, with each replica taking care of 1 node, you need 4 divided by 1, which is 4 replicas, and each replica taking care of 2 cores, you need ceil(13 divided by 2), which is 7 replicas. The controller picks the greater one, which is 7.

Three optional parameters have documented defaults. If min is not set it defaults to 1, and preventSinglePointFailure and includeUnschedulableNodes both default to false. When preventSinglePointFailure is true, the controller ensures at least 2 replicas if there is more than one node. When includeUnschedulableNodes is true, the replicas scale on the total number of nodes, otherwise only on the schedulable ones, so cordoned and draining nodes are excluded.

Either coresPerReplica or nodesPerReplica can be omitted, both are floats, and the lowest replica count is set to 1 when min is less than 1.

## Ladder mode and the stepped alternative

The ladder mode parameters must be JSON and use ladder as the key, and the structure is different in kind from the linear one.

```yaml
  ladder: |-
    {
      "coresToReplicas":
      [
        [ 1, 1 ],
        [ 64, 3 ],
        [ 512, 5 ],
        [ 1024, 7 ],
        [ 2048, 10 ],
        [ 4096, 15 ]
      ],
      "nodesToReplicas":
      [
        [ 1, 1 ],
        [ 2, 2 ]
      ],
      "includeUnschedulableNodes": false
    }
```

The difference is that ladder is an explicit lookup table rather than a formula. Each entry is a pair, a threshold and the replica count to use at or below it. Six core thresholds run from 1 core at 1 replica up to 4096 cores at 15 replicas, and the node ladder is much shorter, from 1 node at 1 replica to 2 nodes at 2 replicas.

The ladder mode exists because the proportional model is wrong for some workloads. A component that is expensive to start and cheap to run, or one whose cost is driven by a discrete unit such as a shard or a leader election, does not scale linearly with the cluster. A ladder lets you say that up to 64 cores one replica is enough, and past 1024 cores you need seven, and the result is a step function that matches how the resource is actually consumed.

The includeUnschedulableNodes flag appears in the ladder form too, with the same meaning as in linear mode.

The examples folder ships a ladder.yaml alongside the linear ones, so the two modes can be compared directly.

## Where the cluster-proportional autoscaler is the wrong choice

The first limitation is that it is not a load-based autoscaler. It has no metrics pipeline and no CPU or memory target, and it will not scale your workload up because it is busy. If you want that, the Horizontal Pod Autoscaler is the tool, and this controller has no relationship to it. Using the cluster-proportional autoscaler for a latency-sensitive service is a category error, and it will happily keep three replicas while the service falls over, because three replicas is what the node count asked for.

The second is the polling model. The controller polls the API server every poll period, with a default of 10 seconds, rather than watching it, so the reaction to a node joining the cluster is bounded below by that interval. For a cluster that grows slowly, that is irrelevant. For an autoscaler that also manages a large fleet of clusters, the aggregate API server load from polling is a real cost, and the flag exists to raise the interval.

The third is the node counting semantics. includeUnschedulableNodes defaults to false, so cordoned and draining nodes are excluded from the count. During a node drain, which is exactly when a cluster operator would want a per-node agent to still be running, the computed replica count drops, and the workload being scaled may go down. The default is defensible, since a drained node is not carrying traffic, but it is a behaviour to think about rather than inherit.

The fourth is the single point of failure question, which the parameters only partly answer. preventSinglePointFailure ensures at least 2 replicas when there is more than one node, which handles the two-replica case but says nothing about a single-node cluster, where one replica is one failure away from no service.

The fifth is the release cadence. The listed releases are v1.10.3 from January 2026, v1.9.0 from November 2024 and v1.8.9 from July 2023, so the project ships infrequently, and the current source tree is ahead of the newest tag. The go.mod asks for Go 1.26.4 and Kubernetes client libraries at v0.37.1, which pins the Kubernetes version the controller is built against, and a stale client library against a newer API server is a compatibility question the release list does not answer.

## Licence, maintenance and the cost of running a sizing controller

The project is under the Apache-2.0 licence, with the LICENSE file at the root of the repository, and it is a Kubernetes SIG project with the usual set of governance files including SECURITY_CONTACTS and a code of conduct.

The repository is not archived and the last push was on 2026-10-13, so the code is being worked on even though releases are infrequent. The module path is github.com/kubernetes-sigs/cluster-proportional-autoscaler and the go.mod requires Go 1.26.4, with k8s.io/api, k8s.io/apimachinery, k8s.io/client-go and k8s.io/component-base all at v0.37.1, plus glog and pflag for the logging and flag handling that the Kubernetes component conventions expect.

The vendor/ directory is committed, which is a Kubernetes SIG convention rather than a personal choice, and it means the build does not depend on the state of a module proxy at build time.

The operational cost of running it is small in resources, since it is a poll loop and a scale operation, and larger in organisational terms. The controller needs RBAC to read nodes and to write the replica count of its target, and the examples folder includes an RBAC example, which tells you the permission set is not trivial. The permission to scale a deployment is a write permission on that workload, and granting it to a controller that is itself running in the cluster is a real privilege, even though the workload being scaled is usually something you already trust.

The other cost is the config drift that comes with parameters living in a ConfigMap. Because the parameters can be changed at any time without a restart, a value edited by hand during an incident can persist long after the incident, and nothing in the controller records what the parameters were when a particular scaling event happened.

## Conclusion

cluster-proportional-autoscaler suits the specific case of a workload whose replica count should follow cluster size, such as a DNS deployment or a per-node agent, because the linear and ladder modes cover both proportional and stepped growth and the parameters can change without a restart. It does not suit ordinary load-based scaling, where the Horizontal Pod Autoscaler is the right tool. Verify the ConfigMap key and mode before deploying, since linear and ladder are separate keys and the wrong one is ignored, and set the min explicitly if a single replica is a single point of failure.

## FAQ

### What does the cluster-proportional-autoscaler scale on?

It polls the API server for the number of nodes and cores in the cluster and resizes the replicas of the target workload to match. The README describes it as suitable for applications that scale with the size of the cluster, such as DNS and other services that scale with the number of nodes or pods.

### What is the difference between linear and ladder mode?

Linear mode computes replicas from a formula using coresPerReplica and nodesPerReplica, clamped by min and max. Ladder mode is an explicit lookup table of thresholds to replica counts, such as [64, 3] and [1024, 7] for cores, which suits a workload whose cost steps rather than scaling in proportion.

### How is the scaling configuration changed without restarting?

The parameters live in a ConfigMap, which the autoscaler refreshes every poll interval. The README says this allows on-the-fly changes including the control mode, without rebuilding or restarting the scaler containers or pods, and that the supported ConfigMap keys are ladder and linear.

### How do I run the cluster-proportional-autoscaler locally?

Pass the kubeconfig flag so it uses your kubeconfig instead of the in-cluster service account. The README gives an example that sets --kubeconfig, --configmap, --target, --namespace and --logtostderr, and for installation it points at the Helm chart repository at kubernetes-sigs.github.io.

## Sources

- [Issues](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/issues)
- [kubernetes-sigs/cluster-proportional-autoscaler on GitHub](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler)
- [License: Apache-2.0](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/master/LICENSE)
- [README](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/master/README.md)
- [Releases](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kubernetes-sigs-cluster-proportional-autoscaler
