← назад к разделу

Между «собрали образ» и «новая версия приняла трафик» лежит конвейер, в котором у разработчика есть конкретные обязанности: честные теги образов, совместимые миграции и понимание, что именно произойдёт с трафиком в момент выкатки. Эта статья — про конвейер целиком: чем описывать, как катится, как откатывается.

Чем описывать: манифесты → 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.