Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

Как оптимизировать деплой через docker-compose?

#php #docker #gitlab #docker_compose #gitlab_ci


Мы реализовали деплой через GitLab CI и docker-compose. В процессе деплоя на удалённый
сервер копируется файл docker-compose.yml и файлы Dockerfile и пр. для создания "вспомогательных"
контейнеров типа nginx и mysql

Всё работает как нужно. Беспокоят 2 момента: даунтайм и "мусорные" образы docker
(те, что с  в колонке TAG в docker images)

Вот кусок файла .gitlab-ci.yml, ответственный собственно на деплой на удалённый сервер:

.template-secure-copy: &secure-copy
    stage: deploy
    image: covex/alpine-git:1.0
    before_script:
      - eval $(ssh-agent -s)
      - ssh-add <(echo "$SSH_PRIVATE_KEY")
    script:
      - ssh -p 22 $DEPLOY_USER@$DEPLOY_HOST 'set -e ;
          rm -rf '"$DEPLOY_DIRECTORY"'_tmp ;
          mkdir -p '"$DEPLOY_DIRECTORY"'_tmp'
      - scp -P 22 -r build/* ''"$DEPLOY_USER"'@'"$DEPLOY_HOST"':'"$DEPLOY_DIRECTORY"'_tmp'
# */ <-- в оригинале строка не закоментирована =)
      - ssh -p 22 $DEPLOY_USER@$DEPLOY_HOST 'set -e ;
          cd '"$DEPLOY_DIRECTORY"'_tmp ;
          docker login -u gitlab-ci-token -p '"$CI_JOB_TOKEN"' '"$CI_REGISTRY"' ;
          docker-compose pull ;
          if [ -d '"$DEPLOY_DIRECTORY"' ]; then cd '"$DEPLOY_DIRECTORY"' && docker-compose
down --rmi local && rm -rf '"$DEPLOY_DIRECTORY"'; fi ;
          cp -r '"$DEPLOY_DIRECTORY"'_tmp '"$DEPLOY_DIRECTORY"' ;
          cd '"$DEPLOY_DIRECTORY"' ;
          docker-compose up -d --remove-orphans ;
          docker-compose exec -T php phing app-deploy -Dsymfony.env=prod ;
          rm -rf '"$DEPLOY_DIRECTORY"'_tmp'
    tags:
      - executor-docker


Даунтайм сейчас - это 1-2-3 минуты. Начинается он с docker-compose down ... и до
конца выполнения скрипта. Хочется его уменьшить.

А как сделать так, чтобы "мусорные" образы docker не появлялись - я вообще не понял.
Про docker image prune знаю, хочется не очищать, а не захламлять.

UPD1:

Файл docker-compose.yml создаётся следующей конструкцией:

.template-docker-compose: &docker-compose
    stage: build
    image: covex/docker-compose:1.0
    script:
      - for name in `env | awk -F= '{if($1 ~ /'"$ENV_SUFFIX"'$/) print $1}'`; do
          eval 'export '`echo $name|awk -F''"$ENV_SUFFIX"'$' '{print $1}'`'='$"$name"'';
        done
      - mkdir build
      - docker-compose -f docker-compose-deploy.yml config > build/docker-compose.yml
      - sed -i 's/\/builds\/'"$CI_PROJECT_NAMESPACE"'\/'"$CI_PROJECT_NAME"'/\./g'
build/docker-compose.yml
      - cp -R docker build
    artifacts:
        untracked: true
        name: "$CI_COMMIT_REF_NAME"
        paths:
          - build/
    tags:
      - executor-docker


В результате этой процедуры получается такой docker-compose.yml:

networks:
  nw_external:
    external:
      name: graynetwork
  nw_internal: {}
services:
  mysql:
    build:
      context: ./docker/mysql
    environment:
      MYSQL_DATABASE: project
      MYSQL_PASSWORD: project
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: project
    expose:
    - '3306'
    networks:
      nw_internal: null
    restart: always
    volumes:
    - database:/var/lib/mysql:rw
  nginx:
    build:
      args:
        app_php: app
        server_name: project-dev1.ru
      context: ./docker/nginx
    depends_on:
      php:
        condition: service_started
    networks:
      nw_external:
        ipv4_address: 192.168.10.13
      nw_internal: null
    ports:
    - 80/tcp
    restart: always
    volumes_from:
    - service:php:ro
  php:
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      ENV_database_host: mysql
      ENV_database_name: project
      ENV_database_password: project
      ENV_database_port: '3306'
      ENV_database_user: project
      ENV_mailer_from: andrey@mindubaev.ru
      ENV_mailer_host: 127.0.0.1
      ENV_mailer_password: 'null'
      ENV_mailer_transport: smtp
      ENV_mailer_user: 'null'
      ENV_secret: ThisTokenIsNotSoSecretChangeIt
    expose:
    - '9000'
    image: gitlab.site.ru:5005/dev1-projects/symfony:master
    networks:
      nw_internal: null
    restart: always
    volumes:
    - /composer/vendor
    - /srv
version: '2.1'
volumes:
  database: {}


Dockerfile для сервиса nginx

FROM nginx:alpine
ARG server_name=docker.local
ARG app_php=app_dev

COPY ./default.conf /etc/nginx/conf.d/default.conf

RUN sed -i 's/@SERVER_NAME@/'"$server_name"'/g' /etc/nginx/conf.d/default.conf \
    && sed -i 's/@APP@/'"$app_php"'/g' /etc/nginx/conf.d/default.conf


Dockerfile для сервиса mysql

FROM mysql:5.7

HEALTHCHECK CMD mysqladmin ping --silent

    


Ответы

Ответ 1



Нужно минимум два контейнера, которые будет zero downtime. Дальше процесс (хорошо описан тут): остановить один старый, запустиить один новый и так по очереди. Но я буквально пару недель назад все это проходил и docker swarm это ОЧЕНЬ ПРОСТО, там пару команд, рецепт по ссылке в несколько раз сложнее. Для своего решения нужно настраивать балансировщик, а в docker swarm уже все есть и ставиться, ещё раз скажу, ОЧЕНЬ ПРОСТО. Переходите сразу на docker swarm. Он очень быстро настраивается и нулевой downtime из коробки. Просто docker service отличный инструмент, а docker stack вообще бомба (как раз поднимает все оружение из docker-compose подобного файла).

Ответ 2



Я существенно уменьшил downtime при деплое изменений на удалённый сервер, используя совет @alexes! Теперь downtime длится где-то около 5 секунд. Точнее, 5 секунд длится перезагрузка контейнера nginx и проверка обновлений для контейнеров nginx и mysql. А т.к. эта операция по большому счёту не обязательная, то downtime можно избежать вообще. В данный момент на удалённый сервер копируется только файл docker-compose.yml, все сервисы в этом файле используют уже готовый образ image, вместо build. Эти образы подготавливаются до внедрения приложения на сервер. Вместо одного сервиса с php-кодом, сейчас используется два абсолютно одинаковых: php и spare. Также операции по подготовке кэша приложения Symfony (cache:warmup) и статичных файлов подключаемых модулей bundles (assets:install) сейчас производятся в процессе подготовки образа приложения. В итоге, процедура деплоя получилась следующей: Загружаем новые образы всего приложения Обновляем и перезапускаем контейнер spare Обновляем статичные файлы Вносим изменения в структуру БД Обновляем и перезапускаем контейнер php Перезапускаем контейнер nginx docker-compose pull docker-compose up -d --no-recreate docker-compose up -d --force-recreate --no-deps spare docker-compose exec -T spare sh -c "cd /srv && rm -rf b/* && cp -a web/. b/ && rm -rf a/* && cp -a web/. a/" docker-compose exec -T spare phing storage-prepare database-deploy docker-compose up -d --force-recreate --no-deps php docker-compose stop nginx docker-compose up -d nginx Эту процедуру можно запускать как для инициализации, так и для обновления приложения. Конфигурация nginx. Здесь оба контейнера включены в upstream. Когда один из них становится недоступным во время обновления, он временно исключается из upstream самим nginx. Конструкция try_files /a$uri /b$uri /web$uri /app.php$is_args$args; позволяет отдавать правильные статичные файлы во время деплоя. upstream backend { server php:9000 fail_timeout=5s; server spare:9000 fail_timeout=5s; } server { listen 80 default_server; root /srv/web; server_name site.ru; charset utf-8; location / { root /srv; try_files /a$uri /b$uri /web$uri /app.php$is_args$args; } location ~ ^/app\.php(/|$) { fastcgi_split_path_info ^(.+\.php)(/.*)$; fastcgi_pass backend; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /srv/web$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT /srv/web; internal; } location /upload/ { root /srv/storage; } location ~ \.php$ { return 404; } sendfile off; client_max_body_size 100m; error_log /var/log/nginx/error.log error; } Новый файл docker-compose.yml networks: nw_external: external: name: graynetwork nw_internal: {} services: mysql: environment: MYSQL_DATABASE: project MYSQL_PASSWORD: project MYSQL_ROOT_PASSWORD: root MYSQL_USER: project expose: - '3306' image: covex/mysql:5.7 networks: nw_internal: null restart: always volumes: - database:/var/lib/mysql:rw nginx: depends_on: mysql: condition: service_healthy image: gitlab.site.ru:5005/dev1-projects/symfony-workflow2/nginx:master networks: nw_external: ipv4_address: 192.168.10.13 nw_internal: null ports: - 80/tcp restart: always volumes: - assets:/srv/a:ro - assets:/srv/b:ro - assets:/srv/storage:ro php: environment: ENV_database_host: mysql ENV_database_mysql_version: '5.7' ENV_database_name: project ENV_database_password: project ENV_database_port: '3306' ENV_database_user: project ENV_mailer_from: andrey@mindubaev.ru ENV_mailer_host: 127.0.0.1 ENV_mailer_password: 'null' ENV_mailer_transport: smtp ENV_mailer_user: 'null' ENV_secret: ThisTokenIsNotSoSecretChangeIt image: gitlab.site.ru:5005/dev1-projects/symfony-workflow2:master networks: nw_internal: null restart: always volumes: - assets:/srv/a:rw - assets:/srv/b:rw - assets:/srv/storage:rw spare: environment: ENV_database_host: mysql ENV_database_mysql_version: '5.7' ENV_database_name: project ENV_database_password: project ENV_database_port: '3306' ENV_database_user: project ENV_mailer_from: andrey@mindubaev.ru ENV_mailer_host: 127.0.0.1 ENV_mailer_password: 'null' ENV_mailer_transport: smtp ENV_mailer_user: 'null' ENV_secret: ThisTokenIsNotSoSecretChangeIt image: gitlab.site.ru:5005/dev1-projects/symfony-workflow2:master networks: nw_internal: null restart: always volumes: - assets:/srv/a:rw - assets:/srv/b:rw - assets:/srv/storage:rw version: '2.1' volumes: assets: {} database: {}

Ответ 3



Я использую команду без downtime: docker-compose up -d --no-deps --build Пример: docker-compose up -d --no-deps --build nginx

Комментариев нет:

Отправить комментарий