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

Когда образ контейнера собран и загружен в 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. Релиз 1: добавить новую колонку, старая остаётся.
  2. Релиз 2: оба кода пишут в обе колонки, читают из новой.
  3. Релиз 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 — поды, узлы, контроллеры и желаемое состояние.