GitLab CI: deploy на docker swarm

Dec 4, 2017 10:39 · 793 words · 4 minute read gitlab gitlab ci docker docker swarm

В цикле о настройке GitLab continuous integration мы подробно рассматривали этап деплоя docker-образов на ревью окружение; чуть позже скрипт деплоя был изменен для достижения zero downtime (избавления от простоя).

В данной статье пойдет речь о деплое сервисов в docker swarm — давайте разберемся!

Рассматривать вопрос настройки docker swarm кластера мы не будем — это задача тривиальная и подобных примеров полным-полно на просторах интернета. Обязательно стоит ознакомиться с двумя статьями — Docker Swarm: stack deploy и env-переменные и Docker Swarm: stack deploy и именованные тома (named volumes), так будет гораздо понятнее что мы делаем и почему именно так.

Как я уже упоминал в предыдущей статье, деплой docker-образов на ревью (или релиз) окружения выполняются задачей из конфигурационного файла .gitlab-ci.yml, которая выглядит так:

...
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
...

Перед выполнением основных команд (секция before_script) с помощью команды ssh-add добавляется приватная часть ssh-ключа пользователя, под которым осуществляется подключение на удаленный сервер (provisioner) и формируются значения переменных ${BRANCH_NAME} и ${TAG}.

Основные команды, описанные в секции script с помощью утилиты scp копируют на сервер staging.lc (в каталог ~/docker) файл с переменными окружения и docker-compose-stack-staging.yml (описание стека docker-контейнеров), после чего передают значения нескольких переменных и выполняют на удаленном сервере скрипт stack_deploy.sh.

Содержимое скрипта stack_deploy.sh, который и выполняет основные действия деплоя в docker swarm:

#!/bin/bash
DIRECTORY=~/docker
REGISTRY=registry.gitlab.lc:5000
cd $DIRECTORY;
 
SERVICES=$(docker service ls --filter name=ed_applications --quiet | wc -l)
 
docker system prune -f;
docker volume create --name code-$VER
 
if [[ "$SERVICES" -gt 0 ]]; then
    # Update
    docker service update \
        --detach=false \
        --mount-add type=volume,source=code-$VER,target=/var/www/${BRANCH_NAME} \
        --constraint-add node.role==manager \
        --with-registry-auth \
        --image $REGISTRY/develop/ed/${BRANCH_NAME}.sources:${TAG} \
        ed_applications
else
    # Create
    docker service create \
        --name=ed_applications \
        --mount type=volume,source=code-$VER,target=/var/www/${BRANCH_NAME} \
        --constraint node.role==manager \
        --with-registry-auth \
        $REGISTRY/develop/ed/${BRANCH_NAME}.sources:${TAG}
fi
 
env $(cat $ENV_FILE | grep ^[A-Z] | xargs) docker stack deploy --with-registry-auth --compose-file $DC_FILE ed;

Как видим, здесь выполняется несколько простых действий:

  • проверяем, запущен ли сервис с именем ed_applications (data-only контейнер с кодом);
  • выполняем очистку системы от неиспользуемых docker-контейнеров/образов/томов и т.д.;
  • создаем новый именованный том, частью имени будет значение переменной переданной из CI;
  • создаем (если его нет) или обновляем (если есть) сервис ed_applications с использованием «свежего» контейнера с кодом (имя/тег также получаем из CI — это дает возможность использовать один и тот же скрипт на разных окружениях);
  • применяем переменные окружения из файла (имя файла получаем из CI) и обновляем стек сервисов, описанных в файле имя которого получаем также из CI (в данном случае docker-compose-stack-staging.yml).

Конфигурационный файл docker-compose-stack-staging.yml выглядит так:

version: '3.1'
services:
### PHP-FPM ############################################################################################################
  php-fpm:
    image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:staging
    volumes:
      - developcode:/var/www/develop
      - static:/var/www/static
    deploy:
      replicas: 4
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "php-fpm"
### Nginx ##############################################################################################################
  nginx:
    image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:staging
    volumes:
      - developcode:/var/www/develop
      - static:/var/www/static
    ports:
      - "80:80"
      - "443:443"
    deploy:
      replicas: 4
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
### SDCV ##############################################################################################################
  sdcv:
    image: registry.gitlab.lc:5000/develop/ed/sdcv-ed-sq:latest
    ports:
      - "9095:9095"
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
        delay: 1s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "sdcv"
### Redis ##############################################################################################################
  redis:
    image: registry.gitlab.lc:5000/develop/ed/redis-ed-sq:latest
    volumes:
      - redis:/data
    ports:
      - "6379:6379"
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
        delay: 1s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "redis"
### Memcached ##########################################################################################################
  memcached:
    image: registry.gitlab.lc:5000/develop/ed/memcached-ed-sq:latest
    volumes:
      - memcached:/var/lib/memcached
    ports:
      - "11211:11211"
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
        delay: 1s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "memcached"
### Websocket ##########################################################################################################
  websocket:
    image: registry.gitlab.lc:5000/develop/ed/websocket-ed-sq:latest
    env_file: .env.staging.lc
    ports:
      - "8092:8092"
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
        delay: 1s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "websocket"
### Monitoring: node-exporter ##########################################################################################
  nodeexporter:
    image: prom/node-exporter:v0.14.0
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '-collector.procfs=/host/proc'
      - '-collector.sysfs=/host/sys'
      - '-collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
    ports:
      - "9100:9100"
    deploy:
      mode: global
      restart_policy:
        condition: on-failure
    labels:
      org.label-schema.group: "monitoring"
### Monitoring: cAdvisor ###############################################################################################
  cadvisor:
    image: google/cadvisor:v0.26.1
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "9200:8080"
    deploy:
      mode: global
      restart_policy:
        condition: on-failure
    labels:
      org.label-schema.group: "monitoring"
### Volumes Setup ######################################################################################################
volumes:
  developcode:
    external:
      name: code-${VER}
  memcached:
    driver: "local"
  redis:
    driver: "local"
  static:
    driver: "local"
    driver_opts:
      type: nfs
      o: addr=192.168.0.218,rw
      device: ":/srv/static"

О переходе на docker-compose.yml третьей версии мы уже упоминали в этой статье, стоит отметить только что в стек сервисов добавились два контейнера для мониторинга (node-exporter и cadvisor), которые запускаются в режиме mode: global (по одному экземпляру на каждой ноде кластера), собирают метрики и отправляют их в Prometheus.

tweet Share