Когда образ контейнера собран и загружен в registry, самое интересное только начинается. Kubernetes нужно объяснить: сколько копий запустить, когда считать под готовым, как обновлять без простоя и куда смотреть, если что-то пошло не так. Всё это описывается в файлах — и файлы живут в git.
Манифест: один файл — одно намерение
Раньше сервисы раскладывали по серверам вручную или через скрипты. Kubernetes устроен иначе: вы описываете, что хотите, а кластер сам разбирается, как это обеспечить.
Описание пишется в YAML-файлах, которые называют манифестами. Типичная связка для одного сервиса — два файла:
# deployment.yaml — сколько запустить и какой образ
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: app
image: order-service:1.42.0
ports:
- containerPort: 8080
# service.yaml — как достучаться до подов изнутри кластера
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
Применить манифест — одна команда:
kubectl apply -f deployment.yaml
Kubernetes прочитает файл и приведёт кластер в описанное состояние. Если под упал — кластер поднимет новый. Если вы поменяли количество реплик и применили файл заново — кластер перестроится сам. Это и называют декларативным подходом.
Kustomize и Helm: когда одного файла мало
Один файл работает хорошо, пока у вас одна среда. Как только появляются staging и prod — они отличаются числом реплик, лимитами ресурсов, адресами баз. Копировать манифест и менять несколько строк — это путь к расхождениям.
Есть два инструмента, которые решают эту задачу по-разному.
Kustomize работает без шаблонов: есть базовый вариант и «наложения» (overlays) для каждой среды. Накладывать можно что угодно: только replicas, только ресурсы, только адрес образа. Kustomize встроен прямо в kubectl:
deploy/
base/ # deployment.yaml, service.yaml — общая основа
overlays/
staging/ # replicas: 1, меньше памяти
prod/ # replicas: 3, HPA, PodDisruptionBudget
kubectl apply -k overlays/prod/
Helm — это уже шаблонизатор с пакетированием. Из одного чарта можно развернуть сервис десятки раз с разными параметрами. Именно так работает весь сторонний софт (базы, мониторинг, ингресс-контроллеры) — скачиваете чарт, передаёте values.yaml под свою среду.
helm upgrade --install order-service ./chart -f values-prod.yaml
Недостаток Helm — шаблоны пишутся на Go-templates поверх YAML. Это читается хуже, и для отладки часто нужно смотреть, что Helm сгенерировал:
helm template order-service ./chart -f values-prod.yaml
Практический выбор: kustomize удобен для своих сервисов, где изменений немного; Helm нужен для стороннего ПО и когда конфигурация действительно сильно варьируется между средами.
Теги образов: как не потерять возможность откатиться
Маленькая деталь с большими последствиями. Тег :latest на образе выглядит удобно — загрузил новый образ под тем же тегом, и всё. Но это ломает три важные вещи:
- откат: на что возвращаться, если тег тот же?
- воспроизводимость: что именно сейчас в проде?
- rolling update: kubelet может решить, что образ «не изменился», и не перезапустит под.
Правило одно: тег уникален и неизменяем. Хорошие варианты — версия (order-service:1.42.0) или SHA коммита (order-service:a1b2c3d). Тогда в любой момент видно, что задеплоено, и можно вернуться к любому предыдущему состоянию.
Rolling update: как версия попадает в прод без простоя
По умолчанию Kubernetes обновляет поды постепенно, не выключая сервис целиком. Это называют rolling update: новые поды поднимаются, старые гасятся порциями.
Поведение настраивается:
strategy:
rollingUpdate:
maxSurge: 1 # сколько дополнительных подов можно создать сверх replicas
maxUnavailable: 0 # сколько подов может не работать в процессе
maxUnavailable: 0 означает: ёмкость никогда не проседает — новый под стартует раньше, чем остановится старый. Это консервативный вариант для прода.
Здесь появляется главная зависимость: новый под получает трафик только после того, как пройдёт readiness probe. Это специальная проверка, которую Kubernetes делает перед тем, как пустить запросы на под. Пока под не ответил «готов» — он трафик не получает.
Если probe честная (проверяет соединение с базой, очередью, нужными зависимостями) — кривая версия просто не пройдёт проверку, и выкатка остановится. kubectl rollout status покажет проблему.
Если probe формальная («отвечаю 200 всегда») — кривая версия сразу примет трафик, и у вас инцидент.
Откат при проблеме — штатная операция:
kubectl rollout undo deployment/order-service
kubectl rollout history deployment/order-service # история ревизий
Кроме rolling update есть стратегия Recreate — сначала выключить все старые поды, потом поднять новые. Это вызывает кратковременный простой, но нужна для фоновых обработчиков, которым нельзя работать в двух версиях одновременно.
Миграции БД: когда в проде одновременно работают две версии
Во время rolling update в кластере несколько минут сосуществуют старая и новая версии приложения. Это означает: обе версии работают с одной базой данных одновременно.
Отсюда правило: каждая миграция должна быть совместима со старой версией приложения. Переименовать колонку через RENAME COLUMN — нельзя: старая версия её не найдёт. Вместо этого используют подход expand-contract на несколько релизов:
- Релиз 1: добавить новую колонку, старая остаётся.
- Релиз 2: оба кода пишут в обе колонки, читают из новой.
- Релиз 3: старая колонка удаляется.
Запускают миграции обычно через init-контейнер — специальный контейнер в поде, который стартует раньше основного приложения и завершается до того, как приложение откроет порт. Kubernetes дожидается успешного завершения init-контейнера перед запуском основного.
GitOps: git как источник правды о состоянии кластера
Обычный CI/CD-конвейер работает так: тесты → сборка образа → push в registry → kubectl apply из pipeline. Это называют push-подходом: CI сам толкает изменения в кластер. Минус — кластер доступен из CI, и фактическое состояние нигде не зафиксировано.
GitOps переворачивает это: в git хранится «желаемое состояние» кластера, а специальный оператор в кластере (Argo CD, Flux) постоянно сверяет реальное состояние с git и применяет разницу сам.
Деплой новой версии в GitOps — это pull request с новым тегом образа. Откат — это git revert. Если кто-то вручную поменял что-то в кластере — оператор это заметит как «отклонение от желаемого» (drift) и вернёт обратно.
developer → git push (новый тег) → Argo CD видит разницу → применяет к кластеру
GitOps хорошо работает, когда сервисов больше двух-трёх: история изменений в git, аудит, откаты через стандартные git-инструменты.
Коротко
- Манифест — YAML-файл с декларацией: «хочу 3 копии этого образа». Kubernetes сам обеспечивает это состояние.
- Kustomize — overlays без шаблонов, для своих сервисов с несколькими средами. Helm — шаблонизатор с пакетированием, нужен для стороннего ПО.
- Тег образа должен быть уникальным и неизменяемым — версия или SHA коммита. Тег
:latestломает откаты и воспроизводимость. - Rolling update обновляет поды порциями без остановки сервиса. Трафик попадает на новый под только после зелёной readiness probe.
kubectl rollout undoоткатывает к предыдущей ревизии — штатная операция, не подвиг.- Во время rolling update в кластере одновременно работают две версии, поэтому миграции БД должны быть совместимы со старым кодом.
- GitOps: git хранит желаемое состояние кластера, оператор (Argo CD, Flux) применяет его автоматически. Деплой = PR с новым тегом.
Что почитать дальше
- Spring Boot в Kubernetes — readiness probe через Actuator, graceful shutdown, ресурсы JVM.
- Эксплуатация и отладка — что делать, когда rollout застрял.
- Основы Kubernetes — поды, узлы, контроллеры и желаемое состояние.