Docker Swarm: stack deploy и именованные тома (named volumes)
Sep 18, 2017 14:53 · 873 words · 5 minute read
При переходе на 3.х версию файла docker-compose.yml
(необходимое требование для работы Docker Swarm) пропадает возможность использовать Data-only контейнеры — специальные контейнеры-спутники, файловая система которых служит для хранения данных и подключается к остальным сервисам с помощью параметра volumes-from
.
Давайте разберемся с возможным решением данной проблемы на реальном примере!
В примере используются сервисы [docker-контейнеры] nginx
(в качестве фронтенда) и php-fpm
(бэкенд), которым необходим доступ к одному и тому же каталогу с кодом. Используя docker-compose.yml
второй версии, мы вправе были написать так:
version: '2'
services:
### Applications Code Container #############################
applications:
container_name: application
image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
### PHP-FPM Container #######################################
php-fpm:
container_name: php-fpm
image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
volumes_from:
- applications
expose:
- "9000"
### Nginx Server Container ##################################
nginx:
container_name: nginx
image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:latest
volumes_from:
- applications
depends_on:
- "php-fpm"
ports:
- "80:80"
- "443:443"
Dockerfile для сборки data-only контейнера (application
) в процессе CI выглядит так (BRANCH_NAME
— имя git-ветки, для которой выполняется CI):
FROM registry.gitlab.lc:5000/develop/ed/image_for_sources:latest
ARG BRANCH_NAME=
COPY ./bin /var/www/${BRANCH_NAME}/bin
COPY ./build /var/www/${BRANCH_NAME}/build
COPY ./config /var/www/${BRANCH_NAME}/config
COPY ./data /var/www/${BRANCH_NAME}/data
COPY ./.env /var/www/${BRANCH_NAME}/
COPY ./grunt /var/www/${BRANCH_NAME}/grunt
COPY ./init_autoloader.php /var/www/${BRANCH_NAME}/
COPY ./library /var/www/${BRANCH_NAME}/library
COPY ./module /var/www/${BRANCH_NAME}/module
COPY ./postcss.config.js /var/www/${BRANCH_NAME}/
COPY ./public /var/www/${BRANCH_NAME}/public
COPY ./vendor /var/www/${BRANCH_NAME}/vendor
COPY ./zf /var/www/${BRANCH_NAME}/
VOLUME /var/www/${BRANCH_NAME}
При изменении data-only контейнера для обновления содержимого каталога /var/www/${BRANCH_NAME}
в контейнерах nginx
и php-fpm
необходимо выполнить команды:
docker-compose down && docker compose up -d
которые неизменно повлекут за собой простой (downtime) ~10-15 секунд (если контейнеров больше, чем в данном примере, то время простоя также возрастает).
Первым этапом на пути к Docker Swarm стало изменение файла docker-compose.yml
(перевод его на третью версию с использованием именованных томов):
version: '3'
services:
### Code from branch develop #################################
applications:
container_name: application
image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
volumes:
- developcode:/var/www/develop
### PHP-FPM ##################################################
php-fpm:
container_name: php-fpm
image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
volumes:
- developcode:/var/www/develop
expose:
- "9000"
### Nginx ####################################################
nginx:
container_name: nginx
image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:latest
volumes:
- developcode:/var/www/develop
ports:
- "80:80"
- "443:443"
### Volumes Setup ############################################
volumes:
developcode:
Однако в этом случае возникли проблемы с обновлением содержимого каталога /var/www/develop
в контейнерах nginx
и php-fpm
. Дело в том, что при первом выполнении команды docker-compose up -d
создается пустой именованный том developcode
, и, так как он пустой, в него копируется содержимое каталога /var/www/develop
из контейнера application
. При повторном запуске docker-compose up -d
именованный том уже существует, и он не пустой (содержит код предыдущего «релиза»), следовательно содержимое тома developcode
будет скопировано в каталоги /var/www/develop
контейнеров application
, nginx
и php-fpm
.
Решение данной проблемы находится быстро, но добавляет еще ~5-10 секунд к времени простоя: нужно выполнять команду docker-compose down
с дополнительным ключом --volumes
или -v
. Таким образом, именованные тома будут удалены, а при следующем вызове docker-compose up -d
вновь создастся пустой том, в который скопируются файлы из data-only контейнера.
Так как наща цель избавиться от времени простоя, то нужно использовать для развертывания/обновления стека сервисов команду docker stack deploy
. Однако здесь мы также сталкиваемся с той же проблемой — содержимое каталога /var/www/develop
в контейнерах nginx
и php-fpm
не обновляется. Использование связки docker stack rm ... && docker stack deploy ...
нам не подходит — во-первых, это не исключает даунтайма, во-вторых именованные тома не удаляются (и дополнительных ключей для этого нет), а значит код не будет обновлен.
После множества вопросов заданных с docker-сообществах и переписках с признанными docker-капитанами (Elton Stoneman и Bret Fisher) мне предложили отказаться от использования томов, переделать процесс CI и сразу добавлять код в оба контейнера nginx
и php-fpm
— мол, это лучшие практики использования docker.
Я уже почти согласился с данным предложением (хотя мне откровенно не нравилось добавлять 337MB «обвески» контейнерам nginx
и php-fpm
и пересобирать их по 15-25 раз в день) но решил попробовать реализовать еще один «велосипед» с использованием переменных в именах томов.
Первая же попытка в таком виде:
version: '3'
services:
### Code from branch develop #################################
applications:
image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
volumes:
- ${VOLUME_NAME}:/var/www/develop
### PHP-FPM ##################################################
php-fpm:
image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
volumes:
- ${VOLUME_NAME}:/var/www/develop
### Nginx ####################################################
nginx:
image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:latest
volumes:
- ${VOLUME_NAME}:/var/www/develop
ports:
- "80:80"
- "443:443"
### Volumes Setup ############################################
volumes:
${VOLUME_NAME}:
привела к неудаче:
...
volumes value Additional properties are not allowed ('${VOLUME_NAME}' was unexpected)
...
Как оказалось, это нормальное поведение — docker-compose умеет работать в переменными в значениях, но не в ключах (подробности). И вообще, как оказалось использование переменных в режиме роя (Swarm) с командой stack deploy
это целая эпопея.
Но ведь никто нам не запрещает использовать external
тома! Теперь наш docker-compose.yml
полностью готов к развертыванию через stack deploy
и выглядит так:
version: '3.1'
services:
### Code from branch develop #################################
applications:
image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
volumes:
- developcode:/var/www/develop
deploy:
replicas: 1
update_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
placement:
constraints: [node.role == manager]
### PHP-FPM ##################################################
php-fpm:
image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
volumes:
- developcode:/var/www/develop
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
placement:
constraints: [node.role == manager]
### Nginx ####################################################
nginx:
image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:staging
volumes:
- developcode:/var/www/develop
ports:
- "80:80"
- "443:443"
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
placement:
constraints: [node.role == manager]
### Volumes Setup ############################################
volumes:
developcode:
external:
name: code-${VER}
Процесс деплоя сервисов усложнился на одну команду (создание именованного тома с определенным именем) и выглядит так:
export VER=1.1 && docker volume create --name code-$VER
docker stack deploy --with-registry-auth --compose-file docker-compose.yml MY_STACK
Стоит отметить, что данное решение пока не тестировалось на продакшене (да и вообще, кластер Docker Swarm в данном примере состоял из одной ноды) — это просто демонстрация возможности использования data-only контейнеров.