Привет, меня зовут Панов Михаил, я DevOps‑инженер МТС Digital. Хочу поделиться с вами опытом построения «модульных» pipelines на основе gitlab-ci. В этой статье я расскажу, что такое модульный CI/CD, из чего он состоит, для чего нужен и как поможет командам, поддерживающим большой список нетиповых репозиториев.

Часть 1: Проблема класcического gitlab-ci
Предположим, что вы участник команды, которая сопровождает крупный стрим, в котором есть несколько команд со своими сервисами:
-
Frontend;
-
Backend;
-
API.
Одна команда использует Golang, другая часть сервисов – на основе Gradle, а еще Frontend есть. Нужно не забывать и о том, что существуют нетиповые репозитории, например, для AWX или Terraform.
В таких условиях возникает вопрос: «А как лучше все это автоматизировать?». Варианты такие:
-
можно создать типовые для каждого языка gitlab-ci.yml и складывать их в репозитории. Но тут мы столкнемся с трудностями версионирования и обновления;
-
можно создать один инфраструктурный репозиторий и переложить все туда, затем с помощью include подключать в нужный репозиторий. Но и тут возникнут трудности. Файлы будут предназначены для конкретной задачи (test, build, deploy, etc).
Хотелось бы получить что-то настолько гибкое и быстрое, что-то, что даст возможность уменьшить время внедрения новых проектов в наши pipelines.
Посмотрим, что можно сделать и перейдем к примерам.
Часть 2: Типовые gitlab-ci – что можно сделать лучше?
Типовой gitlab-ci.yml, как правило, располагается в самом репозитории и содержит несколько stage и job. Например:
#Пример 1 stages: - test - build - deploy test:job: stage: test image: name: $IMAGE entrypoint: [] script: - echo "test" rules: if: $CI_COMMIT_BRANCH == "master" varuables: ENABLE: "true" tags: - prod-runner build:job: stage: build image: name: $IMAGE entrypoint: [] dependencies: [] needs: - test-job script: - echo "build" rules: if: $CI_COMMIT_BRANCH == "master" varuables: ENABLE: "true" tags: - prod-runner deploy:job: stage: deploy image: name: $IMAGE entrypoint: [] dependencies: - build-job needs: - test-job - build-job script: - echo "deploy" rules: if: $CI_COMMIT_BRANCH == "master" varuables: ENABLE: "true" tags: - prod-runner
В свою очередь, каждая job несет в себе информацию о том, что и как нужно сделать с кодом. Это рабочий вариант, но с каждой новой строчкой кода будет усложняться «читабельность» файла.
Пункт 2.1: Теория модульности
Чтобы достичь гибкости и читабельности кода, разобьем Пример 1 на логические части.
Gitlab предоставляет возможности для разделения кода с помощью:
-
include – добавление одного файла в другой;
-
extends – объединение схожих блоков кода между собой или расширение одного блока кода за счет другого;
-
anchors – якоря, которые позволяют переиспользовать блоки кода в разных сценариях;
-
reference tags – ссылки, которые дают возможность сослаться на блок кода, а также использовать и переиспользовать этот код.
Эти инструменты позволят собирать пайплайны (pipelines) как «конструктор».
Пункт 2.2: Структура и теория
Взглянув еще раз на Пример 1, выделим части, которые занимают много места и могут быть переиспользованы. Например:
-
блок правил (rules);
-
блок переменных (variables);
-
блок скриптов(before_script, script, after_script)
Вынесем эти блоки в отдельные файлы и определим структуру проекта:
templates-repo/ |--variables | |--test-vars | | └──test-vars.gitlab-ci.yml | |--build-vars | | └──build-vars.gitlab-ci.yml | └──deploy-vars | └──deploy-vars.gitlab-ci.yml |--rules | |--test-rules | | └──test-rules.gitlab-ci.yml | |--build-rules | | └──build-rules.gitlab-ci.yml | └──deploy-rules | └──deploy-rules.gitlab-ci.yml |--scripts | |--test-scripts | | └──test-scripts.gitlab-ci.yml | |--build-scripts | | └──build-scripts.gitlab-ci.yml | └──deploy-scripts | └──deploy-scripts.gitlab-ci.yml |--templates | |--build-bin | | └──build-bin-template.gitlab-ci.yml | |--build-image | | └──build-image-template.gitlab-ci.yml | └──deploy | └──deploy-template.gitlab-ci.yml |--entrypoints | |--java | | |--mvn.gitlab-ci.yml | | └──gradle.gitlab-ci.yml | |--golang | | └──golang.gitlab-ci.yml | └──deploy | └──deploy-template.gitlab-ci.yml
Структура проекта определена, теперь нам нужно собрать первую job. Для примера соберем job kaniko, остальные jobs будут собираться по такому же принципу.
Пункт 2.3: Примеры
Чтобы получить полноценную job, необходимо объединить вынесенные элементы между собой. Ниже примеры объединяемых шаблонов:
build-rules.gitlab-ci.yml
############# ## Changes ## ############# .changes-exclude-files-devops: &changes-exclude-files-devops - ".gitlab-ci.yml" - "Dockerfile" ########### ## Rules ## ########### .if-enable-build-kaniko-env: &if-enable-build-kaniko-env if: $ENABLE_KANIKO_BUILD == "false" .if-enable-build-kaniko-tag-env: &if-enable-build-kaniko-tag-env if: $ENABLE_KANIKO_BUILD_TAG == "false" .if-ci-branch-and-env-exist: &if-ci-branch-and-env-exist if: $CI_COMMIT_BRANCH =~ /master|feature\/.*/ && $ENABLE_KANIKO_BUILD == "true" .if-ci-tag-and-env-exist: &if-ci-tag-and-env-exist if: $CI_COMMIT_TAG =~ /^(v[0-9]\d*)\.([0-9]\d*)\.([0-9]\d*)(-([a-zA-Z-]*))?/ && $ENABLE_KANIKO_BUILD_TAG == "true" .if-piplince-surce-push-or-merge-request: &if-piplince-surce-push-or-merge-request if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" .if-ci-commit-tag: &if-ci-commit-tag if: $CI_COMMIT_TAG ################# ## Build rules ## ################# .rules:build-image:job: rules: - <<: *if-enable-build-kaniko-env when: never - <<: *if-piplince-surce-push-or-merge-request changes: *changes-exclude-files-devops when: never - <<: *if-ci-commit-tag when: never - <<: *if-ci-branch-and-env-exist - when: never
build-vars.gitlab-ci.yml
.variables:k8s:deploy:kaniko: variables: ENVIRONMENT: prod IMAGE: kaniko:latest
build-scripts.gitlab-ci.yml
.script:infra-build-script: script: - 'echo "Building docker image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"' - /kaniko/executor --context "$CI_PROJECT_DIR" --dockerfile "$CI_PROJECT_DIR/.docker/Dockerfile" --destination "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA" --cache=true --cache-ttl 336h --label ru.mts.git.project="$CI_PROJECT_URL" --label ru.mts.git.ref="$CI_COMMIT_REF_NAME" --label ru.mts.git.revision="$CI_COMMIT_SHORT_SHA" --skip-tls-verify --use-new-run --registry-mirror=your.registry.com --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy --build-arg no_proxy=$no_proxy
Файл /templates/build-image/build-image-template.gitlab-ci.yml будет выглядеть следующим образом:
include: - local: '/scripts/build-scripts/build-scripts.gitlab-ci.yml' - local: '/rules/build-rules/build-rules.gitlab-ci.yml' - local: '/variables/build-vars/build-vars.gitlab-ci.yml' build:kaniko:job: stage: build-image image: name: $IMAGE entrypoint: [] needs: - job: test-job dependencies: [] extends: - .script:infra-build-script - .rules:build-image:job - .variables:k8s:deploy:kaniko tags: - prod-runner
С помощью extends мы объединили в шаблон scripts, rules и variables. Это дает возможность собрать полноценный job.
Как видно из примера выше (Пункт 2.2 — структура), появилась еще одна директория entrypoints/, которая объединяет в себе шаблоны (templates) для формирования подключаемого файла для группы проектов.
Он будет выглядеть следующим образом:
include: - local: '/templates/build-bin/build-bin-template.gitlab-ci.yml' - local: '/templates/build-image/build-image-template.gitlab-ci.yml' - local: '/templates/deploy/deploy-template.gitlab-ci.yml' stages: - test - build - deploy # Global variables variables: ENABLE_LINT: "true" ENABLE_BUILD: "true" ENABLE_DEPLOY_PROD: "true" ENABLE_DEPLOY_TEST: "true" ENABLE_DEPLOY_DEV: "true"
Тут мы перечисляем stages, includes, variables. То есть те элементы которые могут находиться только в «верхнеуровневом» или подключаемом файле.
Пункт 2.4: Нюансы
Пройдемся по нюансам этой реализации. Так как Gitlab у всех может быть разной степени владения — «коммунальный» или полностью подконтрольный и закрытый, реализация шаблонов будет немного меняться в части include. Документация говорит, что мы можем воспользоваться четырьмя возможными «инпутами»:
Possible inputs: The include subkeys.
include:local – используется для подключения файлов, находящихся в репозитории локально;
include:project – вариант, подходящий в большинстве случаев, так как позволяет тонко настраивать подключение файла в репозиторий назначения;
include:remote – используется для подключения шаблонов из внешних источников, потребуется авторизация (подробнее в документации);
include:template – отметаем, так как он используется для подключения встроенных шаблонов самого gitlab.
Также хотелось бы обратить внимание на несколько моментов:
Если репозиторий у вас открыт и все хранится в main (master) ветке — можно воспользоваться include:remote, так как это будет ссылка на конкретный файл и она займет меньше места (хотя, казалось бы, куда еще меньше?). Если репозиторий закрытый(internal) — используйте include:project.
Include:project можно использовать двумя разными способами в зависимости от того, как вы управляете инфраструктурными файлами в своих репозиториях.
Вариант 1. Если изменения производятся с помощью ansible, terraform, etc — файл будет следующего вида, с явным указанием полей:
include: - project: 'my-group/my-project' ref: v1.0.0 file: '/templates/.gitlab-ci-template.yml' variables: ENABLE_LINT: "true" ENABLE_BUILD: "true" ENABLE_DEPLOY_PROD: "false" ENABLE_DEPLOY_TEST: "true" ENABLE_DEPLOY_DEV: "true"
Вариант 2. Если управление производится на основе переменных окружения:
include: - project: '$CI_PROJECT_PATH' ref: '$CI_PROJECT_REF' file: '$CI_PROJECT_FILE' variables: ENABLE_LINT: "true" ENABLE_BUILD: "true" ENABLE_DEPLOY_PROD: "false" ENABLE_DEPLOY_TEST: "true" ENABLE_DEPLOY_DEV: "true"
У вас может быть наоборот.
Заключение
Такой способ объединения шаблонов позволяет гибко настроить CI/CD без заявки на ультиматизм. Вы можете комбинировать шаблоны(templates) любым удобным для вас способом, один из примеров – это репозиторий шаблонов самого gitlab. Мне же хотелось в этой статье раскрыть вопрос модульности и гибкости, а также результаты, которые можно получить, используя такой подход. Буду рад Вашим вопросам и комментариям.
ссылка на оригинал статьи https://habr.com/ru/companies/ru_mts/articles/747978/
Добавить комментарий