Lab 13 – Taints, tolerations a affinity

Lab 13 – Taints, tolerations a affinity

Cíl: řídit, kam pody přistanou – a vidět rozdíl mezi povoleno a přitahováno. Kapitola: Plánování

Tainty odpuzují

# 1 – výchozí stav: 6 podů rozprostřených po třech workerech
kubectl create deploy filler --image nginx:1.27 --replicas 6
kubectl get pods -o wide | sort -k7
# 2 – otaintujte worker03 a sledujte, jak se mu nové pody vyhnou
kubectl taint node worker03 disk=ssd:NoSchedule
kubectl describe node worker03 | grep -i -A2 taint
kubectl scale deploy filler --replicas 12
kubectl get pods -o wide | grep -c worker03      # beze změny

Pody, které na worker03 už byly, tam pořád jsou. NoSchedule ovlivňuje jen umístění; nic nevystěhuje.

# 3 – NoExecute vystěhuje
kubectl taint node worker03 disk=ssd:NoSchedule-           # odeberte první
kubectl taint node worker03 maint=true:NoExecute
kubectl get pods -o wide | grep worker03                   # během sekund prázdno
kubectl get events --sort-by=.lastTimestamp | tail -5

Přesně tohle dělá kubectl drain pod kapotou – jen zdvořile, pod po podu.

# 4 – ukliďte taint
kubectl taint node worker03 maint=true:NoExecute-
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

Tolerance povolují

# 5 – vyhraďte worker03 pro „databázovou“ zátěž
kubectl taint node worker03 workload=db:NoSchedule

cd ~
cat > tolerant.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
  name: tolerant
spec:
  tolerations:
    - key: workload
      operator: Equal
      value: db
      effect: NoSchedule
  containers:
    - name: box
      image: busybox
      command: ["sleep", "3600"]
YAML
kubectl apply -f tolerant.yaml
kubectl get pod tolerant -o wide

Podívejte se, který uzel si vybral. Nejspíš to není worker03. Tolerance řekla „ten taint mi nevadí“, ne „dej mě tam“ – scheduler si mohl vybrat kterýkoli uzel a ten prázdný nemusel být nejlepší volba.

Tohle je nejčastěji nepochopená věc kolem taintů. Odpuzování a přitahování jsou dvě samostatná rozhodnutí.

# 6 – teď ho ještě přitáhněte
kubectl label node worker03 workload=db
kubectl delete -f tolerant.yaml
cat > pinned.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
  name: pinned
spec:
  tolerations:
    - key: workload
      operator: Equal
      value: db
      effect: NoSchedule
  nodeSelector:
    workload: db
  containers:
    - name: box
      image: busybox
      command: ["sleep", "3600"]
YAML
kubectl apply -f pinned.yaml
kubectl get pod pinned -o wide          # worker03, zaručeně

Taint + tolerance + selektor je kompletní recept na „vyhrazené uzly“: taint tam nikoho jiného nepustí, tolerance pustí tenhle pod, selektor ho tam pošle.

Node affinity

# 7 – označte dva uzly a vyžádejte si jeden z nich
kubectl label node worker01 disktype=ssd
kubectl label node worker02 disktype=hdd
kubectl get nodes -L disktype

cat > affinity.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
  name: needs-ssd
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values: ["ssd"]
  containers:
    - name: box
      image: busybox
      command: ["sleep", "3600"]
YAML
kubectl apply -f affinity.yaml
kubectl get pod needs-ssd -o wide       # worker01
# 8 – co když nic neodpovídá?
kubectl label node worker01 disktype=hdd --overwrite
kubectl delete pod needs-ssd
kubectl apply -f affinity.yaml
kubectl get pod needs-ssd               # Pending
kubectl describe pod needs-ssd | tail -6

didn't match Pod's node affinity/selector. Required pravidlo, kterému nic neodpovídá, znamená, že pod nikdy nepoběží – nemá záložní variantu.

# 9 – měkká varianta naplánuje vždy
kubectl delete pod needs-ssd
cat > prefer.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
  name: prefers-ssd
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: disktype
                operator: In
                values: ["ssd"]
  containers:
    - name: box
      image: busybox
      command: ["sleep", "3600"]
YAML
kubectl apply -f prefer.yaml
kubectl get pod prefers-ssd -o wide     # běží, i když žádný uzel ssd nemá
# 10 – IgnoredDuringExecution v praxi
kubectl label node worker02 disktype=ssd --overwrite
kubectl get pod prefers-ssd -o wide     # NEpřesunul se

Affinity se vyhodnocuje jednou, při plánování. Pozdější změna labelů s běžícími pody nic neudělá.

Objevovací část

Infodiscovery

Úkol A. Zařiďte, aby Deployment filler rozprostřel repliky tak, že žádné dva jeho pody nesedí na stejném uzlu, pokud to jde. Očekávaný výsledek: se třemi replikami jeden pod na každý worker.

kubectl scale deploy filler --replicas=3
kubectl patch deploy filler -p '{"spec":{"template":{"spec":{"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchLabels":{"app":"filler"}},"topologyKey":"kubernetes.io/hostname"}}]}}}}}}'
kubectl get pods -l app=filler -o wide | sort -k7

Modernější varianta, která místo pouhého odpuzování vyvažuje:

kubectl patch deploy filler -p '{"spec":{"template":{"spec":{"topologySpreadConstraints":[{"maxSkew":1,"topologyKey":"kubernetes.io/hostname","whenUnsatisfiable":"ScheduleAnyway","labelSelector":{"matchLabels":{"app":"filler"}}}]}}}}'

Úkol B. Najděte jedním příkazem všechny tainty v clusteru včetně těch, které si nastavil Kubernetes sám.

kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
kubectl describe nodes | grep -i -B3 'Taints:'

control01 nese taint control-plane – proto ho váš DaemonSet v labu 12 přeskočil.

Úkol C. Bez použití kubectl drain zařiďte, aby worker02 odmítal nové pody a ty stávající poslal jinam. Pak to vraťte.

kubectl taint node worker02 evacuate=yes:NoExecute
kubectl get pods -o wide | grep -c worker02      # 0
kubectl taint node worker02 evacuate=yes:NoExecute-

Drain je ohleduplnější – respektuje PodDisruptionBudgety a vystěhovává postupně – ale principem je tenhle taint.

Úkol D. Vysvětlete, proč kubectl cordon worker01 nepotřebuje taint, který byste psali sami.

kubectl cordon worker01
kubectl get node worker01 -o jsonpath='{.spec.taints}{"\n"}'
kubectl uncordon worker01

Cordon přidá node.kubernetes.io/unschedulable:NoSchedule. Všechny příkazy na uzly z labu 16 jsou pod kapotou tainty.

Úklid

kubectl delete pod pinned needs-ssd prefers-ssd --ignore-not-found
kubectl delete deploy filler
kubectl taint node worker03 workload=db:NoSchedule-
kubectl label node worker01 disktype- ; kubectl label node worker02 disktype-
kubectl label node worker03 workload-
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints