Autoscaling op Kubernetes. Hoe werkt dat? - Deel 1

Thomas Kooi

Platform Architect

Published: 25 January, 2022

Met Kubernetes kunnen applicaties op 100% geautomatiseerde wijze beheerd worden. Eén van de handige functies in Kubernetes is het concept van horizontaal autoscaling van applicaties. In deze blog gaan we dieper in op de manieren waarop jij autoscaling kan implementeren én waar je goed op moet letten. Deze blog bestaat uit twee delen, dit is deel één. Klik hier voor deel twee waarin de focus ligt op autoscaling met ingress-nginx en Linkerd.

In het kort: waarom je autoscaling wil gebruiken

Autoscaling kan ingezet worden om resources efficiënter in te zetten door gebruik te maken van minder VM's, zoals bijvoorbeeld tijdens piektijden. Als je handmatig op piektijden moet inspelen, dan ben je vaak te langzaam waardoor er niet optijd genoeg capaciteit is en applicaties langzamer zullen werken. Dit kan ervoor zorgen dat jouw applicaties onbeschikbaar of bruikbaar worden! Maar wanneer is autoscaling niet relevant? Autoscaling is niet relevant op het moment dat je een statisch, voorspelbaar applicatielandschap hebt, omdat het dan weinig toegevoegde waarde heeft. Je applicatie zal namelijk stabiel blijven.

Wat zijn de voorwaarden voor autoscaling?

💡 Maak je gebruik van een AME Kubernetes-cluster, dan is autoscaling standaard ondersteund.

Om autoscaling toe te kunnen passen, is ondersteuning voor de metrics-API in je cluster vereist. De meeste managed Kubernetes-providers ondersteunen dit, zonder dat hier een aanpassing voor gedaan hoeft te worden. De meestvoorkomende implementatie is de metrics-server. Via kubectl top node kun je valideren of jouw cluster de metrics-api ondersteunt.

$ kubectl top node
NAME                                      CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
ip-10-0-0-7.eu-west-1.compute.internal    100m         5%     1781Mi          51%
ip-10-0-0-92.eu-west-1.compute.internal   162m         8%     1927Mi          56%


Indien de metrics-API niet beschikbaar is binnen je cluster, kan je deze zelf makkelijk installeren middels kubectl:

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml


Ook zijn er opties voor meer geavanceerde set-ups, die custom metrics ondersteunen. In deel 2 van de blog gaan we hier dieper op in.

Zo werkt het

Autoscaling wordt geconfigureerd met een resource genaamd HorizontalPodAutoscaler (HPA) in Kubernetes. HPA biedt ondersteuning voor zowel Deployments als StatefulSets.

Als voorbeeld beginnen wij met het installeren van een nieuwe Deployment, genaamd myapp (example.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "100m"
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80


We installeren deze resources uit de example.yaml via kubectl apply:

$ kubectl apply -f example.yaml 
deployment.apps/myapp created
service/myapp created

$ kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
myapp-58fd9b8cb7-4pf4d   1/1     Running       0          4s


Na de uitrol is er een pod voor de myapp deployment. Initieel zal deze nagenoeg geen CPU in gebruik hebben. Dit kan worden gevalideerd door gebruik te maken van kubectl top pod:

$ kubectl top pod
NAME                    CPU(cores)   MEMORY(bytes)          
myapp-5664749b7-bblqk   0m          2Mi         


Het is mogelijk dat je direct na het opstarten van de pod, de volgende melding krijgt in plaats van bovenstaande output: "error: metrics not available yet". Dit komt omdat de metrics-server eerst nog metrics moet verzamelen voor deze pod. Na ongeveer een minuut zullen er metrics beschikbaar zijn en zal het top commando wel output geven.

Hierna starten wij een load generator op. Hiervoor maken wij gebruik van het slow_cooker project van Buoyant (de ontwikkelaars achter linkerd).

kubectl run load-generator --image=buoyantio/slow_cooker -- -qps 100 -concurrency 10 http://myapp


Met deze load generator genereren wij 100 queries (requests) per seconde tegen de zojuist uitgerolde Deployment. Dit doen wij om verkeer te simuleren waarop wij kunnen autoscalen. Je kan de logs van de load-generator pod volgen om verschillende metrics te zien, zoals latency. Als je even wacht en de kubectl top pod opstart, zul je zien dat het CPU-gebruik gestegen is.

$ kubectl top pod
NAME                    CPU(cores)   MEMORY(bytes)   
load-generator          128m         5Mi             
myapp-5664749b7-bblqk   79m          2Mi         


We gaan nu beginnen met het configureren van een HPA policy. Dit doen we met onderstaande code.

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60

Binnen scaleTargetRef refereren we aan de Deployment die we eerder hebben gecreëerd. We configureren minReplicas en maxReplicas, dit zijn de onder- en bovengrenzen voor het aantal instanties (replicas) van een pod. Tot slot configureren we de metrics waarop wij het aantal replicas binnen de deployment willen schalen. In bovenstaand voorbeeld doen we dit op basis van het gemiddelde CPU-gebruik.

De autoscaler reageert zodra het CPU-gebruik boven de 60% van de gestelde limiet komt. Aangezien de limiet voor onze Deployment is gesteld op 100m, zal de autoscaler starten met op- of afschalen bij een CPU gebruik van 60m.

Wanneer treedt de autoscaler in werking?

Het automatisch op- en afschalen gebeurt op basis van het gemiddelde gebruik.

  1. Als er twee pods zijn waarbij één op 90m en de andere op 30m (gemiddeld 60m) zal er geen trigger worden afgegeven.
  2. Indien er twee pods zijn waarbij één op 90m en de andere op 50m (gemiddeld 70m) zal er wel een trigger worden afgegeven.
Belangrijk om hierbij te realiseren is dat er een 10% tolerantie-marge wordt gehanteerd voordat de autoscaler een actie onderneemt. Dit voorkomt herhaalijke starten en stoppen van pods als gevolg van een snel fluctuerend CPU-gebruik.

HPA scale in actie

Zodra de HPA policy toegevoegd is, duurt het ongeveer een minuut voordat het effect heeft. 

$ kubectl get hpa
NAME    REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
myapp   Deployment/myapp   75%/60%   1         10        1          51s

Kort hierna zal er een nieuwe pod starten.

$ kubectl get hpa
NAME    REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
myapp   Deployment/myapp   40%/70%   1         10        2          8m42s

$ kubectl describe hpa myapp
Name:                                                  myapp
Namespace:                                             default
Labels:                                                
Annotations:                                           
CreationTimestamp:                                     Sat, 07 Aug 2021 12:30:10 +0200
Reference:                                             Deployment/myapp
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  40% (40m) / 70%
Min replicas:                                          1
Max replicas:                                          10
Deployment pods:                                       2 current / 2 desired
Conditions:
  Type            Status  Reason              Message
  ----            ------  ------              -------
  AbleToScale     True    ReadyForNewScale    recommended size matches current size
  ScalingActive   True    ValidMetricFound    the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
  ScalingLimited  False   DesiredWithinRange  the desired count is within the acceptable range
Events:
  Type    Reason             Age    From                       Message
  ----    ------             ----   ----                       -------
  Normal  SuccessfulRescale  5m26s  horizontal-pod-autoscaler  New size: 2; reason: cpu resource utilization (percentage of request) above target

Je zal zien dat de CPU-utilisation van beide pods nu onder de target utilisation zit:

NAME                    CPU(cores)   MEMORY(bytes)   
load-generator          139m         5Mi             
myapp-5664749b7-bblqk   41m          2Mi             
myapp-5664749b7-lrj8g   54m          2Mi  

Daarnaast kan autoscaling ook geconfigureerd worden op basis van geheugengebruik:

  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 50

Merk op dat omdat metrics een reeks is, je meerdere regels kan gebruiken:

$ kubectl get hpa
NAME    REFERENCE          TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
myapp   Deployment/myapp   2%/50%, 48%/60%   1         10        2          19m0

Waar moeten we voor uitkijken?

Er zijn een paar dingen waar rekening mee gehouden moet worden wanneer je gebruikmaakt van autoscaling.

Clustercapaciteit
Je cluster moet genoeg capaciteit hebben om de nieuwe workloads te kunnen opstarten. Een oplossing hiervoor kan zijn om node-autoscaling aan te zetten (niet elk cluster ondersteund dit). Let hierbij op dat het enige tijd kan duren voordat de nieuwe nodes beschikbaar zijn. Het op- en afschalen van pods gaat sneller dan het op- en afschalen van nieuwe nodes. Om hiermee om te kunnen gaan, heb je voldoende capaciteit nodig binnen je cluster voor een autoscaling-actie, terwijl jij (of jouw cloud-provider) nieuwe nodes provisioned en toevoegt aan jouw cluster.

Als alternatief kan je jouw cluster over-dimensioneren zodat er ruim voldoende capaciteit is om het aantal pods dat wordt opgestart bij autoscaling op te vangen.

Configureer betrouwbare implementaties
Als je gebruikmaakt van autoscaling, wil je er zeker van zijn dat je een paar properties goed geconfigureerd hebt in je implementatie. Hiermee voorkom je (connectie-)errors tijdens scaling operations. Vaak zijn dit dezelfde properties die nodig zijn om zero-downtime implementaties te ondersteunen.

Probes
Configureer een readinessProbe en startupProbe. Op deze manier zorg je ervoor dat een pod pas traffic ontvangt wanneer deze volledig gereed is. 

Bijvoorbeeld:

startupProbe:
  httpGet:
    path: /
    port: http
  initialDelaySeconds: 5
  periodSeconds: 5
readinessProbe:
  httpGet:
    path: /
    port: http
  initialDelaySeconds: 5
  periodSeconds: 5


Sluit je applicatie goed af

Het is belangrijk dat jouw applicaties bij het afsluiten zorgen dat al het werk waaraan begonnen is, eerst wordt afgehandeld. Dit zorgt ervoor dat je geen fouten krijgt zodra de autoscaler het aantal pods naar beneden schaalt.

Daarnaast is het handig om enkele seconden te wachten voordat je applicatie het stop-proces begint. Aangezien Kubernetes een distributed systeem is, duurt het even voordat alle systemen doorhebben dat een pod op een andere node aan het afsluiten is. Door enkele seconden te wachten, krijgen andere systemen de kans om te stoppen met het versturen van nieuwe requests naar een pod die afsluit, voordat deze stopt met het accepteren van nieuw verkeer. Hiervoor moet je applicatie tijdens deze status initieel nog verkeer afhandelen.

Verder kan overwogen worden om in upstream componenten een retry mechanism te implementeren die voor gefaalde connecties een retry uitvoert. Echter is een retry niet geschikt voor elke type transactie of call.

Configureer geen replicas
Zorg ervoor dat je het replicas-veld in je Deployment niet configureert, wanneer je gebruikmaakt van een horizontal pod autoscaler. Wanneer je deze toch configureert, zal bij de uitrol deze waarde gerespecteerd worden, waarna de autoscaler dit weer reset. Dit zorgt voor onnodige opstart en/of afsluit acties.

Gebruik de HorizontalPodAutoscaler resource om het minimum aantal benodigde replicas te configureren via spec.minReplicas

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  # This will make sure there are always at least 3 pods in the myapp deployment
  minReplicas: 3
  maxReplicas: 10


Custom metrics
Horizontal pod autoscaling kan ook worden geconfigureerd op basis van custom metrics. Dit kan je doen via bijvoorbeeld de Prometheus metrics adapter. Hiermee kan je jouw HPA configureren om op en neer te schalen gebaseerd op het aantal verzoeken per seconde, latentie of een andere metric. In deel twee van deze blog gaan wij hier dieper op in.

Conclusie

Nu je weet hoe autoscaling werkt, kan je zelf aan de slag gaan! In deel twee van deze blog kijken we naar hoe custom metrics gebruikt kunnen worden voor het autoscaling van je deployments. 

Door de State-mindset van Cloud Provider Hosts kost het developers veel tijd en handwerk om softwarereleases te doen. Wij van Avisi Cloud vinden dat het uitrollen van software geautomatiseerd moet worden en dat vanuit de Change-mindset gehandeld moet worden. Daarom hebben wij Avisi Managed Environments gecreëerd, waarmee jij 20% extra ontwikkelcapaciteit kan behalen. Wil jij ook 20% extra ontwikkelcapaciteit? Neem dan contact met ons op.

Related blogs

Did you enjoy reading?

Share this blog with your audience!