angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Docker Swarm rolling updates

Docker Swarm rolling updates - это очень простой способ выполнять обновления без какого-либо простоя.

7 июля 2024
post main image
https://unsplash.com/@coinstash_au

Некоторое время назад я писал, что лучше всего перейти на вариант 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 и просим его выполнить обновление.

Ниже приведены два сценария обновления службы:

  1. Обновить переменную окружения службы
  2. Обновить образ службы.

Команда обновления:

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

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

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.