Rollout и rollback:
Deployment по умолчанию обновляется стратегией RollingUpdate: старые поды заменяются новыми порциями. Управляют процессом два параметра, по умолчанию оба 25%.
Код: Выделить всё
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # сколько подов можно создать сверх replicas
maxUnavailable: 0 # сколько подов может быть недоступно
Код: Выделить всё
kubectl set image deploy/api api=registry.cyberlake.ru/api:1.8.3
kubectl rollout status deploy/api --timeout=120s
kubectl rollout history deploy/api
kubectl rollout undo deploy/api # на предыдущую ревизию
kubectl rollout undo deploy/api --to-revision=12
Graceful shutdown:
При удалении пода kubelet шлет процессу SIGTERM, ждет terminationGracePeriodSeconds (по умолчанию 30 секунд) и добивает SIGKILL. Параллельно под выводится из endpoints сервиса. Слово "параллельно" ключевое: трафик может прилетать еще несколько секунд после SIGTERM, пока kube-proxy и внешние балансировщики обновят правила. Отсюда классический паттерн: короткая пауза в preStop плюс приложение, которое по SIGTERM перестает принимать новые запросы, но дорабатывает текущие.
Код: Выделить всё
spec:
terminationGracePeriodSeconds: 60
containers:
- name: api
image: registry.cyberlake.ru/api:1.8.3
lifecycle:
preStop:
sleep: # нативный sleep, стабилен с Kubernetes 1.32,
seconds: 5 # бинарь sleep в образе не нужен
nodeSelector и affinity:
nodeSelector решает простую задачу: вешаете лейбл на ноду через kubectl label, в спеке пода указываете ту же пару ключ-значение, и под планируется только на подходящие ноды. Когда нужны операторы, мягкие предпочтения или привязка к другим подам, берут affinity.
Код: Выделить всё
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["ru-central1-a", "ru-central1-b"]
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
Taints и tolerations:
Лейблы притягивают поды, taint отталкивает: нода с taint не принимает поды без соответствующего toleration.
Код: Выделить всё
kubectl taint nodes gpu-1 dedicated=gpu:NoSchedule
# в спеке пода, которому туда можно:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
Типичные грабли:
rollout undo откатывает только шаблон пода. ConfigMap и Secret остаются новыми, и старый код с новым конфигом порой хуже, чем просто новая версия. Лечится версионированными именами (app-config-v42) или откатом релиза целиком через helm rollback.
maxUnavailable: 1 при replicas: 1 означает даунтайм на каждом деплое. Для маленьких приложений ставьте maxUnavailable: 0.
Если новый под не проходит readiness, rollout status будет ждать до spec.progressDeadlineSeconds (по умолчанию 600 секунд) и только потом упадет. В CI всегда задавайте --timeout.
Опечатка в nodeSelector или лейбл, которого нет ни на одной ноде, дает вечный Pending. kubectl describe pod покажет событие FailedScheduling с раскладкой, сколько нод и почему не подошло.
NoExecute на живой ноде мгновенно выселяет все поды без toleration. Кластер сам пользуется этим механизмом: на недоступную ноду вешается node.kubernetes.io/unreachable:NoExecute, а поды имеют дефолтный toleration на 300 секунд, поэтому переезд не мгновенный. Руками такой эффект на проде ставят осознанно, при выводе ноды, а не для эксперимента.
Итог:
RollingUpdate с maxUnavailable: 0, readiness проба и аккуратная обработка SIGTERM вместе дают деплой без потерянных запросов. rollout undo спасает быстро, но помнит только про шаблон пода, конфиги ваша забота. nodeSelector, affinity и taints решают одну задачу с трех сторон: просто притянуть, гибко притянуть, оттолкнуть лишнее. В следующей главе займемся автомасштабированием: HPA по метрикам и что делать, когда нод перестает хватать.