Docker Swarm rolling updates
Docker Swarm rolling updates - это очень простой способ выполнять обновления без какого-либо простоя.
Некоторое время назад я писал, что лучше всего перейти на вариант Kubernetes , а теперь этот пост посвящен Docker Swarm. Да, я все еще использую Docker Swarm , потому что у меня есть проект, в котором он используется. Недавно я перенес разработку с Docker на Docker Swarm, в основном потому, что с Docker Swarm вы изучаете основы оркестровки контейнеров, так почему бы не изучить это во время разработки.
В этом посте мы рассмотрим rolling updates: переменную окружения и образ. Я предполагаю, что у вас уже есть некоторый практический опыт работы с Docker Swarm. Как обычно, я делаю это на Ubuntu 22.04.
Наш проект Docker-Compose
Сначала мы создадим проект Docker-Compose. Дерево проекта:
.
├── docker-compose.yml
├── .env
├── app
│ └── run.sh
Файлы:
# file: .env
COMPOSE_PROJECT_NAME=my-project
LOGGER_LEVEL=DEBUG
Мы используем образ busybox и начинаем с двух реплик. Мы также сделаем доступными некоторые переменные, специфичные для Docker Swarm :
- X_SERVICE_LABEL_STACK_IMAGE: информация об образе.
- X_TASK_SLOT: номер экземпляра (задачи).
# file: docker-compose.yml
version: "3.7"
x-service_defaults: &service_defaults
env_file:
- ./.env
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
busybox:
<< : *service_defaults
deploy:
mode: replicated
replicas: 2
restart_policy:
condition: on-failure
image: busybox:1.35.0
environment:
# swarm info
X_COMPOSE_PROJECT_NAME: "${COMPOSE_PROJECT_NAME}"
X_SERVICE_LABEL_STACK_IMAGE: '{{index .Service.Labels "com.docker.stack.image"}}'
X_TASK_SLOT: "{{.Task.Slot}}"
ports:
- "127.0.0.1:8280:8280"
volumes:
- "./app:/app"
command: /bin/sh /app/run.sh
networks:
- my-project-network
networks:
my-project-network:
external: true
name: my-project-network
Мы используем скрипт 'run.sh', вызываемый при запуске контейнера, который делает две вещи:
- Запускает httpd server в фоновом режиме.
- В цикле генерируются строки журнала, которые печатаются в stdout.
# file: app/run.sh
echo "Starting httpd server instance ${X_TASK_SLOT} ..."
echo "Hello from httpd server instance ${X_TASK_SLOT}" > /var/www/index.html
/bin/httpd -f -p 8280 -h /var/www/ &
echo "Starting output ..."
while true; do echo "IMAGE: ${X_SERVICE_LABEL_STACK_IMAGE}, LOGGER_LEVEL = ${LOGGER_LEVEL}"; sleep 1; done
В Docker Swarm мы обычно не создаем сети в 'docker-compose.yml', а используем внешние сети, а точнее сети 'overlay' . При создании такой сети мы также можем указать флаг, позволяющий подключаться к ней контейнерам, не являющимся Docker Swarm .
Чтобы создать сеть:
docker network create -d overlay --attachable my-project-network
Чтобы увидеть эту сеть:
docker network ls
Результат:
NETWORK ID NAME DRIVER SCOPE
...
qn7qwhpsooty my-project-network overlay swarm
...
Некоторые команды Docker Swarm
Замечание о командах. Много раз мы используем '--detach=false'. Это означает, что команда не возвращается сразу, а возвращается по завершении. В это время в терминале отображается полезная информация.
Вызовем наш проект, уродливая конструкция используется для передачи переменных окружения:
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Result:
WARN[0000] ignoring IP-address (127.0.0.1:8280:8280/tcp) service will listen on '0.0.0.0'
Creating service my-project_busybox
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service vflo2g4fiybtx0p9b596uk445 converged
Обратите внимание на предупреждение. Это неожиданно и отличается от Docker и означает, что с Docker Swarm мы создаем открытый порт, будьте осторожны!
Чтобы удалить наш проект, мы можем использовать:
docker stack rm --detach=false my-project
Показать сервисы стека:
docker stack services my-project
Результат:
ID NAME MODE REPLICAS IMAGE PORTS
2oz3yg39zuvx my-project_busybox replicated 2/2 busybox:1.35.0 *:8280->8280/tcp
Показать задачи службы 'my-project_busybox':
docker service ps my-project_busybox
Результат:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
v1pc5p8yb2fx my-project_busybox.1 busybox:1.35.0 myra Running Running about a minute ago
6ozvx31c6isq my-project_busybox.2 busybox:1.35.0 myra Running Running about a minute ago
Проверьте журналы, для каждой задачи каждую секунду новая строка журнала:
docker service logs -t -f my-project_busybox
Результат:
...
2024-07-07T15:42:20.805354434Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:21.808005147Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:22.807919531Z my-project_busybox.1.v1pc5p8yb2fx@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:22.809102067Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:23.808999822Z my-project_busybox.1.v1pc5p8yb2fx@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:23.809973729Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
В журнале мы видим, что обе задачи запущены.
Чтобы проверить httpd server, запускаем на нашем хосте:
curl 127.0.0.1:8280
Результат:
Hello from httpd server instance 1
Если повторить это несколько раз:
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Результат :
...
Hello from httpd server instance 2
Hello from httpd server instance 1
Hello from httpd server instance 2
Hello from httpd server instance 1
Здесь мы видим, что балансировщик нагрузки Docker Swarm чередует запросы к обоим экземплярам.
Наконец, давайте проверим работу службы:
docker service inspect --pretty my-project_busybox
Результат:
ID: 2oz3yg39zuvxyl2k4hc77qsic
Name: my-project_busybox
Labels:
com.docker.stack.image=busybox:1.35.0
com.docker.stack.namespace=my-project
Service Mode: Replicated
Replicas: 2
Placement:
UpdateConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
...
Обратите внимание на параметр 'UpdateConfig' 'Parallelism' . Значение 1 означает, что процесс обновления сначала обновит одну задачу, а после ее завершения обновит следующую задачу. Этот же параметр присутствует и в 'RollbackConfig'.
Масштабирование путем добавления реплик
Пока что у нашего сервиса две реплики. Если мы выполним rolling update только с одной задачей, то наш сервис будет временно недоступен. Это не то, чего мы хотим. При наличии двух задач Docker Swarm может обновить одну задачу и после обновления этой задачи обновить вторую задачу. Это означает, что наш сервис остается доступным все время.
Чтобы добавить больше реплик, например 3:
docker service scale my-project_busybox=3
Проверка rolling updates
Чтобы проверить, обновлен ли наш сервис, мы можем проверить журнал сервиса. В нем будет показано, какие задачи запущены и когда задача была перезапущена.
docker service logs -t -f my-project_busybox
Чтобы убедиться, что наш сервис не прерывается в процессе обновления, мы можем проверить httpd server в отдельном терминале, в "бесконечном" цикле, как упоминалось ранее. Мы не должны видеть никаких прерываний:
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Rolling updates и откаты
Почему мы называем это "Rolling update"? Потому что мы передаем инструкцию обновления с новыми данными в Docker Swarm и просим его выполнить обновление.
Ниже приведены два сценария обновления службы:
- Обновить переменную окружения службы
- Обновить образ службы.
Команда обновления:
docker service update <parameters> my-project_busybox
Тип обновления задается параметрами.
Поскольку обновление может завершиться неудачей, мы хотим иметь возможность вернуться к предыдущей версии. Команда отката в обоих случаях выглядит следующим образом:
docker service rollback my-project_busybox
1. Rolling update: переменная окружения
Здесь мы изменяем переменную 'LOGGER_LEVEL' нашего приложения. Изначально 'LOGGER_LEVEL' загружается из файла '.env ' и имеет значение 'DEBUG'. Мы изменим его на 'WARNING' с помощью следующей команды update:
docker service update --env-add LOGGER_LEVEL=WARNING my-project_busybox
Результат:
my-project_busybox
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service my-project_busybox converged
Во время обновления в журнале обслуживания отображается следующее:
...
2024-07-07T16:35:36.177525585Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:37.178678148Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:37.528564504Z my-project_busybox.2.yna9ftex6bau@myra | Starting httpd server instance 2 ...
2024-07-07T16:35:37.528847322Z my-project_busybox.2.yna9ftex6bau@myra | Starting output ...
2024-07-07T16:35:37.529281987Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:38.180094076Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:49.542707071Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:50.194103057Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:52.132182215Z my-project_busybox.1.qrqsbiowltle@myra | Starting httpd server instance 1 ...
2024-07-07T16:35:52.132401060Z my-project_busybox.1.qrqsbiowltle@myra | Starting output ...
2024-07-07T16:35:52.132788443Z my-project_busybox.1.qrqsbiowltle@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:52.546112046Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
Теперь давайте откатимся:
docker service rollback my-project_busybox
Результат:
my-project_busybox
rollback: manually requested rollback
overall progress: rolling back update: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service my-project_busybox converged
После операции отката значение 'LOG_LEVEL' вернулось к значению 'DEBUG', проверьте журнал обслуживания.
2. Rolling update: Изображение
В другом сценарии у нас есть новый образ для нашего приложения. Здесь мы переходим с busybox:1.35.0 на busybox:1.36.0. Команда обновления:
docker service update --image busybox:1.36.0 my-project_busybox
И, опять же, команда отката:
docker service rollback my-project_busybox
Что, если во время обновления что-то пойдет не так?
Давайте ошибемся и обновим несуществующий образ:
docker service update --image busybox:9.99.0 my-project_busybox
Результат:
image busybox:9.99.0 could not be accessed on a registry to record
its digest. Each node will access busybox:9.99.0 independently,
possibly leading to different nodes running different
versions of the image.
my-project_busybox
overall progress: 0 out of 2 tasks
1/2: preparing [=================================> ]
2/2:
service update paused: update paused due to failure or early termination of task n371geu35a4u5xe9oclefv5j9
Когда обновление задачи не удается, процесс обновления завершается. Другая задача продолжает выполняться, а это значит, что наш сервис по-прежнему доступен. Мы также можем убедиться в этом, осмотрев службу:
docker service inspect --pretty my-project_busybox
Результат:
ID: k4a0vy77wirk1fglso42qxx38
Name: my-project_busybox
Labels:
com.docker.stack.image=busybox:1.35.0
com.docker.stack.namespace=my-project
Service Mode: Replicated
Replicas: 2
UpdateStatus:
State: paused
Started: 2 minutes ago
Message: update paused due to failure or early termination of task n371geu35a4u5xe9oclefv5j9
Placement:
...
Как уже говорилось, мы можем вернуться к состоянию до обновления, выполнив откат:
docker service rollback my-project_busybox
Обновление сервиса с помощью 'docker stack deploy' и 'docker-compose.yml'
Теперь все становится немного некрасиво. До сих пор мы обновляли наши сервисы с помощью 'docker service update', и мы могли вернуться к предыдущей версии, выполнив команду 'docker service rollback'.
Но здесь мы используем файл 'docker-compose.yml'. Похоже, что можно изменить файл 'docker-compose.yml' и развернуть его снова.
Давайте посмотрим, что произойдет. Сначала мы изменим переменную окружения в файле '.env ' и тег image в файле 'docker-compose.yml'. Затем мы снова выполняем команду deploy:
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Результат:
Updating service my-project_busybox (id: mi27j7jjsz146y4wqqre439io)
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service mi27j7jjsz146y4wqqre439io converged
Обратите внимание, что в результате теперь упоминается 'Updating service', в то время как в первый раз упоминалось 'Creating service'.
В любом случае, это означает, что Docker Swarm выполняет обновление (без времени простоя) для каждого сервиса в файле 'docker-compose.yml', точно так же, как мы обновляем отдельные сервисы с помощью 'docker service update'. Это здорово!
Но все может пойти не так, а команды 'docker stack rollback' не существует. Это означает, что если что-то пойдет не так, мы можем вернуться к предыдущей версии, восстановив наш предыдущий файл 'docker-compose.yml' и снова развернуть!
Резюме
Выполнение обновлений без простоя стало очень важным. Docker Swarm делает управление и запуск обновлений простым делом. Docker Swarm был и остается чрезвычайно мощным инструментом оркестровки контейнеров, даже несмотря на то, что его развитие, похоже, остановилось.
Ссылки / кредиты
Docker - Apply rolling updates to a service
https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update
docker stack deploy in 1.13 doesn't load .env file as docker-compose up does #29133
https://github.com/moby/moby/issues/29133
Подробнее
Docker Docker Swarm Docker-compose
Недавний
- Использование Ingress для доступа к RabbitMQ на кластере Microk8s
- Простая видеогалерея с Flask, Jinja, Bootstrap и JQuery
- Базовое планирование заданий с помощью APScheduler
- Коммутатор базы данных с HAProxy и HAProxy Runtime API
- Docker Swarm rolling updates
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
Большинство просмотренных
- Использование PyInstaller и Cython для создания исполняемого файла Python
- Уменьшение времени отклика на запросы на странице Flask SQLAlchemy веб-сайта
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Подключение к службе на хосте Docker из контейнера Docker
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов