Как стать автором
Обновить
29.94
Gitorion
CI/CD Kubernetes-платформа

CI/CD Kubernetes платформа Gitorion. Непрерывная доставка Continuous Delivery на базе Jenkins

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров2.7K

Привет всем! В предыдущей статье мы подробно рассмотрели реализацию непрерывной интеграции Continuous Integration (CI) на базе Gitea/Forgejo в CI/CD платформе Gitorion. В данной статье предлагаем вашему вниманию подробнее познакомиться с внедрением непрерывной доставки Continuous Delivery (CD) в CI/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-репозитория.

Jenkins Agent вытягивает Git-репозиторий из Gitea/Forgejo
Jenkins Agent вытягивает Git-репозиторий из Gitea/Forgejo

Безопасная сборка 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, в которых все процессы запускаются от имени пользователя, не имеющего root-прав. В контейнерах, созданных из образов dind:latest и buildkit:latest, процессы запускаются с root-правами, что дает возможность получить root-доступ к хосту кластера Kubernetes. Также следует запретить в настройках git-сервера вносить изменения в Jenkinsfile всем, кроме доверенных лиц, чтобы лишить возможности переопределить модуль агента Jenkins. Как это сделать, мы подробно рассказали в статье про непрерывную интеграцию CI в пункте "Защита веток".

На рисунке ниже приведем пример сборки Docker-образа в пайплайне Jenkins

Jenkins Agent собирает Docker-образ приложения
Jenkins Agent собирает Docker-образ приложения

Кэш сборки Docker-образов

Как известно, Docker-образы последовательно собираются слоями, заданными в Dockerfile. Рассмотрим данный механизм на примере Dockerfile бэкенда:

FROM registry.gitorion.ru/gitorion/php:8.3.1-fpm-alpine3.19 as buildimage

RUN echo -e "https://mirror.yandex.ru/mirrors/alpine/v3.19/main\nhttps://mirror.yandex.ru/mirrors/alpine/v3.19/community" > /etc/apk/repositories \
 && apk update \
 && apk --no-cache add \
    postgresql-dev \
    libmemcached-libs \
    zlib

RUN set -xe && \
    cd /tmp/ && \
    apk add --no-cache --update --virtual .phpize-deps $PHPIZE_DEPS && \
    apk add --no-cache --update --virtual .memcached-deps zlib-dev libmemcached-dev cyrus-sasl-dev && \
    pecl install igbinary && \
    ( \
        pecl install --nobuild memcached && \
        cd "$(pecl config-get temp_dir)/memcached" && \
        phpize && \
        ./configure --enable-memcached-igbinary && \
        make -j$(nproc) && \
        make install && \
        cd /tmp/ \
    )

RUN docker-php-ext-install \
	mysqli \
	pdo \
	pdo_mysql \
	pgsql \
	pdo \
	pdo_pgsql

FROM registry.gitorion.ru/gitorion/php:8.3.1-fpm-alpine3.19

WORKDIR /var/www/html
EXPOSE 9000

COPY --from=buildimage /usr/local/lib/php/extensions/ \
    /usr/local/lib/php/extensions/

RUN echo -e "https://mirror.yandex.ru/mirrors/alpine/v3.19/main\nhttps://mirror.yandex.ru/mirrors/alpine/v3.19/community" > /etc/apk/repositories \
 && apk update \
 && apk --no-cache add \
    libpq \
    libmemcached-libs \
 && docker-php-ext-enable \
    mysqli \
    pdo_mysql \
    pgsql \
    pdo_pgsql \
    pdo \
    igbinary \
    memcached

COPY ./entrypoint.sh /app/entrypoint.sh
COPY ./src /var/www/html

ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "/usr/local/sbin/php-fpm", "-F" ]

В слое RUN сначала выполняется компиляция расширений PHP из исходного кода, и чуть ниже установка пакетов командой apk. Оба действия затрачивают много времени и ресурсов, однако не требуются при каждой сборке Docker-образа, когда вносят правки в код сайта на PHP, который на финальной стадии сборки копируется в Docker-образ из git-репозитория:

COPY ./src /var/www/html

Существенно ускорить процесс сборки Docker-образа можно, используя кэш сборки. Docker позволяет не собирать каждый раз слои не претерпевшие изменения, а взять уже собранные из кэша предыдущей сборки. Вот так выглядит команда сборки Docker-образа:

docker buildx build --push -t registry.gitorion.ru/gitorion/owneruser/backend/feature-change-version:e0a232294efa0b3b73c6a9147a9919e2c35d6943 --cache-to 'type=registry,image-manifest=true,ref=registry.gitorion.ru/gitorion/owneruser/backend/feature-change-version:latest,mode=min' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.ru/gitorion/owneruser/backend/feature-change-version:latest' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.ru/gitorion/php:8.3.1-fpm-alpine3.19' .

Docker-образ "feature-change-version:e0a232294efa0b3b73c6a9147a9919e2c35d6943" собранный данной командой и кэш сборки "feature-change-version:latest" (параметр --cache-to) будут помещены в приватный репозиторий "https://registry.gitorion.ru". При следующей сборке уже будет задействован кэш предыдущей сборки "feature-change-version:latest" (параметр --cache-from).

повторная сборка Docker-образа с кэшем

docker buildx create --use '--driver=remote' tcp://127.0.0.1:1234 amazing_ride

[Pipeline] sh

docker buildx build --push -t registry.gitorion.kvm/gitorion/owneruser/backend/feature-change-version:c52901ce5e46f886cbc5529e70eacc9e525a9718 --cache-to 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/owneruser/backend/feature-change-version:latest,mode=min' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/owneruser/backend/feature-change-version:latest' --cache-from 'type=registry,image-manifest=true,ref=registry.gitorion.kvm/gitorion/php:8.3.1-fpm-alpine3.19' .

#0 building with "amazing_ride" instance using remote driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.98kB done
#1 DONE 0.1s

#2 [auth] gitorion/php:pull token for registry.gitorion.kvm
#2 DONE 0.0s

#3 [internal] load metadata for registry.gitorion.kvm/gitorion/php:8.3.1-fpm-alpine3.19
#3 DONE 0.4s

#4 [internal] load .dockerignore
#4 transferring context: 2B done
#4 DONE 0.1s

#5 [internal] load build context
#5 DONE 0.0s

#6 [auth] gitorion/owneruser/backend/feature-change-version:pull token for registry.gitorion.kvm
#6 DONE 0.0s

#7 [buildimage 1/4] FROM registry.gitorion.kvm/gitorion/php:8.3.1-fpm-alpine3.19@sha256:26756a4f0f716c9e16e2d19a875d229150523d25e93ebd1e2f45dc229987166f
#7 resolve registry.gitorion.kvm/gitorion/php:8.3.1-fpm-alpine3.19@sha256:26756a4f0f716c9e16e2d19a875d229150523d25e93ebd1e2f45dc229987166f 0.0s done
#7 DONE 0.1s

#8 importing cache manifest from registry.gitorion.kvm/gitorion/php:8.3.1-fpm-alpine3.19
#8 inferred cache manifest type: application/vnd.docker.distribution.manifest.v2+json done
#8 DONE 0.2s

#9 importing cache manifest from registry.gitorion.kvm/gitorion/owneruser/backend/feature-change-version:latest
#9 inferred cache manifest type: application/vnd.oci.image.manifest.v1+json done
#9 DONE 0.2s

#5 [internal] load build context
#5 transferring context: 2.41kB done
#5 DONE 0.1s

#10 [stage-1 4/6] RUN echo -e "https://mirror.yandex.ru/mirrors/alpine/v3.19/main\nhttps://mirror.yandex.ru/mirrors/alpine/v3.19/community" > /etc/apk/repositories && apk update && apk --no-cache add libpq libmemcached-libs && docker-php-ext-enable mysqli pdo_mysql pgsql pdo_pgsql pdo igbinary memcached
#10 CACHED

#11 [stage-1 2/6] WORKDIR /var/www/html
#11 CACHED

#12 [buildimage 2/4] RUN echo -e "https://mirror.yandex.ru/mirrors/alpine/v3.19/main\nhttps://mirror.yandex.ru/mirrors/alpine/v3.19/community" > /etc/apk/repositories && apk update && apk --no-cache add postgresql-dev libmemcached-libs zlib
#12 CACHED

#13 [buildimage 3/4] RUN set -xe && cd /tmp/ && apk add --no-cache --update --virtual .phpize-deps autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c && apk add --no-cache --update --virtual .memcached-deps zlib-dev libmemcached-dev cyrus-sasl-dev && pecl install igbinary && ( pecl install --nobuild memcached && cd "(pecl config-get temp_dir)/memcached(nproc) && make install && cd /tmp/ )
#13 CACHED

#14 [buildimage 4/4] RUN docker-php-ext-install mysqli pdo pdo_mysql pgsql pdo pdo_pgsql
#14 CACHED

#15 [stage-1 3/6] COPY --from=buildimage /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
#15 CACHED

#16 [stage-1 5/6] COPY ./entrypoint.sh /app/entrypoint.sh
#16 CACHED

#17 [stage-1 6/6] COPY ./src /var/www/html
#17 DONE 0.1s

Видим, что слои не претерпевшие изменений, взяты из кэша и помечены как CACHED.

В итоге, на первичную сборку Docker-образа без кэша потребовалось "1min45s" а на повторную сборку с кэшем "14s".

Время работы пайплайнов с кэшем сборки Docker-образов и без
Время работы пайплайнов с кэшем сборки Docker-образов и без

Приватный реестр Docker-образов

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

Jenkins Agent отправляет Docker-образ в приватный реестр
Jenkins Agent отправляет Docker-образ в приватный реестр

Multibranch Pipeline

В предыдущей статье про непрерывную интеграцию Continuous Integration на базе Gitea/Forgejo мы упоминали, что каждый программист вносит изменения в код только в пределах своей ветки. В Jenkins есть пайплайн типа Multibranch Pipeline, который автоматически обнаруживает новые ветки в git-репозитории и для каждой из них запускает свой пайплайн. Программист создает новую ветку командами:

git checkout -b feature/change-version
git push --set-upstream origin  feature/change-version

Gitea/Forgejo посылает Web-хук в Jenkins. Jenkins по Web-хуку автоматически находит новую ветку и запускает для нее пайплайн.

Multibranch Pipeline нашел новую ветку feature/change-version в git-репозитории и запустил для нее пайплайн
Multibranch Pipeline нашел новую ветку feature/change-version в git-репозитории и запустил для нее пайплайн

Cтадии пайплайна

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

Стадия Staging
Стадия Staging

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

Стадия Review
Стадия Review

Деплой приложения с помощью Helm

После сборки приложение развертывается в кластер Kubernetes c помощью helm-чарта, хранящегося в git-репозитории приложения. Cтадии Staging и Review используют одни и те же шаблоны templates helm-чарта, чтобы на Review доставлялось то же самое, что и на Staging и в Production. В helm-чарте содержится директория configs с конфигурационными файлами приложения для каждого контура Development, Staging и Production.

Jenkins Agent в момент деплоя приложения в Staging контур извлекает SHA1 коммита из Web-хука Gitea/Forgejo.

Коммиты в Gitea/Forgejo
Коммиты в Gitea/Forgejo

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

Связь SHA1 коммитов Gitea/Forgejo c номерами ревизий helm
Связь SHA1 коммитов Gitea/Forgejo c номерами ревизий helm

Также SHA1 коммитов мы использовали в качестве тэгов при именовании Docker-образов. Например, "master:0cf40b11aae994d1832a83d675d7305647106378".

Промоушен Promote со Staging на Production

После демонстрации в окружении Staging наработок программистов заказчику, тимлид вручную запускает пайплайн промоушена Promote приложения со Staging на Production. Пайплайн извлекает историю ревизий с помощью команды:

helm history staging-owneruser-backend -n staging

Берет SHA1 коммита, соответствующего ревизии, у которой в столбце STATUS стоит "deployed" (см. скрин выше). Из приватного реестра Docker-образов извлекает Docker-образ, тэг которого соответствует коммиту со статусом "deployed":

"master:0cf40b11aae994d1832a83d675d7305647106378"

и доставляет в Production контур. Стадия Build игнорируется, поскольку сборка Docker-образа не нужна на стадии промоушена Promote.

Стадия промоушена Promote со Staging на Production
Стадия промоушена Promote со Staging на Production

Откат Rollback на предыдущие версии

На случай, если в продакшен будет доставлено приложение с ошибкой, разработали параметризованный пайплайн отката Rollback на предыдущие версии. Чтобы иметь возможность оперативно восстановить работу продакшена, пока программисты будут исправлять ошибку.

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

Список коммитов, до которых можно сделать откат Rollback
Список коммитов, до которых можно сделать откат Rollback

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

Откат Rollback до заданного коммита
Откат Rollback до заданного коммита

Canary-релизы

Чтобы не перегружать текущую статью, мы рассказали, как протестировать новый релиз приложения на ограниченном количестве реальных пользователей в продакшене в отдельной статье по Canary-релизы.

Заключение

В следующей статье мы рассмотрим подробнее тонкости реализации единого входа Single Sign-On (SSO) во все сервисы CI/CD платформы Gitorion при помощи Keycloak. Спасибо за внимание!

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Публикации

Информация

Сайт
gitorion.ru
Дата регистрации
Численность
2–10 человек
Представитель
gitorion

Истории