Между «собрали образ» и «новая версия приняла трафик» лежит конвейер, в котором у разработчика есть конкретные обязанности: честные теги образов, совместимые миграции и понимание, что именно произойдёт с трафиком в момент выкатки. Эта статья — про конвейер целиком: чем описывать, как катится, как откатывается.
Чем описывать: манифесты → kustomize → Helm
Сырые YAML-манифесты — правильная точка старта: один сервис, два файла (Deployment + Service), всё видно. Проблема приходит со средами: staging и prod отличаются репликами, ресурсами и адресами — а копипаст манифестов расползается.
Kustomize решает ровно эту проблему без шаблонизации: база + наложения (overlays) на среду. Встроен в kubectl:
deploy/
base/ # deployment.yaml, service.yaml
overlays/
staging/ # replicas: 1, меньше ресурсов
prod/ # replicas: 3, PDB, HPA
Helm — шаблонизатор с пакетированием: чарт + values.yaml на среду. Нужен, когда конфигурация действительно вариативна (один чарт на десяток однотипных сервисов) или когда ставите чужой софт (всё стороннее ставится чартами). Цена — шаблоны в Go-templates поверх YAML: отлаживать сгенерированное (helm template) приходится регулярно.
Практический выбор: свои сервисы — kustomize или общий командный Helm-чарт «один на все Spring-сервисы» (сервис заполняет десяток values); зоопарк уникальных чартов на каждый сервис — поддерживать дороже, чем кажется.
Образы: теги, по которым можно откатиться
Правило одно, но нарушается повсеместно: тег образа уникален и неизменяем — версия или SHA коммита (order-service:1.42.0, order-service:a1b2c3d). Тег :latest (и любой перезаписываемый) ломает сразу три вещи: откат (на что откатываться?), воспроизводимость (что именно сейчас в проде?) и сам rolling update (kubelet может решить, что образ «не изменился»). Дисциплина Dockerfile — non-root, многослойная сборка, pinned-базы — разобрана в Security Style Guide.
Rolling update: что происходит с трафиком
Дефолтная стратегия Deployment — катящееся обновление: новые поды поднимаются порциями, старые выводятся по мере готовности новых.
strategy:
rollingUpdate:
maxSurge: 1 # сколько подов сверх replicas можно создать
maxUnavailable: 0 # сколько может не хватать до replicas
maxUnavailable: 0 + maxSurge: 1 — консервативный вариант для прода: ёмкость никогда не проседает. Ключевая зависимость, которую обязан понимать разработчик: скорость и безопасность выкатки определяется readiness probe. Новый под получает трафик только после зелёной readiness; если probe честная — кривая версия просто не примет трафик, и выкатка застрянет (это хорошо: kubectl rollout status покажет провал, автоматика откатит). Если readiness формальная («отвечаю 200 всегда») — кривая версия примет трафик немедленно, и выкатка превратится в инцидент.
Откат — штатная операция, а не подвиг:
kubectl rollout undo deployment/order-service
kubectl rollout history deployment/order-service
Стратегия Recreate (всё погасить, потом поднять новое) легальна для фоновых обработчиков, которым нельзя работать в две версии одновременно — например, полер очереди с несовместимой сменой схемы claim-а.
Миграции БД: N−1 совместимость
Во время rolling update в проде одновременно работают две версии кода — минуты, а при застрявшей выкатке часы. Отсюда железное правило: каждая миграция совместима с предыдущей версией приложения. Переименовать колонку — это не RENAME COLUMN, а expand-contract на три релиза; уронить старую версию несовместимой схемой — самый частый способ превратить деплой в даунтайм. Полный каталог безопасных рецептов — PG Migrations Style Guide; запуск миграций — init-контейнером или на старте приложения до readiness (вот зачем startup probe с запасом).
CI/CD и GitOps
Минимальный честный конвейер: тесты → сборка образа с тегом-SHA → push в registry → обновление манифеста → применение. Применение бывает двух стилей:
- Push — pipeline сам делает
kubectl apply/helm upgrade. Просто, но кластер доступен из CI, а его фактическое состояние нигде не зафиксировано, кроме самого кластера. - GitOps (pull) — в git живёт репозиторий «желаемое состояние кластера»; оператор (Argo CD, Flux) непрерывно сверяет кластер с git и применяет разницу. Деплой = PR с новым тегом образа; откат = revert; ручные правки в кластере видны как drift и затираются.
GitOps — это идея желаемого состояния, доведённая до конца: git как единственный источник правды о проде. Для команды от пары сервисов и выше — рекомендуемый дефолт.
Локальная разработка
Честная иерархия по скорости обратной связи: обычный bootRun с docker compose для зависимостей (90% работы — здесь, k8s для неё не нужен) → kubectl port-forward к staging-зависимостям, когда нужен реальный сосед → локальный кластер (kind, minikube) — только когда отлаживаются сами манифесты, probes или NetworkPolicy. Антипаттерн — требовать локальный k8s для повседневной разработки: цикл «правка → образ → деплой» на порядок медленнее bootRun с devtools.
Антипаттерны
| Антипаттерн | Чем кончается | Что взамен |
|---|---|---|
Тег :latest в проде | Невозможно откатиться и понять, что задеплоено | Версия или SHA коммита |
kubectl edit/apply руками в прод | Состояние кластера ≠ git, изменения теряются | GitOps; ручное — только в инцидент с последующим PR |
| Readiness «всегда 200» | Кривая версия мгновенно получает трафик | Честная readiness с зависимостями по делу |
| Несовместимая миграция в rolling update | Старая версия падает, деплой = даунтайм | Expand-contract, N−1 совместимость |
| Уникальный Helm-чарт на каждый сервис | Себестоимость платформы растёт квадратично | Общий чарт + values или kustomize |
| Секреты в values.yaml в git | Утечка через историю репозитория | Secret-менеджер (External Secrets, Vault) |
Что почитать дальше
- Spring Boot в Kubernetes — readiness и graceful shutdown, от которых зависит безопасность выкатки.
- Эксплуатация и отладка — что делать, когда rollout застрял.
- PG Migrations Style Guide — expand-contract и безопасные DDL.
- Security Style Guide — Dockerfile, registry и supply chain.