в GitLab

GitLab CI: Чем проще .gitlab-ci.yml, тем лучше


В статье, завершающей цикл о настройке Gitlab CI (continuous integration) была приведена в качестве примера финальная, полностью рабочая версия конфигурационного файла .gitlab-ci.yml.

С момента ее публикации прошло чуть более четырех месяцев, теперь настройки CI в репозитории проекта немного изменились — давайте разберемся!

Целиком наш конфигурационный файл .gitlab-ci.yml выглядит так:

image: registry.gitlab.lc:5000/develop/ed/tmaier-dc-ssh:latest

services:
  - registry.gitlab.lc:5000/develop/ed/my-docker-dind:latest

variables:
  DOCKER_DRIVER: overlay2
  NODEIMAGE: registry.gitlab.lc:5000/develop/ed/node-npm-ed-sq:latest
  NODEHOTIMAGE: registry.gitlab.lc:5000/develop/ed/node-hot-ed-sq:latest
  WORKSPACEIMAGE: registry.gitlab.lc:5000/develop/ed/workspace-ed-sq:latest
  DIR_IGNORED: node_modules,bin,data/logs,data/mail,data/migrations,vendor,build,tests,public,docker
  STAGING_DC_STACK_FILE: docker-compose-stack-staging.yml
  STAGING_ENV_FILE: .env.staging.lc
  RELEASE_DC_STACK_FILE: docker-compose-stack-release.yml
  RELEASE_ENV_FILE: .env.release

stages:
  - build
  - test
  - release
  - deploy

compile:
  stage: build
  before_script:
    - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
  script:
    - cp -u .env.develop .env
    - docker run -v $(pwd):/var/www/englishdom --rm --user www-data ${WORKSPACEIMAGE} sh -c "composer install --no-dev && ./zf routes compile && ./zf cache clean-modified-content && ./zf user-default-avatar"
    - docker run -v $(pwd):/var/www/englishdom --rm ${NODEIMAGE} sh -c "npm install --production --quiet && grunt install-build --no-dev"
    - docker run -v $(pwd):/var/www/englishdom --rm ${NODEHOTIMAGE} sh -c "npm run aglio-build"
  cache:
    key: ${CI_COMMIT_REF_NAME}
    paths:
      - node_modules/
      - vendor/
  artifacts:
    paths:
    - build/api.html
    - vendor/
    - public/dsd/js/utils/routes.js
    - bin/
    - .env
    - public/default_img/user/en/
    - public/default_img/user/ru/
    - public/dsd/css/style.css
    - public/dsd/css/style.css.map
    - public/dsd/mix/tmp-pages-bundle/
    - public/dsd/js/bundles/
    - public/dsd/js/templates.hbs.js
    - public/dsd/js/templates.html.js
    - public/dsl/css/
    - public/dsl/js/bundles/
    - public/vendor/
    when: on_success
    expire_in: 1h
  only:
    - develop
    - /^release\/.*$/
#    - master

testing:
  stage: test
  dependencies:
  - compile
  script:
    - docker run -v $(pwd):/project --rm -w /project jakzal/phpqa phpmetrics --exclude=${DIR_IGNORED} --report-html=./build/phpmetrics/ .
#    - docker run -v $(pwd):/project --rm -w /project jakzal/phpqa phpmd . html codesize --reportfile ./build/phpmd.html --exclude ${DIR_IGNORED}
#    - docker run -v $(pwd):/project --rm -w /project jakzal/phpqa phploc --exclude=${DIR_IGNORED} --log-xml=./build/phploc.xml .
#    - docker run -v $(pwd):/project --rm -w /project jakzal/phpqa phpcpd --exclude=${DIR_IGNORED} --log-pmd=./build/phpcpd.xml .
  artifacts:
    paths:
#    - build/phpcpd.xml
#    - build/phploc.xml
#    - build/phpmd.html
    - build/phpmetrics/
    when: on_success
    expire_in: 1h
  only:
    - develop-disabled
#    - /^release\/.*$/
#    - master

release-image:
  stage: release
  dependencies:
  - compile
  - testing
  before_script:
    - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
    - BRANCH_NAME=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $1}')
    - TAG=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $2}')
    - CONTAINER_RELEASE_IMAGE=${CI_REGISTRY}/develop/ed/${BRANCH_NAME}.sources:${TAG:-latest}
  script:
    - docker build --squash -t ${CONTAINER_RELEASE_IMAGE} -f Dockerfile . --build-arg BRANCH_NAME=${BRANCH_NAME}
    - docker push ${CONTAINER_RELEASE_IMAGE}
  only:
    - develop
    - /^release\/.*$/
#    - master

deploy-to-review:
  stage: deploy
  before_script:
    - eval $(ssh-agent -s)
    - ssh-add <(echo "${SSH_PRIVATE_KEY}")
    - BRANCH_NAME=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $1}')
    - TAG=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $2}')
  script:
    - scp ./docker/${STAGING_ENV_FILE} ./docker/${STAGING_DC_STACK_FILE} provisioner@staging.lc:~/docker
    - ssh provisioner@staging.lc VER=${CI_PIPELINE_ID} DC_FILE=${STAGING_DC_STACK_FILE} ENV_FILE=${STAGING_ENV_FILE} BRANCH_NAME=${BRANCH_NAME} TAG=${TAG:-latest} 'bash -s' < ./docker/stack_deploy.sh
  environment:
    name: review/develop
    url: https://www.develop-labs.cf
  only:
    - develop
  tags:
    - deploy

deploy-to-release:
  stage: deploy
  before_script:
    - eval $(ssh-agent -s)
    - ssh-add <(echo "${SSH_PRIVATE_KEY}")
    - BRANCH_NAME=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $1}')
    - TAG=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $2}')
  script:
    - scp ./docker/${RELEASE_ENV_FILE} ./docker/${RELEASE_DC_STACK_FILE} provisioner@release.lc:~/docker
    - ssh provisioner@release.lc VER=${CI_PIPELINE_ID} DC_FILE=${RELEASE_DC_STACK_FILE} ENV_FILE=${RELEASE_ENV_FILE} BRANCH_NAME=${BRANCH_NAME} TAG=${TAG:-latest} 'bash -s' < ./docker/stack_deploy.sh
  environment:
    name: review/release
    url: https://release.eddev.cf/
  only:
    - /^release\/.*$/
  when: manual
  tags:
    - deploy

deploy-to-prod:
  stage: deploy
  before_script:
    - mkdir -p ~/.ssh
    - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -p 5022 -H 'example.com' >> ~/.ssh/known_hosts
  script:
    - git push ssh://git@example.com:/home/git/repo/ed.git HEAD:master
  environment:
    name: production
    url: https://www.example.com/
  only:
    - master
  when: manual
  tags:
    - deploy

Итак, что же изменилось за 4 месяца:

  • на ревью-окружении (задача deploy-to-review) docker теперь работает в режиме swarm, в связи с чем полностью изменился скрипт деплоя (об этом отдельная статья);
  • было развернуто release-окружение (отдельный сервер также с docker в режиме swarm), в связи с чем появилась задача deploy-to-release выполняющаяся в ручном режиме;
  • добавилась задача деплоя на production-сервер (увы, docker пока не внедрен, поэтому git push и гитхук на стороне сервера) в ручном режиме;
  • самая ресурсоемкая и длинная задача (testing) по умолчанию отключена. При необходимости ее можно включить и выбрать какие именно тесты нужно запускать (не забываем про полученные на выходе артефакты). К слову, для запуска тестов теперь используется другой docker-контейнер с огромным набором статических тестов;
  • этапы spawn (когда в начале поднимаются контейнеры, описанные в специальном файле docker-compose-build.yml) и cleanup (когда останавливаем и удаляем запущенные вначале контейнеры) полностью удалены. Теперь сборка проекта (задача compile) выполняется посредством docker run ..., а не docker exec -T ... — функциональность та же, экономия времени 30-40 секунд;
  • добавлены новые переменные в секцию variables (и удалены больше неиспользуемые). Имена переменных «говорящие», в комментариях не нуждаются;
  • так как собираются docker-образы для разных веток, формируем их имена/теги «на лету».

Чуть подробнее стоит остановиться разве что на формировании имен/тегов docker-образов. В репозитории постоянно существует ветка develop (множество коммитов/мерж реквестов ежедневно, после каждого запускается CI и образ с изменениями выкатывается на ревью); релизные ветки создаются примерно раз в 2 недели и выглядят как release/2.35.

Для сборки образов с разными именами получаем имя ветки (все, что до первого символа «/», например develop или release):

BRANCH_NAME=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $1}')

Тегом будет все, что встретится после символа «/» (например, 2.35 или пустая строка):

TAG=$(echo $CI_COMMIT_REF_NAME | awk -F "/" '{print $2}')

Имя docker-образа формируется так:

CONTAINER_RELEASE_IMAGE=${CI_REGISTRY}/develop/ed/${BRANCH_NAME}.sources:${TAG:-latest}

Следует обратить внимание на конструкцию ${TAG:-latest}, которая значит «использовать значение переменной TAG, но если такого значения нет (пусто), то использовать latest». Именно благодаря такому «костылю» решению можно получить правильные имена docker-образов.

Как всегда, буду рад любым дополнениям/замечаниям и постараюсь ответить на ваши вопросы. В следующей статье рассмотрим как изменился скрипт деплоя с переходом на docker в режиме swarm.

Добавить комментарий

  1. А есть ли способ прокинуть на машину переменные из CI, чтобы использовать например для указания версии образа в compose?

    • Не совсем понял, что вам нужно прокинуть и в каком compose это должно использоваться ((

      Но да, сделать можно все — например, записывать переменные в файл, который монтируется с машины внутрь контейнера…