Как это устроено:
Deployment сам подами не управляет. Он создает ReplicaSet, и уже тот держит заданное количество подов: если под упал, ReplicaSet тут же создает замену. С отказом ноды чуть сложнее: когда kubelet перестает отвечать, нода получает taint, и поды с нее выселяются через taint-based eviction только по истечении tolerationSeconds, по умолчанию это 300 секунд. То есть при смерти ноды замена появится примерно через 5 минут, не мгновенно, имейте это в виду. Зачем тогда два уровня? Из-за обновлений. Когда вы меняете шаблон пода (например, версию образа), Deployment создает новый ReplicaSet и постепенно переносит реплики со старого на новый. Старый ReplicaSet остается с нулем реплик, к нему можно откатиться. Руками ReplicaSet вы создавать не будете, считайте его внутренней деталью.
Минимальный рабочий манифест, файл backend.yaml:
Код: Выделить всё
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: nginx:1.27
ports:
- containerPort: 80
Применяем в кластер из второй главы и смотрим:
Код: Выделить всё
kubectl apply -f backend.yaml
kubectl get deploy,rs,pods
Масштабирование:
Быстрый способ: kubectl scale deployment backend --replicas=5. Но если манифесты лежат в гите (а они должны там лежать), правильнее поменять replicas в файле и снова сделать apply. Иначе следующий apply из CI молча вернет старое значение, и вы будете гадать, куда делись реплики.
Обновление без простоя:
Меняем в манифесте образ на nginx:1.28, применяем и следим:
Код: Выделить всё
kubectl apply -f backend.yaml
kubectl rollout status deployment/backend
RollingUpdate подходит не всем. Если две версии приложения не могут работать одновременно (несовместимая миграция схемы БД, эксклюзивный доступ к файлу или внешнему ресурсу), задайте spec.strategy.type: Recreate. Тогда Deployment сначала погасит все старые поды и только потом поднимет новые. Простой будет, зато не будет двух версий, конкурирующих за одни данные.
Если новая версия оказалась битой, откат делается одной командой:
Код: Выделить всё
kubectl rollout history deployment/backend
kubectl rollout undo deployment/backend
В выводе history колонка CHANGE-CAUSE по умолчанию пустая, и понять, что было в каждой ревизии, невозможно. Заполняется она аннотацией kubernetes.io/change-cause:
Код: Выделить всё
kubectl annotate deployment/backend kubernetes.io/change-cause="upgrade nginx 1.27 -> 1.28"
Еще одна штатная операция: перекатить поды без смены образа, например после обновления ConfigMap или Secret, которые приложение читает при старте. Для этого есть kubectl rollout restart deployment/backend: команда меняет аннотацию в шаблоне пода, и Deployment делает обычный rolling update с тем же образом.
История по умолчанию хранит 10 ревизий (revisionHistoryLimit), это те самые старые ReplicaSet с нулем реплик.
Типичные грабли:
Тег latest. Вы пересобрали образ, сделали apply, а Kubernetes ничего не обновил: шаблон пода не изменился, значит и катить нечего. Вторая половина проблемы это imagePullPolicy: для тега latest он по умолчанию Always, для любого фиксированного тега IfNotPresent. Поэтому при очередном пересоздании пода (вылет, переезд на другую ноду) kubelet скачает свежий latest, и версия обновится сама собой, в случайный момент и не на всех репликах сразу. В итоге вы никогда не знаете, какая версия сейчас крутится. Фиксируйте тег: номер версии, git sha, дата сборки, что угодно, кроме latest.
Правка пода напрямую. kubectl edit pod что-то поменяет, но при первом же пересоздании ReplicaSet вернет все по шаблону. Любые изменения только через Deployment.
Зависший rollout. Новые поды не проходят readiness-пробу (про пробы будет глава 10), и rollout status не двигается. Вечно он висеть не будет: по умолчанию progressDeadlineSeconds равен 600, через 10 минут Deployment получит condition ProgressDeadlineExceeded, и rollout status завершится с ошибкой. Все это время старые поды продолжают обслуживать трафик, это защитное поведение, а не баг. Смотрите kubectl describe deployment и логи новых подов, к отладке мы плотно вернемся в главе 11. Чтобы CI не ждал лишние 10 минут, уменьшите progressDeadlineSeconds до разумного для вашего приложения значения.
Итог:
Deployment это основной способ запускать stateless-приложения: он держит реплики через ReplicaSet, катит обновления без простоя (или через Recreate, когда две версии не должны жить одновременно) и умеет откатываться. Осталась проблема: у каждого пода свой IP, и при каждом пересоздании он меняется. Как обращаться к трем репликам по одному стабильному адресу, разберем в следующей главе про Service.