Внедряем CI/CD в разработку с помощью Gitlab CI

CI/CD — это практика DevOps, ориентированная на автоматизацию развертывания приложений. Она позволяет разработчикам сосредоточиться на решении задач, не тратя время на рутинные действия, связанные с деплоем нового функционала или правок. Это становится возможным благодаря обеспечению автоматизированной системы доставки кода в необходимое окружение, чаще — на продакшн-площадку.

CI/CD пытается устранить человеческий фактор и убрать необходимость публиковать изменения вручную разработчикам.

Почему GitLab?

В своей работе мы используем self-hosted версию Gitlab, и поскольку эта платформа дает мощные возможности для организации CI/CD из коробки, мы используем именно его. Тем более, Gitlab CI очень прост для понимания и легко настраивается.

Таким образом, добавить CI/CD на новый проект занимает не более часа, а трудозатраты на публикации и шансы, что что-то сломается, сокращаются многократно.

Как это выглядит

Чтобы начать использовать GitLab CI, нужно разместить в корне проекта специальный файл — .gitlab-ci.yml. В этом файле указываются этапы (stages), задания (jobs) и скрипты, которые будут выполняться во время выполнения задания.

Также Gitlab CI позволяет прописать условия срабатывания заданий, например, отложенное выполнение (scheduled jobs) или пуш в указанную ветку.

Набор этапов называется пайплайном. Пайплайны могут представлять из себя что угодно, но чаще это стандартный набор: сборка, тесты, развертывание на площадке. Именно пайплайны вы будете описывать в .gitlab-ci.yml, если захотите использовать Gitlab CI.

Gitlab Runner

Пайплайны выполняются специальными демонами — раннерами.

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

Если вы также используете self-hosted решение, вы можете зарегистрировать новый раннер на сервере или использовать уже зарегистрированный. В публичной версии Gitlab должны быть доступны раннеры для Linux, Windows и Mac.

Реализуем CI/CD

Приблизительно .gitlab-ci.yml может выглядеть так:

stages:
  - deploy
  - error

.branch_stay_actual:
  stage: deploy
  rules:
    - if: ($CI_COMMIT_BRANCH == $BRANCH_NAME)
      when: on_success
  script:
    - make check_vars --makefile=MakefilePipeline -j1
    - make notify_start --makefile=MakefilePipeline -j1
    - make build --makefile=MakefilePipeline -j1
    - make shift_build_versions --makefile=MakefilePipeline -j1
    - make notify_success --makefile=MakefilePipeline -j1
    - make clear_tmp --makefile=MakefilePipeline -j1
  variables:
    GIT_STRATEGY: clone

dev-stay-actual:
  rules:
    - if: ($CI_PIPELINE_SOURCE == "schedule")
      when: never
    - if: ($CI_COMMIT_BRANCH == $BRANCH_NAME)
      when: on_success
  extends: .branch_stay_actual
  variables:
    COMPOSE_NAME: docker-compose-dev.yml
    FPM_CONTAINER_NAME: backend-dev-fpm
    CICD_DIR: /var/www/html/project/path/
  environment:
    name: "backend-dev"
  tags:
    - development

dev-react_error:
  stage: error
  tags:
    - development
  environment:
    name: "backend-dev"
  script:
    - sh $TELEGRAM_SCRIPT "❌ FAILURE"
  rules:
    - if: ($CI_PIPELINE_SOURCE == "schedule")
      when: never
    - if: ($CI_COMMIT_BRANCH != $BRANCH_NAME)
      when: never
    - when: on_failure

prod-stay-actual:
  rules:
    - if: ($CI_PIPELINE_SOURCE == "schedule")
      when: never
    - if: ($CI_COMMIT_BRANCH == $BRANCH_NAME)
      when: on_success
  extends: .branch_stay_actual
  variables:
    COMPOSE_NAME: docker-compose-prod.yml
    FPM_CONTAINER_NAME: backend-prod-fpm
    CICD_DIR:  /var/www/html/project/path/
  environment:
    name: "backend-prod"
  tags:
    - production

prod-error:
  stage: error
  tags:
    - production
  environment:
    name: "backend-prod"
  script:
    - sh $TELEGRAM_SCRIPT "❌ FAILURE"
  rules:
    - if: ($CI_PIPELINE_SOURCE == "schedule")
      when: never
    - if: ($CI_COMMIT_BRANCH != $BRANCH_NAME)
      when: never
    - when: on_failure

Разберем файл по порядку.

Блок stages описывает стадии пайплайна. В данном случае, имеем всего две стадии: deploy, в котором выполняются необходимые действия для деплоя проекта на сервере, и error, который выполняется в случае ошибки.

Далее идут задания.

Задание .branch_stay_actual является своеобразным прототипом, который не выполняется напрямую и наследуется в настоящих заданиях ниже (в dev-stay-actual и prod-stay-actual для деплоя на dev-площадке и prod-площадке соответственно).

Для каждого задания определены правила, при которых задание будет выполнено. Так, задание dev-stay-actual не выполняется при отложенном выполнении, а запускается лишь когда значение специальной предопределенной Gitlab переменной CI_COMMIT_BRANCH будет равно значению переменной BRANCH_NAME, которая указывается вручную. Переменная CI_COMMIT_BRANCH инициализируется автоматически самим Gitlab при пуше в ветку.

Далее идет специальный блок environment, грубо говоря, отвечающий за то, какие переменные стоит передавать в пайплайн. Environments создаются в настройках Operate вашего проекта в Gitlab, после чего в настройках CI/CD проекта можно будет указать переменные для выбранного environment.

4668218df092da8b02c80b22dddef902.jpg

Скрипт отвечает за то, какие команды выполнять. Содержимое скрипта зависит от ваших целей.

Мы применяем Makefile, чтобы уменьшить размер .gitlab-ci.yml и сохранить читаемость. Внутри этого мейкфайла, как и внутри скрипта в целом, как и говорилось, может быть что угодно — главное, чтобы команды были доступны на машине с раннером.

Пример задания Makefile, проверяющего, что все необходимые переменные заданы:

check_vars:
    ifeq ($(BRANCH_NAME),)
        $(error BRANCH_NAME var is required!)
    endif
    ifeq ($(CICD_DIR),)
        $(error CICD_DIR var is required!)
    endif
    ifeq ($(TELEGRAM_SCRIPT),)
        $(error TELEGRAM_SCRIPT var is required!)
    endif
    ifeq ($(DOTENV), )
    	$(error DOTENV var is required)
    endif

Таким образом, переменные, указанные в блоке variables задания будут переданы в окружение выполняемого скрипта. Список предопределенных переменных CI/CD Gitlab можно найти здесь.

Немаловажной деталью пайплайна являются теги заданий. Они необходимы, чтобы раннер понимал, нужно ли выполнять данное задание или нет.

Теги настраиваются для каждого раннера индивидуально и указываются у заданий. У заданий и у раннеров может быть ноль или более тегов.

Например, вы можете описать задания, которые подхватываются только раннером, работающим с заданиями помеченными как testing для периодического тестирования системы. Или отметить задания, которые выполняются только на dev-, test- или prod-площадках.

Также, можно наследовать целые пайплайны. Выглядит это примерно так:

reusable-pipeline.yml:

variables:
  DB_USER: user
  DB_PASSWORD: password

production:
  stage: production
  script:
    - build
    - deploy
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

.gitlab-ci.yml:

include:
   - project: "project-name"
        ref: 0.1.0
        file: reusable-pipeline.yml

variables:
  DB_USER: root
  DB_PASSWORD: real_password

stages:
  - build
  - test
  - production

production:
  environment:
    name: production

В данном случае .gitlab-ci.yml называется reusable-pipeline.yml и находит в другом репозитории — project-name. Так можно описать один общий пайплайн для всех похожих проектов и наследовать при необходимости.

Заключение

CI/CD помогает разработчикам сократить затраты на развертывание и настройку проектов, позволяя им сконцентрироваться на решении бизнес-задач. Gitlab — чрезвычайно мощная платформа, и мы рекомендуем присмотреться к использованию средств CI/CD, которые она представляет.

В этой статье мы рассмотрели один из самых простых сценариев использования Gitlab CI, но его возможности — куда шире.

© Habrahabr.ru