Autoscaling an SKS Nodepool
One of the prime features of running your applications on Kubernetes is to be able to scale your cluster without your intervention based on the current workload.
In Kubernetes, one can scale:
- the Pods themselves vertically by raising and lowering resource requests using the Vertical Pod Autoscaler
- the Pods horizontally by raising and lowering the number of Pods in a deployment using the Horizontal Pod Autoscaler (See the test down below for an example)
- the number of nodes by resizing the nodepool based on either node utilization or Pod deployment requirements using the Cluster Autoscaler
Here we will describe the Cluster Autoscaler option.
As a prerequisite for the following documentation, you need:
- An Exoscale SKS (Pro) Cluster
- Access to your Cluster via kubectl
- Basic Linux knowledge
If you don’t have access to an SKS cluster yet, follow the Quick Start Guide.
Deploying the Cluster Autoscaler
You can find the Kubernetes Autoscaler here and the Exoscale provider in the ./cluster-autoscaler/cloudprovider/exoscale folder.
As described there in the README, you need to first create a secret containing an appropriate API key as well as the zone of your cluster:
export EXOSCALE_API_KEY="EXOxxxxxxxxxxxxxxxxxxxxxxxx" export EXOSCALE_API_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" export EXOSCALE_ZONE="ch-gva-2" kubectl -n kube-system create secret generic exoscale-api-credentials \ --from-literal=api-key="$EXOSCALE_API_KEY" \ --from-literal=api-secret="$EXOSCALE_API_SECRET" \ --from-literal=api-zone="$EXOSCALE_ZONE"
This API key must be authorized to perform following API operations:
Afterwards you can grab the deployment manifests and deploy the Autoscaler:
kubectl apply -f cluster-autoscaler.yaml
When testing, you should adjust the commented timeouts towards the end of the file to see scale-down happening within a minute or so instead of the slower behaviour one wants in a production deployment.
In case your cluster has multiple nodepools, you might want to tell the Cluster Autoscaler which nodepool should be scaled up and down. Otherwise a random nodepool will be scaled.
To do this, you need to create a
ConfigMap with your nodepools Instance Pool
ID (NOT the nodepool ID):
apiVersion: v1 kind: ConfigMap metadata: name: cluster-autoscaler-priority-expander namespace: kube-system data: priorities: |- 50: - 00719c6e-1d06-4053-afea-8926c3431ef7
As well as adding an argument to the autoscaler deployment:
The Cluster Autoscaler will then target this prioritized nodepool instead.
Putting it to the Test
To test everything is working as it should, we deploy a
DaemonSet that is very
busy but still fit in our current deployment of two nodes:
apiVersion: apps/v1 kind: Deployment metadata: name: stress namespace: default spec: replicas: 2 selector: matchLabels: run: stress template: metadata: labels: run: stress spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: run operator: In values: - stress topologyKey: kubernetes.io/hostname containers: - image: nixery.dev/stress:latest name: stress command: - stress - --cpu - "1" resources: limits: cpu: 300m memory: 30Mi requests: cpu: 150m memory: 15Mi
Note how we use a podAntiAffinity to ensure these Pods can’t share a node. This reflects real requirements but the node filling up with Pods would result in a similar behaviour.
After this is is deployed, the CPU limit will be consumed in each Pod and the Kubernetes metrics show this:
$ kubectl top pods NAME CPU(cores) MEMORY(bytes) stress-d77bdc8db-22dwb 292m 0Mi stress-d77bdc8db-pd4xn 291m 0Mi
We now use the Horizontal Pod Autoscaler to react on these internal metrics and increase this deployments replicas to 11:
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: stress namespace: default spec: minReplicas: 1 maxReplicas: 11 metrics: - resource: name: cpu target: averageUtilization: 50 type: Utilization type: Resource scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: stress
We can see new Pods being spawned and new nodes being added after a short while as the Pods can’t be scheduled onto an existing node:
NAME STATUS ROLES AGE VERSION pool-0f4cd-fquea Ready <none> 3d6h v1.23.3 pool-0f4cd-llrsh NotReady <none> 16s v1.23.3 pool-0f4cd-vhend NotReady <none> 15s v1.23.3 pool-0f4cd-ygwhj Ready <none> 3d6h v1.23.3 pool-0f4cd-zknko NotReady <none> 15s v1.23.3 [...]
You can look into the Cluster Autoscaler logs to see it taking action:
$ kubectl -n kube-system logs deployment/cluster-autoscaler [...] I0324 19:50:12.521062 1 scale_up.go:675] Scale-up: setting group 0f4cd2ad-2825-4ae7-aaa2-1fd0f8e0af19 size to 5 I0324 19:50:12.530709 1 log.go:32] exoscale-provider: scaling SKS Nodepool afe7aa21-b4f6-409b-a70b-6fc31d6fada1 to size 5 [...]
Once the new nodes are ready, the Pods will start up on these. When you remove the test deployment, superfluous nodes will be removed after a grace period.
Tips and Tricks
- It can be useful to add
--v4to the list of arguments to get more information in the logs about why the Cluster Autoscaler makes a certain decision.
- Certain Pods can prevent the Autoscaler from removing a node. See the CA FAQ for more on that.
- You can also annotate certain nodes not to be touched by the CA
- Longhorn Users: Note that the Cluster Autoscaler is not fully supported (see)