Kubernetes Scheduling

In deze post woren de verschillende manieren om pods in Kubernetes te schedulen en te beheren behandeld. Het gaat hier over handmatig pods toewijzen met nodeNames of BindingObject, en hoe je d.m.v. labels en selectors resources kunt organiseren. Daarnast worden taints en tolerations behandeld, die bepalen welke pods op welke nodes mogen draaien. Ten slotte wordt node affinity behandeld, waarna er kort gesproken wordt over de werking van DaemonSets.

Manual Scheduling

Elk manifest bevat onderwater de field nodeName, deze wordt vaak niet gedefinieerd omdat Kubernetes deze zelf invult. Kubernetes checkt welke nodes er zijn, en welke hiervan nog resources over hebbben om een extra pod te kunnen draaien. het veld nodeName kan ingesteld worden om zo handmatig pods te schedulen om opgezet te worden. De nodeName kan alleen bij het aanmaken worden ingesteld en kan daarna niet meer worden aangepast.

Binding Object

Een andere manier om een pod aan een specifieke node te koppelen, is door een Binding Object te gebruiken. Dit object linkt een pod aan een node op basis van de opgegeven gegevens.

apiVersion: v1 
kind: Binding 
metadata: 
  name: nginx
target: 
  apiVersion: v1 
  kind: Node 
  name: worker-node1

Labels & Selectors

Labels zijn een dictionary in de metadata-tag die kan worden gebruikt op objecten. Ze bieden een manier om resources te organiseren en te groeperen. Labels bestaan uit key-values en kunnen op elk moment worden toegevoegd of gewijzigd zonder de ondeliggende objecten aan te passen

Selectors worden gebruikt om objecten te filteren op basis van labels. Ze worden toegepast bij services, deployments en replica sets om alleen de juiste resources te targeten.

apiVersion: apps/v1 
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector: 
    matchLabels: 
      app: nginx
  template:
    metadata: 
      labels: 
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest

Taints & Tolerations

Taints en Tolerations worden in Kubernetes gebruikt om te bepalen welke pods wel of niet op een bepaalde node mogen draaien.

Taints

Een taint wordt toegepast op een node om te voorkomen dat pods daar worden gescheduled, tenzij ze de bijpassende toleration hebben. Hierbij is het belangrijk om te onthouden dat een node meerdere taints kan hebben, maar standaard geen taints heeft.

Er zijn in totaal drie verschillende taint effecten:

  • NoSchedule: Nieuwe pods zonder de bijpassende toleration worden niet gescheduled op de node
  • PreferNoSchedule: De scheduler probeert te vermijden dat nieuwe pods op deze node worden geplaatst, maar het is geen strikte regel
  • NoExecute: Nieuwe pods zonder een bijpassende toleration worden niet gescheduled, en bestaande pods zonder de juiste toleration worden verwijderd van de node.

Het toevoegen van een taint kan met het volgende commando

kubectl taint nodes node01 stage=development:NoSchedule 

Hiermee kan geen enkele pod op node01 draaien zonder de toleration stage=development:NoSchedule

Tolerations

Een toleration wordt toegevoegd aan een pod om het toe te staan dat het mag draaien op een node met de bijpassende taint. Een toleration dwingt niet af dat een pod op een bepaalde node draait, het maakt het alleen mogelijk.

Het zetten van een toleration kan op de volgende manier:

apiVersion: v1 
kind: Pod
metadata: 
  name: nginx
spec: 
  containers: 
  - name: nginx 
    image: nginx:latest
  tolerations:
  - key: "stage"
    operator: "Equal"
    value: "development"
    effect: "NoSchedule"

Met bovenstaande manifest kan een pod draaien op elke node met de taint stage=development:NoSchedule

Node Selectors

Standaard kan een pod op elke node in het cluster worden gescheduled. Om dit te beperken tot specifieke nodes, kan een NodeSelector worden gebruikt

met een NodeSelector geef je aan dat een pod alleen mag draaien op nodes met een specifiek label. Bijvoorbeeld met de volgende pod-configuratie:

apiVersion: v1 
kind: Pod
metadata: 
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:latest
  nodeSelector: 
    stage: development

Om ervoor te zorgen dat de NodeSelector een node kan vinden, moet de betreffende node wel een bijpassend label hebben. Dit kan worden ingesteld met het volgende commando:

kubectl label nodes node01 stage=development

Node Affinity

Node affinity is een manier om pods aan een specifieke node te koppelen, netzoals NodeSelectors. Het maakt het mogelijk om complexere regels te definiëren over waar een pod mag draaien. De node affinity kan worden aangegeven met meerdere types, namelijk:

TypeEffect
requiredDuringSchedulingIgnoredDuringExecutionDe pod moet op een node draaien die aan de voorwaarden voldoet. Als er geen geschikte node is, wordt de pod niet gescheduled.
preferredDuringSchedulingIgnoredDuringExecutionKubernetes probeert de pod op een geschikte node te plaatsen, maar als dat niet kan, wordt de pod alsnog elders gescheduled.
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
  affinity: 
    nodeAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution: 
        nodeSelectorTerms: 
        - matchExpressions:
          - key: stage
            operator: In
            values: ["development"]

Zoals te zien in bovenstaande manifest wordt er gebruik gemaakt van operators, de volgende operators bestaan binnen Kubernetes:

OperatorFunctie
InDe waarde van het label moet gelijk zijn aan de opgegeven waarde
NotInDe waarde van het label mag niet gelijk zijn aan de opgegeven waarde
ExistsHet label moest bestaan op de node
DoesNotExistHet label mag niet bestaan op de node
GtDe waarde van het label moet groter zijn dan de opgegeven waarde
LtDe waarde van het label moet kleiner zijn dan de opgegeven waarde

Resource Scheduling

In Kubernetes kunnen resource requests en limits ingesteld worden om te bepalen hoeveel CPU en geheugen een container minimaal nodig heeft en maximaal mag gebruiken.

Resource Request

Een resource request geeft de minimale CPU en geheugen hoeveelheid aan die een container nodig heeft om te draaien. De Kubernetes-scheduler gebruik deze waarde op zijn beurt om te bepalen op welke node de pod geplaatst kan worden. Nodes moeten ten minste de gevraagde resources hebben, anders wordt de pod niet gescheduled

Resource Limits

Een resource limit geeft de maximale CPU en geheugen hoeveelheid aan die een container mag gebruiken. Als een container probeert om meer te gebruiken dan ingesteld, kan het cluster bepalen om de pod af te sluiten.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    resources:
    requests:
        memory: "3Gi"
        cpu: "1"
    limits:
        memory: "4Gi"
        cpu: "2"

In bovenstaande manifest heeft de pod minimaal 3GiB en 1 CPU nodig om te draaien, de Kubernetes scheduler gebruikt deze waardes om een bijpassende node te vinden. De pod mag maximaal 4GiB geheugen en 2 CPU’s gebruiken.

DaemonSets

Een DaemonSet lijkt op een ReplicaSet, maar het verschil is dat een DaemonSet ervoor zorgt dat er exact één pod op elke node draait, in plaats van een specifiek aantal pods op één node. Als er een nieuwe node wordt toegevoegd aan het cluster, wordt er automatische een pod op deze node toegevoegd. Dit maakt DaemonSets erg toepasbaar voor bijvoorbeeld monitoring en logging.

Een Daemonset kan worden gedefinieerd met het volgende manifest:

apiVesrion: apps/v1 
kind: DaemonSet
metadata:
  name: monitoring
spec:
  selector:
    matchLabels:
      app: monitoring-agent 
  template:
    metadata: 
      labels: 
        app: monitoring-agent
    spec:
      containers:
        name: monitoring-agent
        image: prometheus