Docker — прекрасен. С ним можно упаковать приложение по контейнерам, забросить их на случайный хост, и всё будет просто работать. Но на одном хосте особо не отмасштабируешься. Да и если хост прикажет долго жить, всё приложение отправится на тот свет вместе с ним. Конечно, для масштабирования можно завести сразу несколько хостов, объединить их при помощи overlay сети, так что и места больше будет, и возможность для контейнеров общаться останется.
Но опять же, как всем этим управлять? Хосты всё ещё могут отмирать. Как быстро определить, какой именно? Какие контейнеры на нём были? Куда теперь их переносить?
Начиная с версии 1.12.0 Docker может работать в режиме Swarm («Рой». В старых версиях был просто Docker Swarm, но тот работал по-другому), и потому способен решать все эти проблемы самостоятельно.
Что такое режим Swarm
Docker в Swarm режиме это просто Docker Engine, работающий в кластере. Кроме того, что он считает все кластерные хосты единым контейнерным пространством, он получает в нагрузку несколько новых команд (вроде docker node
и docker service
) и концепцию сервисов.
Сервисы — это ещё один уровень абстракции над контейнерами. Как и у контейнера, у сервиса будет имя, базовый образ, опубликованные порты и тома (volumes). В отличие от контейнера, сервису можно задать требования к хосту (constraints), на которых его можно запускать. Да и вообще, сервис можно масштабировать прямо в момент создания, указав, сколько именно контейнеров для него нужно запустить.
Но важно понимать одну большую разницу. Сама по себе команда docker service create
не создаёт никаких контейнеров. Она описывает желаемое состояние сервиса, а дальше уже Swarm менеджер будет искать способы это состояние достигнуть. Найдёт подходящие хосты, запустит контейнеры, будет следить, чтобы с ними (и хостами под ними) всё было хорошо, и перезапустит контейнер, если «хорошо» закончится. Иногда желаемое состояние сервиса так и не будет достигнуто. Например, если в кластере закончились доступные хосты. В таком случае сервис будет висеть в режиме ожидания до тех пор, пока что-нибудь не изменится.
План на сегодня
Будем играться. Пользы от абстрактной и непонятной теории — ноль, так что создадим-ка свой собственный уютный Docker кластер на три виртуальные машины. Запустим в нём один сервис для визуализации кластера, и ещё один, например web, чтобы масштабировался и заодно демонстрировал, как он успешно восстанавливается от внезапно упавшего хоста.
Что понадобится
Нам понадобится установленный Docker версии 1.12.0
и новее, docker-machine
и VirtualBox. Первые два обычно устанавливаются вместе c Docker Toolbox на Маке и Windows. На Linux, ЕМНИП, docker-machine
устанавливается отдельно, но всё ещё достаточно понятно. С установкой же VirtualBox проблем вообще не бывает. Обычно.
Я буду использовать Docker 17.03.1-ce
для Mac и VirtualBox 5.1.20
Шаг 0. Создаём три виртуальные машины
Создавать машины при помощи именно docker-machine
имеет смысл хотя бы потому, что они сразу будут идти с предустановленным Docker. Из трёх хостов на одном мы поселим менеджера Swarm, а на двух других — обычных рабочих. Собственно, создание виртуальных машин — удивительно безболезненное занятие:
1 2 3 4 5 6 |
docker-machine create sw-master # lots of boring output docker-machine create sw-worker-1 # equal amount of boring output docker-machine create sw-worker-2 # you guessed it, more output |
Шаг 1. Создаём Swarm
Есть две команды, которые подойдут для того, чтобы превратить одинокий Docker Engine в участника кластера: docker swarm init
для новых кластеров и docker swarm join
для уже существующих. Кластера у нас ещё нет, так что заходим по ssh в sw-master
и делаем из него кластер:
1 2 3 4 5 6 7 8 9 10 11 |
docker-machine ssh sw-master # Get inside of sw-master docker swarm init --advertise-addr 192.168.99.101 #Swarm initialized: current node (dy1wvk5r3gmk8dz2gkmn9zgc9) is now a manager. # #To add a worker to this swarm, run the following command: # # docker swarm join \ # --token SWMTKN-1-3liicadqwz2xmmw8hk5s66n21ssa2i3fondkamtuiw2wnsmkl4-aop5zxnrvv2tko7pja0tm2pff \ # 192.168.99.101:2377 # # To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. |
Первый хост кластера стал к тому же и менеджером — больше ведь нет никого. Сама по себе команда создания кластера оказалась настолько любезной, что даже вернула в ответ другую команду (строки 7-9), при помощи которой можно подключить к кластеру остальные хосты. --token
параметр в ней — секретный ключ, в который кроме секретов забито, в какой роли новые хосты будут входить в кластер (обычные работяги). Если токен потерять, или вместо рабочего хоста мы хотим добавить ещё одного менеджера, то получить новый токен можно через docker swarm join-token [manager]
.
Кстати, из-за того, что в виртуалках, созданных VirtualBox, есть аж два сетевых интерфейса и, соответственно, две айпишки, мне пришлось явно указать кластеру, на какую из них вешаться: --advertise-addr
.
Теперь выходим из машины, и заходим поочерёдно в другие хосты и тоже подключаем их к кластеру той самой командой, которую вывел docker swarm init
.
Верьте или нет — кластер абсолютно и бесповоротно готов. Если выйти из виртуальной машины и подключить локального докер клиента к Swarm менеджеру ( eval $(docker-machine env sw-manager
) ), то можно даже попробовать какую-нибудь из новых команд. Например, docker node ls
:
1 2 3 4 5 6 |
eval $(docker-machine env sw-master) # Connect to docker engine in sw-master docker node ls #ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS #dy1wvk5r3gmk8dz2gkmn9zgc9 * sw-master Ready Active Leader #mfn03tid5qz54vf7pyr1898ba sw-worker-2 Ready Active #n7dr63crfm19w2hc1hxfkac3a sw-worker-1 Ready Active |
Шаг 2. Создаём сервис визуализации кластера
У Docker, оказывается, есть публичный образ visualizer
для отрисовки кластера. На его примере можно и показать, как создавать сервис, так и протаращиться на красивую картинку. Итак, команда (я потом всё объясню):
1 2 3 4 5 6 |
docker service create \ --name=viz \ --publish=8080:8080 \ --constraint=node.role==manager \ --mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ dockersamples/visualizer |
Что именно она делает, построчно:
docker service create
— всё просто, создаём сервис. На самом деле, команда очень, очень похожа наdocker run
и в большинстве случаев она тоже приводит к запущенному контейнеру.--name=viz
— имя сервиса. Один-в-один как вdocker run
.--publish=8080:8080
— это старая добрая команда привязки портов контейнера к портам хоста, как и-p
вdocker run
. Просто в полной форме.--constraint=node.role==manager
— этот параметр говорит, что запускать сервис можно только на хостах-менеджерах. Это нужно для того, чтобыvisualizer
‘у было у кого запрашивать данные о состоянии кластера.--mount=...
— хотя это самая длинная строка-параметр, она вполне простая./var/run/docker.sock
— это сокет, через который Docker может принимать команды извне. Мы просто пробрасываем его в контейнер (src
,dst
), чтобы тот мог по нему общаться. Вот и всё. В принципе, mount делает всё то же самое, что и-v
(volume) вdocker run
.dockersamples/visualizer
— старое доброе имя образа, из которого потом будут делаться контейнеры.
Пока команда выполняется, у нас будет шанс позапрашивать состояние сервиса при помощи docker service ls
(состояние сервиса) и docker service ps
(состояние связанных с ним задач). Мне удалось поймать «промежуточное» состояние, когда сервис ещё не готов (current state: preparing):
1 2 3 4 5 6 |
docker service ls #ID NAME MODE REPLICAS IMAGE #xo55viuyqr81 viz replicated 0/1 dockersamples/visualizer:latest docker service ps viz #ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS #vlbqdw0hfnhp viz.1 dockersamples/visualizer:latest sw-master Running Preparing 36 seconds ago |
Айпишка моего Swarm менеджера — 192.168.99.101 (docker-machine ip sw-master
), так что по ней и порту 8080 можно посмотреть на работу visualizer
сервиса:
Красота.
Шаг 3. Маштабируем сервис и замеряем его производительность
По умолчанию Swarm будет размещать контейнеры нового сервиса в самом пустом на данный момент хосте, поэтому в реалиях нашего кластера команда docker service create --name=web --publish=80:80 nginx
запустит новый web
сервис на одном из двух обычных рабочих хостов:
Но это не самая интересная часть. Самая интересная часть заключается в том, что web
сервис всё ещё можно запрашивать по айпишке хоста-менеджера — 192.168.99.101. Да что там, его можно запросить вообще на любом хосте кластера:
Прикол в том, что открытие порта в сервисе/контейнере в Swarm кластере открывает его на всех машинах. Это очень удобно, когда мы масштабируем сервис. То есть запускаем сразу несколько его копий (реплик). При таком сценарии Swarm начинает работать ещё и как балансировщик нагрузки и равномерно распределяет запросы по контейнерам, с какой бы стороны кластера они не поступали. С этой фичей мы сейчас и поиграем.
Во-первых, отмасштабируем web
сервис. Двух реплик для начала будет достаточно.
1 |
docker service scale web=2 |
Если Swarm действительно работает как балансировщик нагрузки, то по идее мы сможем заметить рост пропускной способности web
и даже её замерить. У меня каким-то образом завалялся Apache Bench, который для таких целей вполне сгодится, и я попробую нагрузить сервис десятью тысячами запросов в пятидесяти потоках и посмотрю, сколько запросов в секунду он может переварить. Затем оставлю только одну копию web
, и ещё раз замерю его пропускную способность.
1 2 3 4 5 6 7 8 9 |
ab -n 10000 -c 50 http://192.168.99.101/ #... #Requests per second: 1925.16 [#/sec] (mean) #... docker service scale web=1 ab -n 10000 -c 50 http://192.168.99.101/ #... #Requests per second: 745.05 [#/sec] (mean) #... |
Прикольно. Две реплики web
сервиса были даже больше чем в два раза производительнее, чем одна — 1925 запросов в секунду против 745. Скорее всего, если бы я сделал хотя бы по 10 замеров, числа были бы более правдоподобными, но всё равно тенденция понятна: больше реплик — больше вычислительных способностей.
Шаг 4. Проверка на живучесть
Пост получился очень большим, но я не могу поставить точку без последнего, простенького теста. Оставшийся web
сервис теперь живёт на sw-worker-2
. Что с ним произойдёт, если хост отойдёт в лучший из миров? Будем проверять.
1 |
docker-machine stop sw-worker-2 |
Не особо удивительно, что через несколько секунд после эвтаназии sw-worker-2
web
сервис реинкарнировался на sw-worker-1
. Swarm менеджер заметил, что заявленная конфигурация сервиса больше не выполняется — вместо одного запущенного контейнера есть ноль — и восстановил баланс сил на новом хосте.
Мораль
Если Докер просто классный, то Swarm — восхитительно прекрасен. Кластер делается элементарно, он автоматом идёт с балансировкой нагрузки, проверкой контейнерной смертности и масштабируемостью. И это далеко не все его фичи. Лично я собираюсь использовать Swarm даже на одиночных машинах. Хотя бы потому, что он сам будет следить, чтобы контейнеры были перманентно живыми.
Спасибо автору за статью!
Если нет желания ставить докер локально и плодить его копии то есть замечательный сайт где можно играться с Docker Swarm — https://labs.play-with-docker.com/
Отличный обзор
Спасибо за статью! Можете сказать, команды масштабирования и создания сервиса нужно запускать на менеджере и потом он распределит их по воркерам, или можно в воркерах ?
Не могу сейчас проверить, но я бы очень удивился, если бы swarm обязывал запускать команды только из master-машины. Это всё-таки противоречит концепции общего для всех кластера. Тем более, что command line клиенты сами ничего не решают и перенаправляют команды api-сервисам, которые в случае кластера подчиняются общей иерархии
$ docker service scale web=1
web: Error response from daemon: This node is not a swarm manager. Worker nodes can’t be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.
Прикольно, буду знать. Спасибо
Огромное спасибо за материал. Все попробовал и все воспроизвелось! Паззл наконец то начал складываться в голове. Можно исследовать тему дальше самостоятельно.