Kubernetes: Pod, Deployment, StatefulSet, DaemonSet, Job, CronJob
Манифесты рабочих нагрузок — контейнеры, init- и sidecar-контейнеры, реплики, стратегии выкатки, задачи
Рабочая нагрузка (workload) — объект, который запускает контейнеры. Сам контейнер всегда живёт внутри пода (Pod): это минимальная единица, у подов в одном экземпляре общий IP и тома. Поды руками почти не создают — их порождают контроллеры:
| Объект | Когда брать |
|---|---|
| Deployment | сервис без состояния (веб, API): N одинаковых взаимозаменяемых подов, плавная выкатка новой версии |
| StatefulSet | сервис с состоянием (БД, очереди): у каждого пода постоянное имя имя-0, имя-1 и свой диск |
| DaemonSet | ровно один под на каждом узле: агенты логов и мониторинга, сетевые плагины |
| Job | одноразовая задача, которая должна завершиться успешно: миграция, импорт |
| CronJob | Job по расписанию: бэкап, очистка |
Описание пода у всех них одинаковое и лежит в spec.template (у CronJob — в spec.jobTemplate.spec.template).
Синтаксис
apiVersion: apps/v1 # Pod — v1, Deployment/StatefulSet/DaemonSet — apps/v1, Job/CronJob — batch/v1
kind: Deployment
metadata:
name: web
labels: {app: web}
spec:
replicas: 2
selector:
matchLabels: {app: web} # какими подами управляет контроллер
template: # шаблон пода
metadata:
labels: {app: web} # должны подходить под selector
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
Заготовку проще не набирать, а сгенерировать: kubectl create deployment|job|cronjob … --dry-run=client -o yaml (см. kubectl). Для Pod, StatefulSet и DaemonSet офлайн-генератора нет: kubectl run требует кластер, а StatefulSet и DaemonSet удобно делать из сгенерированного Deployment.
Под: контейнеры
Поля элемента spec.containers[] (и initContainers[]):
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
name | имя контейнера, уникальное в поде | обязательно |
image | образ: nginx:1.27, registry.example.com/app:2.1 | без тега берётся :latest |
imagePullPolicy | Always / IfNotPresent / Never | Always для :latest и образа без тега, иначе IfNotPresent |
command | список: заменяет ENTRYPOINT образа | не задан — берётся из образа |
args | список: заменяет CMD образа | $(VAR) подставляет переменную окружения контейнера |
workingDir | рабочий каталог | из образа |
ports[].containerPort | порт, который слушает приложение | справочное поле: порт доступен и без него; name даёт порту имя для Service и проб |
env, envFrom | переменные окружения | см. k8s-config |
volumeMounts | куда смонтировать тома пода | см. k8s-config |
resources | requests / limits по CPU и памяти | см. k8s-probes-resources |
livenessProbe, readinessProbe, startupProbe | проверки здоровья | см. k8s-probes-resources |
securityContext | runAsUser, runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation, capabilities |
Под: уровень пода
Поля spec пода (в контроллерах — spec.template.spec):
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
containers | основные контейнеры, запускаются одновременно | обязательно, хотя бы один |
initContainers | запускаются по очереди до основных; каждый должен завершиться с кодом 0 | упал — под перезапускает их заново (по restartPolicy) |
initContainers[].restartPolicy: Always | sidecar: стартует в порядке init-контейнеров, но не ждёт завершения и работает рядом с основными | стабильно с Kubernetes 1.33, включено по умолчанию с 1.29 |
restartPolicy | Always / OnFailure / Never — перезапускать ли упавшие контейнеры | Always; у Deployment/StatefulSet/DaemonSet только Always, у Job — только OnFailure или Never |
serviceAccountName | от чьего имени под ходит в API | default; см. k8s-rbac |
volumes | тома, общие для контейнеров пода | см. k8s-config |
nodeSelector | запускать только на узлах с такими метками: {disktype: ssd} | |
tolerations | разрешить узлы с «запрещающими» taint | |
affinity | nodeAffinity, podAntiAffinity — гибкие правила размещения | |
imagePullSecrets | [{name: секрет}] — доступ к приватному реестру | секрет типа kubernetes.io/dockerconfigjson |
terminationGracePeriodSeconds | сколько ждать после SIGTERM до SIGKILL | 30 |
hostNetwork | использовать сеть узла | false |
securityContext | на весь под: runAsUser, fsGroup, … |
Deployment
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.replicas | сколько подов держать | 1 |
spec.selector.matchLabels | метки, по которым Deployment находит свои поды | обязательно; неизменяемо после создания |
spec.selector.matchExpressions | [{key, operator: In|NotIn|Exists|DoesNotExist, values}] | |
spec.template.metadata.labels | метки подов | обязаны подходить под selector |
spec.strategy.type | RollingUpdate — по одному заменять / Recreate — сначала убить все старые | RollingUpdate |
spec.strategy.rollingUpdate.maxSurge | сколько подов можно запустить сверх replicas во время выкатки (число или %) | 25% (округление вверх) |
spec.strategy.rollingUpdate.maxUnavailable | сколько подов может быть недоступно во время выкатки | 25% (округление вниз); вместе с maxSurge не может быть 0 |
spec.minReadySeconds | сколько под должен быть Ready, чтобы считаться доступным | 0 |
spec.revisionHistoryLimit | сколько старых ReplicaSet хранить для отката | 10 |
spec.progressDeadlineSeconds | через сколько секунд без прогресса выкатка считается зависшей | 600 |
spec.paused | приостановить выкатку | false |
Deployment управляет подами через ReplicaSet: каждая версия шаблона — свой ReplicaSet, откат (kubectl rollout undo) возвращает предыдущий. Новая выкатка начинается при любом изменении spec.template — образа, переменных, меток пода; изменение replicas выкатку не запускает.
StatefulSet
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.serviceName | имя headless-сервиса (clusterIP: None), который даёт подам DNS-имена | в схеме 1.32 обязательно; сам Service создаёшь отдельно |
spec.replicas, spec.selector, spec.template | как у Deployment | |
spec.volumeClaimTemplates | шаблоны PVC: каждому поду — свой диск | PVC называется <шаблон>-<statefulset>-<номер>, например data-db-0 |
spec.podManagementPolicy | OrderedReady — поды по одному, по порядку / Parallel — все сразу | OrderedReady |
spec.updateStrategy.type | RollingUpdate (с последнего номера к нулевому) / OnDelete (обновится только удалённый вручную под) | RollingUpdate |
spec.updateStrategy.rollingUpdate.partition | обновлять только поды с номером ≥ N (канареечная выкатка) | 0 |
spec.persistentVolumeClaimRetentionPolicy | whenDeleted / whenScaled: Retain или Delete | оба Retain: диски не удаляются |
Поды получают имена db-0, db-1, … и DNS-имена db-0.<serviceName>.<namespace>.svc.cluster.local. Под с тем же номером после пересоздания получает то же имя и тот же PVC.
DaemonSet
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.selector, spec.template | как у Deployment | поля replicas нет |
spec.template.spec.nodeSelector / affinity | на каких узлах запускать | без них — на всех подходящих |
spec.template.spec.tolerations | нужны, чтобы попасть на узлы с taint (например, control-plane) | |
spec.updateStrategy.type | RollingUpdate / OnDelete | RollingUpdate, maxUnavailable: 1 |
Job
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.completions | сколько успешных завершений нужно | 1 |
spec.parallelism | сколько подов работает одновременно | 1 |
spec.backoffLimit | сколько неудачных попыток до признания Job проваленным | 6; паузы между попытками растут: 10 с, 20 с, 40 с… до 6 мин |
spec.activeDeadlineSeconds | общий лимит времени; по истечении поды убиваются, Job — Failed | без лимита |
spec.ttlSecondsAfterFinished | удалить Job и его поды через N секунд после завершения | не удаляется |
spec.completionMode | NonIndexed / Indexed — каждый под получает номер в JOB_COMPLETION_INDEX | NonIndexed |
spec.suspend | не создавать поды, пока true | false |
spec.template.spec.restartPolicy | Never — при сбое новый под / OnFailure — перезапуск контейнера в том же поде | обязательно указать одно из двух |
selector у Job не пиши: Kubernetes генерирует его сам.
CronJob
| Поле | Что значит | По умолчанию / заметки |
|---|---|---|
spec.schedule | расписание в формате cron: '*/15 * * * *', '0 3 * * 1', @hourly, @daily | обязательно; см. cron |
spec.timeZone | часовой пояс: Europe/Moscow | пояс kube-controller-manager, обычно UTC |
spec.jobTemplate.spec | описание Job (все поля из таблицы выше) | обязательно |
spec.concurrencyPolicy | Allow — запускать параллельно / Forbid — пропустить запуск, пока идёт прошлый / Replace — убить прошлый и запустить новый | Allow |
spec.startingDeadlineSeconds | насколько можно опоздать с запуском; позже — запуск пропускается | без лимита |
spec.successfulJobsHistoryLimit | сколько успешных Job хранить | 3 |
spec.failedJobsHistoryLimit | сколько проваленных Job хранить | 1 |
spec.suspend | приостановить расписание | false |
Примеры
# заготовки манифестов без кластера
kubectl create deployment web --image=nginx:1.27 --replicas=3 --port=80 \
--dry-run=client -o yaml > deploy.yaml
kubectl create job migrate --image=busybox:1.36 \
--dry-run=client -o yaml -- sh -c 'echo migrating' > job.yaml
kubectl create cronjob cleanup --image=busybox:1.36 --schedule='0 3 * * *' \
--restart=OnFailure --dry-run=client -o yaml -- sh -c 'echo cleanup' > cron.yaml
# доработать через yq: стратегия выкатки и параметры Job
yq -i '.spec.strategy = {"type": "RollingUpdate", "rollingUpdate": {"maxSurge": 1, "maxUnavailable": 0}}' deploy.yaml
yq -i '.spec.backoffLimit = 2 | .spec.ttlSecondsAfterFinished = 600' job.yaml
yq -i '.spec.concurrencyPolicy = "Forbid" | .spec.timeZone = "Europe/Moscow"' cron.yaml
# DaemonSet из Deployment: сменить kind, убрать replicas, strategy и status
# (status у DaemonSet другой — с пустым status: {} схема не пройдёт)
kubectl create deployment node-agent --image=busybox:1.36 --dry-run=client -o yaml -- sleep infinity \
| yq '.kind = "DaemonSet" | del(.spec.replicas) | del(.spec.strategy) | del(.status)' > ds.yaml
# проверить всё по схемам
kubeconform -strict -summary deploy.yaml job.yaml cron.yaml ds.yaml
# одно поле — посмотреть, что получилось
yq '.spec.jobTemplate.spec.template.spec.restartPolicy' cron.yaml
# в реальном кластере (здесь не сработают):
# kubectl apply -f deploy.yaml
# kubectl rollout status deploy/web
# kubectl get pods -l app=web
# kubectl create job manual-run --from=cronjob/cleanup
Под с init-контейнером и sidecar:
apiVersion: v1
kind: Pod
metadata:
name: app
labels: {app: app}
spec:
initContainers:
- name: wait-db # обычный init: ждёт и завершается
image: busybox:1.36
command: ["sh", "-c", "until nc -z db 5432; do sleep 2; done"]
- name: log-shipper # sidecar: работает всё время жизни пода
image: busybox:1.36
restartPolicy: Always
command: ["sh", "-c", "tail -F /logs/app.log"]
volumeMounts:
- {name: logs, mountPath: /logs}
containers:
- name: app
image: registry.example.com/app:1.4.2
command: ["/app/server"] # вместо ENTRYPOINT образа
args: ["--port", "8080"] # вместо CMD образа
ports:
- {name: http, containerPort: 8080}
volumeMounts:
- {name: logs, mountPath: /var/log/app}
volumes:
- name: logs
emptyDir: {}
StatefulSet с headless-сервисом и своим диском у каждого пода:
apiVersion: v1
kind: Service
metadata:
name: db
spec:
clusterIP: None # headless: DNS-имена db-0.db, db-1.db
selector: {app: db}
ports:
- {name: pg, port: 5432}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db
replicas: 2
selector:
matchLabels: {app: db}
template:
metadata:
labels: {app: db}
spec:
containers:
- name: postgres
image: postgres:16
ports:
- {name: pg, containerPort: 5432}
volumeMounts:
- {name: data, mountPath: /var/lib/postgresql/data}
volumeClaimTemplates: # PVC data-db-0, data-db-1
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
Job, который обрабатывает 10 частей по 3 одновременно:
apiVersion: batch/v1
kind: Job
metadata:
name: batch
spec:
completions: 10
parallelism: 3
completionMode: Indexed # номер части — в $JOB_COMPLETION_INDEX
backoffLimit: 4
activeDeadlineSeconds: 3600
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "echo part $JOB_COMPLETION_INDEX"]
Подводные камни
- Селектор и метки пода не совпадают. Для Deployment API-сервер такой манифест отвергнет (
selector does not match template labels), аkubeconformпропустит — он проверяет только структуру. Совпадение меток проверяй глазами. selectorнельзя поменять у существующего Deployment, StatefulSet и DaemonSet: только удалить и создать заново. Меняешь метки — добавляй новые в шаблон пода, аselectorне трогай.restartPolicy: Alwaysв Job API-сервер не примет (нужноNeverилиOnFailure), но схема это не ловит:kubeconformскажетvalid.commandзаменяет ENTRYPOINT целиком. Задалcommand: ["--debug"]— Kubernetes попробует запустить файл--debug. Аргументы к стандартной команде образа пиши вargs.command: ["sh", "-c", "a && b"]— вся строка оболочки идёт одним элементом.command: ["sh", "-c", "echo", "hi"]выведет пустую строку:hiстанет$0, а не аргументомecho.- Отступы в
containers.- name:иimage:должны быть на одном уровне. Сдвинутыйimageпревращается в поле другого объекта или в ошибку разбора. :latestиimagePullPolicy. С:latestобраз скачивается при каждом старте пода, а без тега тоже берётся:latest. Для воспроизводимости указывай точный тег.maxSurge: 0иmaxUnavailable: 0одновременно запрещены: выкатке некуда двигаться.replicasв манифесте и HPA. Если репликами управляет HorizontalPodAutoscaler, каждыйkubectl applyсreplicas: 2сбросит их обратно. Убериreplicasиз манифеста (см. k8s-probes-resources).- StatefulSet не создаёт Service из
serviceName— его нужно описать отдельно, сclusterIP: None. - Диски StatefulSet остаются после удаления и уменьшения
replicas: PVC изvolumeClaimTemplatesпо умолчанию не удаляются. Это защита данных, но и источник «потерянных» томов. - CronJob и часовой пояс. Без
timeZoneрасписание считается в поясе контроллера, обычно UTC:0 3 * * *— это 06:00 по Москве. concurrencyPolicy: Allowпо умолчанию. Если задача идёт дольше интервала, запуски накладываются друг на друга. Для бэкапов и миграций ставьForbid.- Init-контейнер, который не завершается, держит под в статусе
Init:0/1навсегда. Долгоживущий вспомогательный контейнер делай sidecar (restartPolicy: Always) или обычным контейнером.
См. также
kubectl · kubeconform · yq · k8s-networking · k8s-config · k8s-probes-resources · k8s-rbac · cron · docker-compose