← До програми курсу

Модуль 12 / 19

StatefulSet, DaemonSet, Job і CronJob

KUBERNETES

Зміст
01

1. Цілі та результат модуля

Ви навчитеся обирати controller за потрібною гарантією: стабільна identity, один Pod на Node, скінченне завдання або розклад.

  • StatefulSet дає ordinal identity та впорядковані операції
  • DaemonSet узгоджує Pod із придатними Nodes
  • Job відстежує успішні completions
  • CronJob створює Jobs за розкладом
  • жоден controller сам не робить застосунок HA
02

2. Теорія: controller обирають за семантикою

Deployment підходить взаємозамінним replicas. StatefulSet потрібен, коли важливі стабільні імена чи порядок. DaemonSet моделює node-local функцію. Job має кінцевий результат, CronJob — розклад створення Jobs. Вибір за звичкою створює неправильні lifecycle guarantees.

03

3. StatefulSet: identity і порядок

Pod отримує ім’я <set>-<ordinal>, стабільний network identity через headless Service та передбачувані PVC names із volumeClaimTemplates. Видалений web-0 створюється знову як web-0, але з новим UID.

StatefulSet не реплікує дані й не гарантує кворум. Без persistent volume дані контейнера зникають. OrderedReady може блокувати progress; Parallel змінює ordering guarantees.

04

4. DaemonSet: node-local workload

DaemonSet controller створює Pod на кожному придатному Node з урахуванням selector, affinity, taints/tolerations. Типові випадки: log agent, CNI, storage plugin, node exporter. Кількість Pods дорівнює eligible Nodes, а не фіксованому replicas.

На одновузловому стенді очікується один Pod. У HA-курсі той самий manifest покаже різницю між control-plane та worker placement.

05

5. Job: completion semantics

Job керує Pods до потрібної кількості успішних completions. parallelism обмежує одночасну роботу; backoffLimit — повтори після failures; activeDeadlineSeconds — загальний час. restartPolicy для Job — Never або OnFailure.

Завдання має враховувати at-least-once execution: код повинен бути ідемпотентним або дедуплікувати side effects.

06

6. CronJob: розклад, а не точний таймер

CronJob створює Jobs за cron schedule. Controller loop, downtime і clock skew означають, що запуск не є real-time сигналом. timeZone задавайте явно. concurrencyPolicy Allow, Forbid або Replace керує перетином Jobs, але не замінює locking у застосунку.

startingDeadlineSeconds обмежує запізнення; suspend зупиняє нові запуски; history limits чистять старі Jobs, але не є log retention.

07

7. Методичні вказівки

  • Формулюйте lifecycle guarantee до kind
  • Не називайте StatefulSet базою даних високої доступності
  • Перевіряйте ownerReferences і status controller
  • Для DaemonSet рахуйте eligible Nodes
  • Робіть Jobs ідемпотентними
  • Задавайте timeZone CronJob
  • Не використовуйте history як архів логів
  • Читайте Job conditions, failed Pods та Events разом
08

8. Лабораторна робота: чотири controller

Створіть headless Service, StatefulSet, DaemonSet, Job і CronJob. Повторний apply після першого проходу потрібен для namespaced документів після створення Namespace.

terminal
mkdir -p /tmp/k8s-m12
cat > /tmp/k8s-m12/workloads.yaml <<'EOF'
apiVersion: v1
kind: Namespace
metadata: {name: k8s-m12}
---
apiVersion: v1
kind: Service
metadata: {name: peers, namespace: k8s-m12}
spec:
  clusterIP: None
  selector: {app: stateful-web}
  ports: [{name: http, port: 80}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: web, namespace: k8s-m12}
spec:
  serviceName: peers
  replicas: 2
  selector: {matchLabels: {app: stateful-web}}
  template:
    metadata: {labels: {app: stateful-web}}
    spec:
      containers:
      - name: nginx
        image: nginx:1.29-alpine
        command: ["sh", "-c"]
        args: ["printf 'pod=%s\n' "$HOSTNAME" > /usr/share/nginx/html/index.html; exec nginx -g 'daemon off;'"]
        ports: [{name: http, containerPort: 80}]
        readinessProbe: {httpGet: {path: /, port: http}, periodSeconds: 2}
---
apiVersion: apps/v1
kind: DaemonSet
metadata: {name: node-agent, namespace: k8s-m12}
spec:
  selector: {matchLabels: {app: node-agent}}
  template:
    metadata: {labels: {app: node-agent}}
    spec:
      containers:
      - name: agent
        image: busybox:1.37
        command: ["sh", "-c", "echo node=$NODE_NAME; sleep 3600"]
        env: [{name: NODE_NAME, valueFrom: {fieldRef: {fieldPath: spec.nodeName}}}]
---
apiVersion: batch/v1
kind: Job
metadata: {name: batch, namespace: k8s-m12}
spec:
  completions: 3
  parallelism: 2
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
      - {name: task, image: busybox:1.37, command: ["sh", "-c", "echo completed-by=$HOSTNAME; sleep 2"]}
---
apiVersion: batch/v1
kind: CronJob
metadata: {name: report, namespace: k8s-m12}
spec:
  schedule: "*/5 * * * *"
  timeZone: Etc/UTC
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - {name: report, image: busybox:1.37, command: ["sh", "-c", "date -Iseconds; echo report=$HOSTNAME"]}
EOF
kubectl apply -f /tmp/k8s-m12/workloads.yaml || true
kubectl apply --dry-run=server -f /tmp/k8s-m12/workloads.yaml
kubectl apply -f /tmp/k8s-m12/workloads.yaml
09

9. Лабораторна робота: stable identity

Перевірте DNS web-0/web-1. Після видалення web-0 ім’я зберігається, UID змінюється: identity controller стабільна, object instance — нова.

terminal
kubectl -n k8s-m12 rollout status statefulset/web --timeout=180s
kubectl -n k8s-m12 get statefulset,pod -l app=stateful-web -o wide
kubectl -n k8s-m12 run dns-client --image=busybox:1.37 --restart=Never --command -- sleep 3600
kubectl -n k8s-m12 wait --for=condition=Ready pod/dns-client --timeout=120s
kubectl -n k8s-m12 exec dns-client -- nslookup web-0.peers
kubectl -n k8s-m12 exec dns-client -- nslookup web-1.peers
old_uid=$(kubectl -n k8s-m12 get pod web-0 -o jsonpath='{.metadata.uid}')
kubectl -n k8s-m12 delete pod web-0 --wait=true
kubectl -n k8s-m12 wait --for=condition=Ready pod/web-0 --timeout=120s
kubectl -n k8s-m12 get pod web-0 -o jsonpath='name={.metadata.name}{" uid="}{.metadata.uid}{"\n"}'
printf 'old uid=%s\n' "$old_uid"
10

10. Лабораторна робота: DaemonSet, Job і CronJob

Зіставте один agent Pod з одним Node. Потім дослідіть 3 completions за parallelism 2 і створіть негайний Job із CronJob template без очікування розкладу.

terminal
kubectl -n k8s-m12 rollout status daemonset/node-agent --timeout=120s
kubectl get nodes -o wide
kubectl -n k8s-m12 get daemonset node-agent
kubectl -n k8s-m12 get pods -l app=node-agent -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName,OWNER:.metadata.ownerReferences[0].kind
kubectl -n k8s-m12 logs -l app=node-agent --prefix
terminal
kubectl -n k8s-m12 wait --for=condition=Complete job/batch --timeout=120s
kubectl -n k8s-m12 get job batch
kubectl -n k8s-m12 get pods -l job-name=batch
kubectl -n k8s-m12 logs job/batch --prefix

kubectl -n k8s-m12 create job report-now --from=cronjob/report
kubectl -n k8s-m12 wait --for=condition=Complete job/report-now --timeout=120s
kubectl -n k8s-m12 logs job/report-now
kubectl -n k8s-m12 get cronjob report -o yaml
11

11. Діагностика: вичерпаний backoff

Завдання завжди повертає 42. Job створює failed Pods до вичерпання backoffLimit та отримує Failed condition. Логів одного Pod недостатньо: перевіряйте всі attempts, Job status і Events.

terminal
cat > /tmp/k8s-m12/failing-job.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata: {name: failing, namespace: k8s-m12}
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
      - {name: task, image: busybox:1.37, command: ["sh", "-c", "echo deliberate failure >&2; exit 42"]}
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m12/failing-job.yaml
kubectl apply -f /tmp/k8s-m12/failing-job.yaml
kubectl -n k8s-m12 wait --for=condition=Failed job/failing --timeout=120s
kubectl -n k8s-m12 get job failing -o wide
kubectl -n k8s-m12 get pods -l job-name=failing
kubectl -n k8s-m12 logs job/failing --prefix
kubectl -n k8s-m12 describe job failing
kubectl -n k8s-m12 get events --sort-by=.metadata.creationTimestamp

Порядок: controller spec/status → owned Pods → scheduling/image/startup → container terminated reason/exit → retry policy/deadline → application idempotency.

12

12. Контроль, критерії засвоєння та cleanup

  • Коли Deployment кращий за StatefulSet?
  • Що стабільне після заміни web-0?
  • StatefulSet реплікує дані?
  • Від чого залежить число DaemonSet Pods?
  • Що рахують completions і parallelism?
  • Чому Job має бути ідемпотентним?
  • Що обмежує backoffLimit?
  • CronJob гарантує запуск рівно один раз?
  • Навіщо timeZone?
  • Чим Forbid відрізняється від application lock?
terminal
kubectl delete namespace k8s-m12 --wait=true
rm -rf /tmp/k8s-m12
kubectl get namespace k8s-m12 2>&1 || true

Модуль засвоєно, якщо ви обґрунтовуєте вибір кожного controller і діагностуєте failed Job від condition до exit code.