Механика: как Jenkins получает доступ к кластеру
Чтобы стейдж деплоя что-то выкатил, агенту нужны две вещи: бинарь (kubectl или helm) и kubeconfig с правами в нужном namespace. Бинари ставим в образ агента заранее - не тяни их curl-ом на каждом запуске. Доступ к кластеру оформляем как credentials Jenkins, а не как файл на диске агента.
Есть два рабочих подхода. Первый - плагин Kubernetes CLI: ты кладёшь kubeconfig (или токен сервис-аккаунта) в Secret file credentials, а шаг withKubeConfig генерирует временный конфиг в воркспейсе и сносит его после выхода из блока. Второй, если сам агент крутится подом внутри того же кластера (см. урок про эфемерные pod-агенты, глава 59), - вообще не передавать креды: kubectl подхватит ServiceAccount пода. Это чище всего, но работает только для деплоя в тот же кластер, где живёт Jenkins.
Код: Выделить всё
pipeline {
agent { label 'k8s' }
environment {
IMAGE = "registry.cyberlake.ru/shop/api"
TAG = "${env.GIT_COMMIT.take(12)}"
NAMESPACE = "dev"
}
stages {
stage('Deploy dev') {
steps {
withKubeConfig([credentialsId: 'kubeconfig-dev', namespace: "${NAMESPACE}"]) {
sh '''
kubectl -n "$NAMESPACE" set image deployment/api \
api="$IMAGE:$TAG" --record
kubectl -n "$NAMESPACE" rollout status deployment/api --timeout=180s
'''
}
}
}
}
}

Helm вместо голого kubectl
Голый kubectl хорош для одного-двух манифестов. Как только появляются разные значения по окружениям, конфигмапы, инжресы и зависимости - удобнее Helm. Подход jenkins helm в declarative-пайплайне выглядит так:
Код: Выделить всё
stage('Deploy stage') {
steps {
withKubeConfig([credentialsId: 'kubeconfig-stage']) {
sh '''
helm upgrade --install api ./charts/api \
--namespace stage --create-namespace \
--set image.repository="$IMAGE" \
--set image.tag="$TAG" \
--values ./charts/api/values-stage.yaml \
--wait --timeout 3m --atomic
'''
}
}
}
Окружения, аппрув и откат на проде
dev выкатываем автоматом на каждый коммит. На прод так нельзя - нужен живой человек, который нажмёт кнопку, и условие, что катим только из main. Это директива when и шаг input:
Код: Выделить всё
stage('Deploy prod') {
when { branch 'main' }
steps {
timeout(time: 30, unit: 'MINUTES') {
input message: "Выкатить ${TAG} в прод?", ok: 'Деплой',
submitter: 'release-managers'
}
withKubeConfig([credentialsId: 'kubeconfig-prod']) {
sh '''
helm upgrade --install api ./charts/api \
--namespace prod \
--set image.tag="$TAG" \
--values ./charts/api/values-prod.yaml \
--wait --timeout 5m --atomic
'''
}
}
post {
failure {
withKubeConfig([credentialsId: 'kubeconfig-prod']) {
sh 'helm -n prod rollback api --wait --timeout 3m || true'
}
}
}
}
Про секреты кластера. kubeconfig и токены - это Secret file или Secret text в Jenkins Credentials, привязанные к нужным folder/job, чтобы dev-джоба физически не дотянулась до prod-кредов. Прод-kubeconfig - только для прод-джобы, в идеале на отдельном агенте. Сами секреты приложения (пароли БД, ключи) в манифесты не зашивай: храни в Kubernetes Secret, External Secrets Operator или Vault, а Helm пусть ссылается на уже существующий Secret. Логи Jenkins - публичная зона, любой echo токена туда и утечёт.
Где Jenkins отдаёт деплой GitOps (Argo CD)
Push-модель, которую мы разобрали, - Jenkins сам идёт в кластер и применяет манифесты - проста и наглядна, но у неё есть слабые места. Прод-креды живут в CI. Нет автоматического отслеживания дрейфа: кто-то поправил руками через kubectl edit - и состояние кластера разошлось с гитом, а ты не узнаешь. На десятках сервисов это становится больно.
Тут на сцену выходит GitOps и Argo CD. Идея простая: единственный источник правды - git-репозиторий с манифестами. Argo CD крутит в кластере reconciliation loop, постоянно сравнивая желаемое состояние из гита с реальным и подтягивая расхождения. В этой модели роль Jenkins сужается до честного CI: собрать, протестировать, запушить образ и обновить тег в git-репозитории конфигов. Дальше деплоит уже Argo CD.
Код: Выделить всё
stage('Bump image tag (GitOps)') {
when { branch 'main' }
steps {
withCredentials([gitUsernamePassword(credentialsId: 'gitops-repo')]) {
sh '''
git clone https://git.cyberlake.ru/infra/gitops.git
cd gitops
yq -i ".image.tag = \\"$TAG\\"" apps/api/values-prod.yaml
git commit -am "api: prod -> $TAG"
git push
'''
}
}
}
Типичные грабли
- Деплой по latest. Пайплайн зелёный, в кластере непонятно что. Всегда фиксируй тег из стейджа сборки - коммит-хэш или семвер.
- apply без rollout status или helm без --wait. Джоба завершается раньше, чем поды поднялись, провал маскируется зелёной галочкой.
- input без timeout. Забытый аппрув держит executor и копит очередь.
- Один kubeconfig на все окружения. dev-джоба получает доступ к проду. Разделяй креды по namespace и по folder.
- Токен в логах. Любой sh с echo секрета пишет его в публичный лог. Только через withCredentials / withKubeConfig.
- kubectl/helm версии сильно расходятся с версией кластера. Держи бинари в образе агента и обновляй их вместе с апгрейдом кластера.
- Заведи Secret file credentials с kubeconfig для namespace dev. Собери пайплайн со стейджем deploy через withKubeConfig + kubectl set image + rollout status. Убедись, что джоба краснеет, если выставить заведомо несуществующий тег.
- Перепиши deploy на helm upgrade --install с флагами --wait --atomic. Сломай деплой намеренно (битый image.tag) и проверь, что Helm сам откатил релиз.
- Добавь стейдж deploy prod с when { branch 'main' }, input с submitter и блоком post { failure } с rollback. Прогони из ветки и из main - убедись, что стейдж пропускается не из main.
- Зачем после kubectl apply ставить rollout status, а в helm - флаг --wait? Что без них ломается?
- Чем отличается push-деплой из Jenkins от pull-модели Argo CD и в какой точке Jenkins отдаёт раскат GitOps?
- Как ограничить, из какой ветки и кем можно выкатить на прод? Какие директивы за это отвечают?
- Почему прод-kubeconfig нельзя держать доступным dev-джобе и как развести креды по окружениям?
CD из Jenkins в Kubernetes держится на четырёх вещах: тег образа из стейджа сборки, kubeconfig как изолированный секрет, проверка раската через rollout status или helm --wait, и откат при сбое (--atomic либо rollback в post failure). На прод вешаешь when + input с timeout. А как только сервисов становится много - не геройствуй с push-деплоем, передай раскат Argo CD, оставив Jenkins честным CI. Версии бинарей и кластера держи в одной лодке, секреты - вне манифестов и вне логов.