← К программе курса

Модуль 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.

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: даже при одном completion код обязан быть идемпотентным либо дедуплицировать 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
  • Не используйте 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.