Ускорение развертывания программного обеспечения — руководство по Docker Swarm

Опубликовано: 2022-03-11

Если вы не жили внутри транспортного контейнера, вы, вероятно, слышали о контейнерах. Отрасль делает явный переход от постоянной к эфемерной инфраструктуре, и контейнеры занимают центральное место в этом движении. Причина довольно проста: хотя контейнеры, безусловно, помогают командам разработчиков быстро приступить к работе, они обладают еще большим потенциалом для полного изменения характера операций.

Но на что именно это похоже? Что произойдет, когда вы будете готовы запустить контейнеры локально или вручную на нескольких серверах? В идеальном мире вы хотите просто бросить свое приложение на кластер серверов и сказать «запустите его!»

Ну, к счастью, это то, где мы находимся сегодня.

В этой статье мы рассмотрим, что такое Docker Swarm, а также некоторые замечательные функции, которые он может предложить. Затем мы взглянем на то, как на самом деле выглядит использование режима Swarm и развертывание в рой, и в заключение мы приведем несколько примеров того, на что похожи ежедневные операции с развернутым роем. Определенно рекомендуется иметь базовые знания о Docker и контейнерах, но вы можете сначала ознакомиться с этой отличной записью в блоге, если вы новичок в контейнерах.

Что такое Docker Swarm?

Логотип режима Docker Swarm

Прежде чем мы углубимся в создание и развертывание нашего первого роя, полезно иметь представление о том, что такое Docker Swarm. Сам Docker существует уже много лет, и большинство людей сегодня думают о нем как о среде выполнения контейнера. Однако на самом деле Docker состоит из множества разных частей, работающих вместе. Например, эта часть среды выполнения контейнера обрабатывается двумя меньшими компонентами, называемыми runC и containerd. По мере того, как Docker развивался и возвращался сообществу, они обнаружили, что создание этих небольших компонентов — лучший способ расти и быстро добавлять функции. Таким образом, теперь у нас есть SwarmKit и режим Swarm, встроенный прямо в Docker.

Docker Swarm — это механизм оркестрации контейнеров. На высоком уровне требуется несколько Docker Engine, работающих на разных хостах, и вы можете использовать их вместе. Использование простое: объявите свои приложения как стеки сервисов, и пусть Docker сделает все остальное. Службы могут быть чем угодно, от экземпляров приложений до баз данных или утилит, таких как Redis или RabbitMQ. Это должно показаться знакомым, если вы когда-либо работали с docker-compose в процессе разработки, так как это точно такая же концепция. На самом деле объявление стека — это просто файл docker-compose.yml с синтаксисом версии 3.1. Это означает, что вы можете использовать аналогичную (и во многих случаях идентичную) конфигурацию compose для разработки и развертывания swarm, но здесь я немного забегаю вперед. Что происходит, когда у вас есть экземпляры Docker в режиме Swarm?

В режиме Docker Swarm службы группируются в стеки.

Не упасть с плота

У нас есть два типа узлов (серверов) в мире Swarm: менеджеры и рабочие. Важно иметь в виду, что менеджеры также являются работниками, просто на них лежит дополнительная ответственность за то, чтобы все работало. Каждый рой начинается с одного управляющего узла, назначенного лидером. Оттуда достаточно запустить одну команду, чтобы безопасно добавить узлы в рой.

Swarm отличается высокой доступностью благодаря реализации алгоритма Raft. Я не буду вдаваться в подробности о Raft, потому что уже есть отличный учебник о том, как он работает, но вот общая идея: ведущий узел постоянно связывается со своими коллегами-управляющими узлами и синхронизирует их состояния. Чтобы изменение состояния было «принято», узлы-менеджеры в значительной степени достигают консенсуса, что происходит, когда большинство узлов подтверждают изменение состояния.

Прелесть этого в том, что узлы-менеджеры могут время от времени отключаться без ущерба для консенсуса роя. Если изменение состояния достигает консенсуса, мы знаем, что оно гарантированно существует на большинстве управляющих узлов и будет сохраняться, даже если текущий лидер выйдет из строя.

Допустим, у нас есть три управляющих узла с именами A, B и C. Конечно, A — наш бесстрашный лидер. Что ж, однажды временная сетевая ошибка отключает A от сети, оставляя B и C в одиночестве. Не имея никаких известий от A в течение длительного времени (несколько сотен миллисекунд), B и C ждут случайно сгенерированный период времени, прежде чем выставить себя на выборы и уведомить другого. Конечно, тот, кто первым пойдет на выборы, в этом случае будет избран. В этом примере B становится новым лидером, и кворум восстанавливается. Но затем поворот сюжета: что произойдет, когда А вернется в сеть? Он будет думать, что он все еще лидер, верно? Каждые выборы имеют связанный с ними срок, поэтому A фактически был избран в срок 1. Как только A вернется в сеть и начнет командовать B и C, они любезно сообщат ему, что B является лидером срока 2, и А уйдет в отставку.

Конечно, этот же процесс работает в гораздо большем масштабе. У вас может быть гораздо больше, чем три узла менеджера. Я добавлю еще одну быструю заметку, хотя. Каждый Рой может выдержать только определенное количество потерь менеджеров. Рой из n узлов менеджеров может потерять (n-1)/2 менеджеров без потери кворума. Это означает, что для группы из трех менеджеров вы можете потерять одного, для пяти вы можете потерять двух и т. д. Основная причина этого связана с идеей о консенсусе большинства, и об этом определенно следует помнить, когда вы переходите к производству.

Планирование задач и согласование

На данный момент мы установили, что наши менеджеры действительно хорошо синхронизируются. Здорово! Но что они делают на самом деле? Помните, я говорил, что вы развертываете стек сервисов в Swarm? Когда вы объявляете свои сервисы, вы предоставляете Swarm важную информацию о том, как вы на самом деле хотите, чтобы ваши сервисы работали. Сюда входят такие параметры, как количество реплик каждой службы, способ их распределения, должны ли они запускаться только на определенных узлах и т. д.

После развертывания службы задача менеджеров состоит в том, чтобы убедиться, что любые установленные вами требования к развертыванию продолжают выполняться. Допустим, вы развертываете службу Nginx и указываете, что реплик должно быть три. Менеджеры увидят, что ни один контейнер не запущен, и равномерно распределит три контейнера по доступным узлам.

Что еще круче, так это то, что если контейнер выйдет из строя (или весь узел отключится), Swarm автоматически создаст контейнеры на оставшихся узлах, чтобы компенсировать разницу. Если вы говорите, что хотите запустить три контейнера, у вас будет три работающих контейнера, а Swarm обрабатывает все мельчайшие детали. Плюс — и это большой плюс — масштабирование вверх или вниз так же просто, как дать Swarm новую настройку репликации.

Обнаружение сервисов и балансировка нагрузки

Я хочу указать на важную, но тонкую деталь из последнего примера: если Swarm разумно запускает контейнеры на узлах по своему выбору, мы не обязательно знаем, где эти контейнеры будут работать. Поначалу это может показаться пугающим, но на самом деле это одна из самых мощных функций Swarm.

Продолжая тот же пример Nginx, представьте, что мы сказали Docker, что эти контейнеры должны открывать порт 80. Если вы укажете в своем браузере узел, на котором запущен этот контейнер на порту 80, вы увидите содержимое этого контейнера. Там нет ничего удивительного. Что может быть удивительным, так это то, что если вы отправите свой запрос на узел, на котором не работает этот контейнер, вы все равно увидите тот же контент! Что тут происходит?

Swarm фактически использует входную сеть для отправки вашего запроса на доступный узел, на котором запущен этот контейнер, и одновременно балансирует нагрузку. Поэтому, если вы сделаете три запроса к одному и тому же узлу, вы, скорее всего, попадете в три разных контейнера. Если вы знаете IP-адрес одного узла в рое, вы можете получить доступ ко всему, что в нем работает. И наоборот, это позволяет вам направить балансировщик нагрузки (например, ELB) на все узлы в рое, не беспокоясь о том, что и где выполняется.

Это не останавливается на внешних соединениях. Службы, работающие в одном стеке, имеют оверлейную сеть, которая позволяет им взаимодействовать друг с другом. Вместо того, чтобы жестко кодировать IP-адреса в своем коде, вы можете просто использовать имя службы в качестве имени хоста, к которому вы хотите подключиться. Например, если вашему приложению необходимо взаимодействовать со службой Redis с именем «redis», оно может просто использовать «redis» в качестве имени хоста, и Swarm направит свой запрос в соответствующий контейнер. А поскольку это без проблем работает в разработке с помощью docker-compose и в производстве с помощью Docker Swarm, при развертывании приложения вам не о чем беспокоиться.

Сетка маршрутизации режима Docker Swarm направляет запросы в соответствующие контейнеры, даже если они выполняются на разных узлах.

Последовательные обновления

Если вы работаете в операционной, вы, вероятно, испытали паническую атаку, когда производственное обновление пошло не так, как надо. Это может быть плохое обновление кода или даже просто ошибка конфигурации, но вдруг производство останавливается! Скорее всего, боссу все равно. Они просто узнают, что это твоя вина. Что ж, не волнуйтесь, Swarm тоже поддерживает вас.

При обновлении службы вы можете указать, сколько контейнеров следует обновлять за раз и что должно произойти, если новые контейнеры начнут выходить из строя. После определенного порога Swarm может либо остановить обновление, либо (начиная с Docker 17.04) откатить контейнеры к предыдущему образу и настройкам. Не беспокойтесь о том, что завтра утром вам придется принести вашему боссу кофе.

Безопасность

И последнее, но не менее важное: Docker Swarm поставляется с отличными функциями безопасности из коробки. Когда узел присоединяется к рою, он использует токен, который не только проверяет себя, но и подтверждает, что он присоединяется к рою, которым вы его считаете. С этого момента вся связь между узлами происходит с использованием взаимного шифрования TLS. Все это шифрование обеспечивается и управляется Swarm автоматически, поэтому вам не нужно беспокоиться об обновлении сертификатов и других типичных проблемах безопасности. И, конечно же, если вы хотите принудительно повернуть ключ, для этого есть команда.

Docker Swarm по умолчанию безопасен.

Последняя версия Docker Swarm также имеет встроенное управление секретами. Это позволяет безопасно развертывать секреты, такие как ключи и пароли, в тех службах, которые в них нуждаются, и только в тех службах, которым они нужны. Когда вы предоставляете службе секрет, контейнеры для этой службы будут иметь специальный файл, смонтированный в их файловой системе, который включает значение секрета. Само собой разумеется, но это намного безопаснее, чем использование переменных среды, которые были традиционным подходом.

Погружение в рой

Если вы чем-то похожи на меня, вам не терпится опробовать все эти функции! Итак, без лишних слов, давайте погрузимся!

Пример приложения Docker Swarm

Я создал очень примитивное приложение Flask, чтобы продемонстрировать мощь и простоту использования Docker Swarm. Веб-приложение просто отображает страницу, на которой сообщается, какой контейнер обслужил ваш запрос, сколько всего запросов было обслужено и каков «секретный» пароль базы данных.

Он разбит на три службы: собственно приложение Flask, обратный прокси-сервер Nginx и хранилище ключей Redis. При каждом запросе приложение увеличивает ключ num_requests в Redis, поэтому независимо от того, какой экземпляр Flask вы нажимаете, вы увидите правильное количество отраженных запросов.

Весь исходный код доступен на GitHub, если вы хотите «проверить», что происходит.

Играй с Докером!

Не стесняйтесь использовать свои собственные серверы во время прохождения этого руководства, но я настоятельно рекомендую использовать play-with-docker.com, если вы хотите просто прыгнуть. Это сайт, управляемый несколькими разработчиками Docker, который позволяет вам запустить несколько сетевых серверов. узлы с предустановленным Docker. Они будут отключены через четыре часа, но для этого примера этого достаточно!

Создание роя

Хорошо, поехали! Идите вперед и создайте три экземпляра в PWD (play-with-docker) или разверните три сервера в своем любимом сервисе VPS (виртуальный частный сервер) и установите движок Docker на все из них. Имейте в виду, что вы всегда можете создать образ и повторно использовать его при добавлении узлов в будущем. С точки зрения программного обеспечения нет никакой разницы между узлом менеджера и рабочим узлом, поэтому вам не нужно поддерживать два разных образа.

Все еще раскручивается? Не волнуйся, я подожду. Хорошо, теперь мы собираемся создать наш первый управляющий и лидерный узел. В вашем первом экземпляре инициализируйте рой:

 docker swarm init --advertise-addr <node ip here>

Замените <node_ip_here> на IP-адрес вашего узла. В PWD IP-адрес отображается вверху, и если вы используете свой собственный VPS, не стесняйтесь использовать частный IP-адрес вашего сервера, если он доступен с других узлов в вашей сети.

Теперь у вас есть рой! Однако это довольно скучный рой, так как он имеет только один узел. Давайте продолжим и добавим другие узлы. Вы заметите, что при запуске init отображается длинное сообщение, объясняющее, как использовать токен присоединения. Мы не собираемся использовать его, потому что это заставит другие узлы работать, а мы хотим, чтобы они были менеджерами. Давайте получим токен присоединения для менеджера, запустив это на первом узле:

 docker swarm join-token manager

Скопируйте полученную команду и запустите ее на втором и третьем узлах. Вот, рой из трех узлов! Давайте проверим, что все наши узлы действительно существуют. Команда docker node ls выведет список всех узлов в нашем рое. Вы должны увидеть что-то вроде этого:

 $ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable

Обратите внимание, что наш первый узел имеет звездочку рядом с идентификатором. Это просто говорит нам, что это узел, к которому мы в данный момент подключены. Мы также можем видеть, что этот узел в настоящее время является лидером, а другие узлы доступны, если с ним что-то случится.

Найдите минутку, чтобы оценить, насколько это было просто, и давайте развернем наше первое приложение!

Отправим его!

Вот только команда по развитию бизнеса пообещала клиенту, что их новое приложение будет развернуто и готово в течение часа! Типично, я знаю. Но не бойтесь, нам не понадобится столько времени, так как он был создан с использованием Docker! Разработчики любезно предоставили нам свой файл docker-compose :

 version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true

Мы разберем его через минуту, но пока нет на это времени. Давайте его развернуть! Идите вперед и создайте файл на своем первом узле с именем docker-compose.yml и заполните его приведенной выше конфигурацией. Вы можете легко сделать это с помощью echo "<pasted contents here>" > docker-compose.yml .

Обычно мы могли бы просто развернуть это, но в нашей конфигурации упоминается, что мы используем секрет с именем db_password , поэтому давайте быстро создадим этот секрет:

 echo "supersecretpassword" | docker secret create db_password -

Здорово! Теперь все, что нам нужно сделать, это указать Docker использовать нашу конфигурацию:

 docker stack deploy -c docker-compose.yml demo

Когда вы запустите эту команду, вы увидите, что Docker создает три определенных нами сервиса: web , nginx и redis . Однако, поскольку мы назвали наш стек demo, наши сервисы на самом деле называются demo_web , demo_nginx и demo_redis . Мы можем посмотреть на наши запущенные службы, выполнив команду docker service ls , которая должна показать что-то вроде этого:

 $ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest

Вуаля! Docker загрузил наши образы на соответствующие узлы и создал контейнеры для наших сервисов. Если ваши реплики еще не загружены на полную мощность, подождите немного и проверьте еще раз. Докер, вероятно, все еще загружает изображения.

Видеть значит верить

Однако не верьте мне на слово (или слову Докера). Попробуем подключиться к нашему приложению. Конфигурация нашей службы сообщает Docker о том, что NGINX должен открывать порт 8000. Если вы используете PWD, в верхней части страницы должна быть синяя ссылка с надписью «8000». PWD фактически автоматически обнаружил, что на этом порту запущена служба! Нажмите на нее, и он направит вас к выбранному узлу на порту 8000. Если вы развернули свои собственные серверы, просто перейдите к одному из IP-адресов ваших серверов на порту 8000.

Вас встретит красиво оформленный экран с базовой информацией:

Контент, обслуживаемый созданным стеком

Запишите, какой контейнер обслуживал ваш запрос, а затем обновите страницу. Скорее всего, он изменился. Но почему? Ну, мы сказали Docker создать две реплики нашего приложения Flask, и он распределяет запросы на оба этих экземпляра. Вы случайно попали в другой контейнер во второй раз. Вы также заметите, что количество запросов увеличилось, потому что оба контейнера Flask взаимодействуют с одним указанным нами экземпляром Redis.

Не стесняйтесь попробовать подключиться к порту 8000 с любого узла. Вы по-прежнему будете правильно перенаправлены в приложение.

Демистификация магии

На данный момент все работает, и, надеюсь, вы нашли этот процесс безболезненным! Давайте подробнее рассмотрим этот файл docker-compose.yml и посмотрим, что мы на самом деле сказали Docker. На высоком уровне мы видим, что определили три службы: web , nginx и redis . Как и в случае с обычным файлом компоновки, мы предоставили Docker образ для использования для каждой службы, а также команду для запуска. В случае nginx мы также указали, что порт 8000 на хосте должен сопоставляться с портом 80 в контейнере. Пока все это стандартный синтаксис компоновки.

Новым здесь являются ключи развертывания и секреты. Эти ключи игнорируются docker-compose , поэтому они не повлияют на вашу среду разработки, но используются docker stack . Давайте посмотрим на веб-сервис. Достаточно просто: мы сообщаем Docker, что хотим запустить две копии нашего приложения Flask. Мы также сообщаем Docker, что веб-службе требуется секрет db_password . Именно это гарантирует, что в контейнере будет файл с именем /run/secrets/db_password , содержащий значение секрета.

Переходя к Nginx, мы видим, что для режима развертывания установлено значение global . Значение по умолчанию (которое неявно используется в Интернете) — replicated , что означает, что мы укажем, сколько реплик мы хотим. Когда мы указываем global , он сообщает Docker, что каждый узел в рое должен запускать ровно один экземпляр службы. Запустив docker service ls еще раз, вы заметите, что у nginx есть три реплики, по одной на каждую ноду в нашем рое.

Наконец, мы поручили Docker запустить один экземпляр Redis где-то в рое. Неважно, где, так как наши веб-контейнеры автоматически перенаправляются на него, когда запрашивают хост Redis.

Использование Swarm изо дня в день

Поздравляем с развертыванием вашего первого приложения в Docker Swarm! Давайте рассмотрим несколько распространенных команд, которые вы будете использовать.

Проверка вашего роя

Нужно проверить ваши услуги? Попробуйте docker service ls и docker service ps <service name> . Первый показывает вам общий обзор каждой службы, а второй дает вам информацию о каждом контейнере, работающем для указанной службы. Это особенно полезно, когда вы хотите увидеть, на каких узлах запущена служба.

Последовательные обновления

Как насчет того, когда вы будете готовы обновить приложение? Что ж, самое интересное в docker stack deploy заключается в том, что оно также будет применять обновления к существующему стеку. Допустим, вы отправили новый образ Docker в свой репозиторий. На самом деле вы можете просто запустить ту же команду развертывания, которую вы использовали в первый раз, и ваш рой загрузит и развернет новый образ.

Конечно, вы не всегда можете захотеть обновлять каждую службу в стеке. Мы также можем выполнять обновления на уровне обслуживания. Предположим, я недавно обновил изображение для своего веб-сервиса. Я могу выполнить эту команду, чтобы обновить все мои веб-контейнеры:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web

Дополнительным преимуществом этой команды является то, что она будет применять непрерывное обновление, если вы указали, что это должно быть сделано в исходной конфигурации. И даже если вы этого не сделали, вы можете передать флаги для обновления, которые будут указывать ему выполнять непрерывное обновление следующим образом:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web

Это будет обновлять один контейнер за раз, ожидая 30 секунд между обновлениями.

Масштабирование сервисов вверх или вниз

Иметь два веб-контейнера — это здорово, но знаете, что лучше? Наличие десяти! Масштабировать сервисы вверх и вниз в рое так же просто, как:

 docker service scale demo_web=10

Запустите эту команду и проверьте вывод docker service ps demo_web . Вы увидите, что теперь у нас есть десять контейнеров, и восемь из них были запущены только что. Если вам интересно, вы также можете вернуться в веб-приложение и обновить страницу несколько раз, чтобы увидеть, что теперь вы получаете больше, чем исходные два идентификатора контейнера.

Удаление стеков и сервисов

Ваши стеки и сервисы развернуты и масштабированы, здорово! Но теперь вы хотите перевести их в автономный режим. Это можно сделать с помощью соответствующей команды rm . Чтобы удалить наш демонстрационный стек, выполните команду:

 docker stack rm demo

Или, если вы хотите просто удалить одну службу, просто используйте:

 docker service rm demo_web

Осушающие узлы

Помните, как мы запускали docker node ls ранее, чтобы проверить узлы в нашем рое? Он предоставил кучу информации о каждом узле, включая его доступность . По умолчанию узлы активны , что означает, что они подходят для запуска контейнеров. Однако бывают случаи, когда вам может потребоваться временно отключить узел для выполнения обслуживания. Конечно, вы могли бы просто закрыть его, и рой восстановился бы, но было бы неплохо уделить немного внимания Моби (киту-докеру).

Вот тут-то и появляются узлы слива. Когда вы помечаете узел как Drain , Docker Swarm делегирует любые контейнеры, работающие на нем, другим узлам, и он не будет запускать какие-либо контейнеры на узле, пока вы не измените его доступность обратно на Active .

Допустим, мы хотим слить node1 . Мы можем запустить:

 docker node update --availability drain node1

Легкий! Когда вы будете готовы вернуться к работе:

 docker node update --availability active node1

Подведение итогов

Как мы видели, Docker в сочетании с режимом Swarm позволяет нам развертывать приложения более эффективно и надежно, чем когда-либо прежде. Стоит отметить, что Docker Swarm ни в коем случае не является единственным движком для оркестровки контейнеров. На самом деле, это один из младших. Kubernetes существует дольше и определенно используется в большем количестве производственных приложений. Тем не менее, Swarm — это тот, который официально разработан Docker, и они работают над добавлением новых функций каждый день. Независимо от того, что вы решите использовать, продолжайте хранить в контейнерах!