Привет всем! В предыдущей статье мы подробно рассмотрели реализацию непрерывной интеграции CI на базе Gitea/Forgejo в платформе Gitorion. В данной статье предлагаем вашему вниманию подробнее познакомиться с внедрением непрерывной доставки CD в платформу Gitorion на базе Jenkins.
Jenkins Agents
Jenkins выполняет все команды пайплайнов в агентах, которые запускает как модули в кластере Kubernetes по Web-хуку из Gitea/Forgejo. Спецификацию модуля агента Jenkins задайте в Jenkinsfile:
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
metadata:
name: build-pod
annotations:
container.apparmor.security.beta.kubernetes.io/buildkitd: unconfined
labels:
app.kubernetes.io/component: jenkins-dind
app.kubernetes.io/instance: jenkins
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- "dc1-worker1"
containers:
- name: jnlp
resources: {}
- name: docker-client
image: docker:dind-rootless
imagePullPolicy: IfNotPresent
env:
- name: DOCKER_HOST
value: "unix:///run/user/1000/docker.sock"
securityContext:
privileged: true
- name: buildkitd
image: moby/buildkit:master-rootless
imagePullPolicy: IfNotPresent
args:
- --oci-worker-no-process-sandbox
- --addr
- unix:///run/user/1000/buildkit/buildkitd.sock
- --addr
- tcp://0.0.0.0:1234
readinessProbe:
exec:
command:
- buildctl
- debug
- workers
initialDelaySeconds: 5
periodSeconds: 30
livenessProbe:
exec:
command:
- buildctl
- debug
- workers
initialDelaySeconds: 5
periodSeconds: 30
securityContext:
seccompProfile:
type: Unconfined
runAsUser: 1000
runAsGroup: 1000
volumeMounts:
- mountPath: /home/user/.local/share/buildkit
name: buildkitd
volumes:
- name: buildkitd
emptyDir: {}
"""
}
}
stage ('build') {
steps {
container('docker-client') {
script {
...
}
}
}
}
stage('staging') {
steps {
script {
container('docker-client') {
script {
...
}
}
}
}
}
...
}
Агент вытягивает Git-репозиторий микросервиса из Gitea/Forgejo по Web-хуку и выполняет стадии пайплана в Jenkinsfile, расположенном в корне git-репозитория.

Безопасная сборка Docker-образов в Kubernetes
Для сборки Docker-образов в кластере Kubernetes запустите в модуле агента Jenkins "build-pod" контейнер "docker-client" созданный из Docker-образа "image: docker:dind-rootless". В данном контейнере будет запущен Docker-сервер, который выполнит все команды docker из пайплайна. Сборку Docker-образов Docker-сервер отправляет на Buildkitd-сервер, запущенный в контейнере "buildkitd" из образа "image: moby/buildkit:master-rootless".
Из соображений безопасности настоятельно рекомендуем использовать только rootless образы dind и buildkit d в которых все процессы запускаются от имени пользователя не имеющего root-прав. В контейнерах, созданных из образов dind:latest и buildkit:latest процессы запускаются с root-правами, что дает возможность получить root-доступ к хосту кластера Kubernetes. Так же следует запретить на git-сервере вносить изменения в Jenkinsfile всем кроме доверенных лиц, чтобы лишить возможности переопределить модуль агента Jenkins. Как это сделать мы подробно рассказали в статье про непрерывную интеграцию CI в пункте "Защита веток".
Для ускорения сборки Docker-образов Buildkit задействует кэш сборки Docker, который хранит в приватном репозитории Docker-образов. Данный подход позволяет не собирать при каждой сборке слои, в которых не внесли изменения, а брать уже ранее собранные слои в предыдущих сборках из кэша:
+ docker buildx create --use '--driver=remote' tcp://127.0.0.1:1234
keen_hoover
[Pipeline] sh
+ docker buildx build --push -t registry.gitorion.kvm/gitorion/owneruser/frontend/master:d5a6fa44a1fafdcf89423096f7d2125702e85001 --cache-to 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/owneruser/frontend/master:latest,mode=min' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/owneruser/frontend/master:latest' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/nginx:1.27.3-alpine3.20' .doc
На рисунке ниже приведем пример сборки Docker-образа в пайплайне Jenkins

Приватный репозиторий Docker-образов
Для хранения собранных Docker-образов установили в кластер Kubernetes приватный репозиторий Docker-образов Harbor. Jenkins Agent выполняет команду docker push и отправляет Docker-образ, собранный на стадии Build, в приватный репозиторий Docker-образов. На стадии доставки микросервиса в кластер Kubernetes ресурсы Deployment или StatefulSet берут Docker-образы из приватного репозитория Docker-образов и используют для запуска Docker-контейнеров микросервисов в кластере Kubernetes.

Multibranch Pipeline
В предыдущей статье про непрерывную интеграцию CI на базе Gitea/Forgejo мы упоминали, что каждый программист вносит изменения в код только в пределах своей ветки. В Jenkins есть пайплайн типа Multibranch Pipeline, который автоматически обнаруживает новые ветки в Git-репозитории и для каждой из них запускает свой пайплайн. Программист создает новую ветку командами:
git checkout -b dev3
git push --set-upstream origin dev3
Gitea/Forgejo посылает Web-хук в Jenkins. Jenkins по Web-хуку автоматически находит новую ветку и запускает для нее пайплайн.

Cтадии пайплайна
Независимо от имени ветки, первой запускается стадия Build, в которой собирается Docker-образ микросервиса и помещается в приватный репозиторий Docker Registry. Далее, пайплайн извлекает из Web-хука имя ветки. Для ветки main запускает стадию Staging и доставляет микросервис в контур Staging.

Для всех остальных веток пайплайн запускает стадию Review и деплоит микросервис в контур Development.

Cтадии Staging и Review используют одни и те же шаблоны templates helm-чарта в Git-репозитории микросервиса, чтобы на Review доставлялось то же самое, что и на Staging и в Production. В helm-чарте содержится директория configs с конфигами микросервиса отдельно для каждого контура Development, Staging и Production.
Связь коммитов Gitea с ревизиями Helm
Jenkins Agent в момент деплоя микросервиса в Staging контур извлекает SHA1 коммита из Web-хука Gitea/Forgejo.

Каждый новый деплой микросервиса - это очередная ревизия helm. Мы добавили SHA1 коммита к именам ревизий helm, тем самым связав SHA1 коммитов с номерами ревизий helm.

Так же SHA1 коммитов мы использовали в качестве тэгов при именования Docker-образов:
image: docker-registry.jenkins.svc.cluster.local/owneruser/backend/main:bb7cfed40a
Промоушен Promote со Staging на Production
После демонстрации в окружении Staging наработок программистов заказчику, тимлид вручную запускает пайплайн промоушена Promote микросервиса со Staging на Production. Пайплайн выполняет команду:
helm history staging-owneruser-backend -n staging
Берет SHA1 коммита, соответствующего ревизии, у которой в столбце STATUS стоит "deployed" (см. скрин выше). Извлекает из приватного репозитория образов Docker Registry Docker-образ, тэг которого соответствует коммиту со статусом "deployed":
image: docker-registry.jenkins.svc.cluster.local/owneruser/backend/main:74e5cbd967
и доставляет в Production контур. Стадия Build игнорируется, поскольку сборка Docker-образа не нужна на стадии промоушена Promote.

Откат Rollback на предыдущие версии
На случай, если в продакшен будет доставлен микросервис с ошибкой, разработали пайплайн отката Rollback на предыдущие версии. Чтобы иметь возможность оперативно восстановить работу продакшена, пока программисты будут исправлять ошибку.
Для отката Rollback тимлиду нужно определиться, до какого коммита в Gitea/Forgejo он хочет откатиться, задать SHA1 коммита в параметрах пайплайна и вручную запустить пайплайн.

Jenkins Agent выполнит откат командой helm rollback до ревизии, соответствующей выбранному коммиту.

Canary-релизы
Чтобы не перегружать текущую статью, мы рассказали о способах безопасно протестировать новый релиз приложения на ограниченном количестве реальных пользователей в продакшене в отдельной статье по Canary-релизы.
Заключение
В следующей статье мы рассмотрим подробнее тонкости реализации единого входа Single Sign-On (SSO) во все сервисы платформы Gitorion при помощи Keycloak. Спасибо.