Модуль 12 / 19
StatefulSet, DaemonSet, Job і CronJob
Зміст
1. Цілі та результат модуля
Ви навчитеся обирати controller за потрібною гарантією: стабільна identity, один Pod на Node, скінченне завдання або розклад.
- StatefulSet дає ordinal identity та впорядковані операції
- DaemonSet узгоджує Pod із придатними Nodes
- Job відстежує успішні completions
- CronJob створює Jobs за розкладом
- жоден controller сам не робить застосунок HA
2. Теорія: controller обирають за семантикою
Deployment підходить взаємозамінним replicas. StatefulSet потрібен, коли важливі стабільні імена чи порядок. DaemonSet моделює node-local функцію. Job має кінцевий результат, CronJob — розклад створення Jobs. Вибір за звичкою створює неправильні lifecycle guarantees.
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.
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.
5. Job: completion semantics
Job керує Pods до потрібної кількості успішних completions. parallelism обмежує одночасну роботу; backoffLimit — повтори після failures; activeDeadlineSeconds — загальний час. restartPolicy для Job — Never або OnFailure.
Завдання має враховувати at-least-once execution: код повинен бути ідемпотентним або дедуплікувати side effects.
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.
7. Методичні вказівки
- Формулюйте lifecycle guarantee до kind
- Не називайте StatefulSet базою даних високої доступності
- Перевіряйте ownerReferences і status controller
- Для DaemonSet рахуйте eligible Nodes
- Робіть Jobs ідемпотентними
- Задавайте timeZone CronJob
- Не використовуйте history як архів логів
- Читайте Job conditions, failed Pods та Events разом
8. Лабораторна робота: чотири controller
Створіть headless Service, StatefulSet, DaemonSet, Job і CronJob. Повторний apply після першого проходу потрібен для namespaced документів після створення Namespace.
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.yaml9. Лабораторна робота: stable identity
Перевірте DNS web-0/web-1. Після видалення web-0 ім’я зберігається, UID змінюється: identity controller стабільна, object instance — нова.
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. Лабораторна робота: DaemonSet, Job і CronJob
Зіставте один agent Pod з одним Node. Потім дослідіть 3 completions за parallelism 2 і створіть негайний Job із CronJob template без очікування розкладу.
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 --prefixkubectl -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 yaml11. Діагностика: вичерпаний backoff
Завдання завжди повертає 42. Job створює failed Pods до вичерпання backoffLimit та отримує Failed condition. Логів одного Pod недостатньо: перевіряйте всі attempts, Job status і Events.
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. Контроль, критерії засвоєння та cleanup
- Коли Deployment кращий за StatefulSet?
- Що стабільне після заміни web-0?
- StatefulSet реплікує дані?
- Від чого залежить число DaemonSet Pods?
- Що рахують completions і parallelism?
- Чому Job має бути ідемпотентним?
- Що обмежує backoffLimit?
- CronJob гарантує запуск рівно один раз?
- Навіщо timeZone?
- Чим Forbid відрізняється від application lock?
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.