Деплой в Kubernetes и Auto DevOps
Учимся деплоить в Kubernetes из пайплайна и узнаём, что предлагает Auto DevOps.
Auto DevOps — набор готовых шаблонов GitLab, автоматически собирающий, тестирующий, сканирующий и деплоящий приложение в Kubernetes с минимальной настройкой.
Kubernetes стал фактическим стандартом для запуска контейнеров в продакшене, и рано или поздно ваш пайплайн упрётся в вопрос: «как доставить свежесобранный образ в кластер?». На предыдущих уроках мы собирали образ и клали его в реестр — но реестр это лишь склад. Деплой в Kubernetes превращает артефакт со склада в работающий сервис, который принимает трафик пользователей. Этот шаг — мост между «код собрался» и «фича в проде», и именно здесь чаще всего ломается доставка: то нет доступа к кластеру, то джоба зеленеет раньше, чем поды поднялись, то старый образ продолжает крутиться, потому что тег не поменялся.
Важно понимать философию Kubernetes, без которой деплой кажется магией. Kubernetes работает декларативно: вы не командуете «запусти три копии», вы описываете желаемое состояние («я хочу три реплики вот этого образа»), а контроллеры кластера сами приводят реальность к этому описанию. Задача пайплайна — не «развернуть приложение» в императивном смысле, а донести до кластера новое желаемое состояние. Всё остальное (остановка старых подов, запуск новых, проверка готовности) кластер берёт на себя. Поэтому деплой из CI почти всегда сводится к одной из двух операций: либо «поменяй тег образа в существующем объекте», либо «примени вот этот набор манифестов».
Ручной деплой: kubectl и helm
Самый прозрачный способ — вызывать в джобе kubectl или helm, как вы делали бы вручную. Джобе нужен образ с этими утилитами и доступ к кластеру (kubeconfig в защищённой переменной).
deploy-k8s:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context my-cluster
- kubectl set image deployment/web web=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- kubectl rollout status deployment/web
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'Здесь kubectl set image обновляет образ деплоймента на свежесобранный (с тегом по SHA), а rollout status ждёт завершения раскатки — это и есть rolling update из прошлого урока. Для более сложных релизов берут helm upgrade с чартом.
Почему тег по SHA, а не latest
Обратите внимание на деталь, которая кажется мелочью, но критична: образ тегируется по $CI_COMMIT_SHORT_SHA, а не по latest. Если деплоить latest, вы получаете два больших зла. Во-первых, Kubernetes не понимает, что образ изменился — манифест-то остался прежним, тег тот же самый, и контроллер не видит причины перезапускать поды (приходится костылить imagePullPolicy: Always и принудительный рестарт). Во-вторых, вы теряете воспроизводимость: непонятно, какая именно версия кода крутится в проде прямо сейчас, и откат превращается в гадание. Уникальный иммутабельный тег решает обе проблемы: смена тега в манифесте — это видимое изменение желаемого состояния, и по тегу всегда восстановим точный коммит. Это золотое правило промышленного деплоя: образы иммутабельны, тег = коммит.
Подключение кластера
GitLab умеет подключать кластер через агент (GitLab Agent for Kubernetes) — безопасный канал, при котором кластер сам соединяется с GitLab, а не наоборот. Это предпочтительнее, чем хранить мощный kubeconfig в переменных. Агент даёт джобам контекст для kubectl без раздачи прямых учёток.
Разница между «агент» и «kubeconfig в переменной» — это разница между pull- и push-моделью доступа, и она важнее, чем кажется. При push-модели (kubeconfig в переменной) ваш раннер хранит ключи от кластера и сам стучится в его API-сервер. Значит, API кластера должен быть доступен из сети раннеров, а полноценный admin-kubeconfig лежит в переменной — украв его, атакующий получает весь кластер. При pull-модели агент — это под, живущий внутри кластера; он сам инициирует исходящее соединение к GitLab и слушает команды. Кластеру не нужен публичный API-эндпоинт, а в GitLab не хранится мощный токен. Схематично:
push-модель (kubeconfig):
GitLab Runner --kubeconfig--> [API-сервер кластера]
(раннер инициирует, нужен доступ к API, ключи у раннера)
pull-модель (agent):
[GitLab] <--исходящее соединение-- Agent (под в кластере)
(кластер инициирует, API закрыт наружу, токена в GitLab нет)Для учебного проекта kubeconfig в protected-переменной приемлем. Для боевого кластера с реальными пользователями агент — почти обязательный выбор, особенно если кластер не должен торчать в публичную сеть.
helm для управляемых релизов
deploy-helm:
stage: deploy
image: alpine/helm:latest
script:
- helm upgrade --install web ./chart
--set image.tag=$CI_COMMIT_SHORT_SHA
--namespace prod --wait
environment:
name: productionhelm upgrade --install применяет чарт идемпотентно: создаст релиз, если его нет, или обновит существующий. Флаг --wait заставляет дождаться готовности подов.
Когда kubectl, а когда helm
Грубое правило: kubectl apply хорош, пока манифестов мало и они почти не зависят от окружения. Как только появляется три окружения (dev/staging/prod) с разными именами хостов, числом реплик и лимитами ресурсов, голые манифесты начинают расползаться в копипасту — те же YAML с мелкими отличиями. Helm решает это шаблонами: один чарт с параметрами (values.yaml) на окружение, и вы передаёте отличия через --set или отдельный values-файл. Бонусом helm ведёт историю релизов: helm rollback web 1 мгновенно откатит на предыдущую ревизию — кластер сам вернёт прошлое желаемое состояние. Для пет-проекта хватит kubectl; для сервиса с несколькими окружениями и потребностью в быстром откате helm экономит много нервов.
Auto DevOps: всё из коробки
Auto DevOps — противоположный полюс: вы почти ничего не пишете, GitLab сам строит пайплайн build → test → SAST → review app → staging → production по соглашениям. Подходит для типовых приложений и быстрого старта, но для нестандартных случаев его обычно отключают в пользу явного .gitlab-ci.yml. Знать о нём полезно: многие шаблоны GitLab (Code Quality, SAST) — кирпичики того же Auto DevOps.
Здесь полезно сравнить подход GitLab с GitHub Actions, если вы пришли оттуда. В GitHub Actions нет встроенного аналога Auto DevOps: вы либо пишете workflow руками, либо собираете его из готовых actions из Marketplace (например, azure/k8s-deploy, helm-actions). Деплой в кластер там — это тоже kubectl/helm в шаге, плюс настройка доступа через секрет с kubeconfig или OIDC-федерацию с облаком. Ключевые отличия: у GitLab есть родной GitLab Agent (pull-модель из коробки) и встроенные Environments с историей деплоев и кнопкой отката; в GitHub окружения (Environments) есть тоже, но связка с кластером собирается из сторонних кирпичей. Auto DevOps — фишка именно GitLab: целостный «всё-в-одном» путь от коммита до прода без написания YAML, чего у Actions концептуально нет.
Как работает под капотом
Деплой в Kubernetes — это, по сути, обновление объектов кластера (Deployment, Service) через API. kubectl/helm в джобе аутентифицируются (через агент или kubeconfig) и шлют кластеру желаемое состояние; контроллеры Kubernetes приводят реальное состояние к желаемому, выполняя rolling update. Auto DevOps просто оборачивает эти шаги в готовые шаблонные джобы.
Заглянем чуть глубже в момент, когда джоба «зеленеет». Когда kubectl set image отрабатывает, API-сервер лишь записывает новое желаемое состояние в etcd и сразу отвечает «ок» — поды ещё даже не начали меняться. Дальше Deployment-контроллер создаёт новый ReplicaSet, постепенно поднимает свежие поды и гасит старые, сверяясь с readiness-пробами. Именно поэтому rollout status и --wait так важны: без них джоба считается успешной в момент «API принял запрос», а не «приложение реально живо». Если новый образ падает на старте, без ожидания пайплайн покажет зелёный, а пользователи увидят пятисотки. С rollout status джоба честно дождётся либо успеха, либо таймаута, и упадёт, если поды не поднялись — что и нужно, чтобы триггерить откат.
Связка с механизмом Environments довязывает картину. Указав environment: name: production, вы регистрируете деплой в GitLab: на странице окружения видно, какой коммит сейчас в проде, когда он туда попал и кто это сделал. Это не косметика — отсюда же доступна кнопка отката на предыдущий деплой и связь с review apps. По сути окружение — это журнал «что и когда мы выкатили», который ведётся автоматически по факту прохождения деплой-джоб.
Частые ошибки
- Хранить полный admin-kubeconfig в обычной (не protected) переменной — серьёзный риск.
- Не дожидаться
rollout status/--wait— джоба зеленеет до того, как поды реально поднялись. - Включить Auto DevOps поверх своего сложного пайплайна и получить конфликтующие джобы.
- Деплоить тег
latest— кластер не видит изменения, поды не перезапускаются, версия в проде неизвестна. - Давать деплой-джобе на прод доступ без
when: manualили защиты окружения — любой merge сразу едет в продакшен. - Забыть про namespace и нечаянно раскатить staging-релиз поверх продового деплоймента.
Итоги
- Деплой в Kubernetes — вызов
kubectl/helmиз джобы с доступом к кластеру. - GitLab Agent безопаснее, чем kubeconfig в переменных (pull-модель против push).
- Образ тегируем по коммиту, а не
latest— иначе кластер не увидит изменения. - Всегда дожидаемся раскатки через
rollout status/--wait, иначе джоба врёт о готовности. - Auto DevOps даёт готовый пайплайн «из коробки» по соглашениям; для нестандартного — явный файл.