О Docker Swarm
2016-10-18
Swarm — это рой.
В частности, насекомых.
Пчёл, например.
Но лично у меня это слово ассоциируется со стаей зергов.
Помните, такие милые и очаровательные букашки?
Ну а в Docker тоже есть свой Swarm — рой Docker Engine.
Одиночный Docker Engine запускает контейнеры на локалохосте, из образов, либо собранных тоже на локалхосте, либо выкачанных с Docker Hub.
Docker Swarm — это кластер Докеров. Множество хостов, на каждом из которых запущен отдельный Docker Engine, объединены под общим управлением, и выглядят как один большой Докер. Хосты могут добавляться в кластер, и вычислительная ёмкость вашего большого Докера будет тоже увеличена.
Поднять свою стаю Докеров довольно легко. Главный момент: компоненты кластера — это тоже докер контейнеры. Вообще складывается ощущение, что в мире Докера образы используются как обычные приложения: можно и нужно запускать одни и те же образы с разными параметрами, чтобы получить нужный результат.
По умолчанию Docker Engine,
то бишь демон Docker,
управляется через локальный сокет/var/run/docker.sock
.
Для Swarm демоны должны управляться удалённо,
через TCP.
Поэтому демону нужно добавить параметр запуска:
-H tcp://0.0.0.0:2275
Демон будет слушать порт 2275
,
без TLS шифрования.
В Ubuntu с Upstart этот параметр задаётся
в файле /etc/default/docker
,
дописать в переменную DOCKER_OPTS
.
Управлять Swarm будет Swarm Manager.
Это, внезапно, контейнер,
запущенный из образа swarm
с параметром manage
.
Можно для этого образа ручками сделать docker run
.
Можно сочинить docker-compose.yml
.
Но я, так как дело будет происходить на кучке хостов кластера,
предпочёл запользовать Ansible.
Модуль docker_service работает вполне себе ничего.
Он дёргает docker-compose
на целевой машине.
А описание самого композа можно задать прямо в ансибловой задаче
(и там и там, таки, YAML),
со всей мощью шаблонов, конечно.
Получается, что через Ansible Docker Swarm Manager можно запустить как-то так.
- name: start swarm manager
run_once: true
become: yes
docker_service:
project_name: swarm
definition:
version: '2'
services:
manager:
image: swarm
command: 'manage -H :3375 --advertise {{ inventory_hostname }}:3375 consul://{{ inventory_hostname }}:8500'
restart: unless-stopped
ports:
- '3375:3375'
run_once
пригодится,
чтобы не запустить случайно несколько менеджеров.
В кластере действительно может быть несколько менеджеров.
Они устраивают выборы,
и кто победит,
становится primary
,
а остальные replica
.
Впрочем, команды можно слать на любой,
replica будет пересылать их в primary.
Тут можно указать project_name
— ту самую первую часть имени контейнеров,
создаваемых Docker Compose,
которая обычно берётся из имени каталога,
где лежит docker-compose.yml
.
Берём образ по имени swarm
c Docker Hub,
и выполняем в нём команду manage
с параметрами.
Ключ -H
указывает порт,
который будет слушать менеджер.
Этот порт нужно пробросить наружу в директиве ports
.
Для доступа к менеджеру без TLS шифрования
принято использовать порт 3375
.
Чтобы менеджеры видели друг друга для выборов
используется ключ --advertise
,
где указывается внешнее имя хоста и порт,
по которому доступен данный менеджер,
так он будет себя объявлять.
Ещё нужен ключ --replication
без параметров,
его я тут не указал,
потому что если у вас менеджер один,
он рискует ни с кем не договориться,
кто здесь главный,
и зависнуть в состоянии replica
,
рой тогда не заведётся.
Последний параметр команды manage
— адрес Consul.
Менеджеру нужно какое-то распределённое хранилище,
чтобы хранить адреса и состояния узлов кластера,
а также выбирать главного менеджера.
Хранилищем может быть Consul,
или Etcd,
или Zookeeper.
Мегаполезный параметр restart
у Docker и Docker Compose
появился с Докера 1.10.
Он говорит демону,
нужно ли запускать этот контейнер при старте самого демона.
always
— запускать всегда.
unless-stopped
— запускать всегда,
если только контейнер не был ранее остановлен явно
(через docker stop
или docker-compose down
).
Так как Swarm нам нужен после ребута,
ставим этот параметр.
Нам нужен кластер Consulа. Конечно же сам Консул будем запускать в докер контейнере. На всех узлах Swarm кластера и там, где запущен Swarm Manager. Чтобы все, кому нужен Consul, могли бы ходить за ним на свой собственный хост. Конечно же для Consul уже есть готовый образ, который нужно запустить с правильными параметрами.
На одном из узлов нужно стартовать сервер Консула.
- name: start consul
run_once: true
become: yes
docker_service:
project_name: swarm
definition:
version: '2'
services:
consul:
image: progrium/consul
command: "-server -advertise {{ inventory_hostname }} -bootstrap"
restart: unless-stopped
ports:
- '8300:8300'
- '8301:8301'
- '8301:8301/udp'
- '8302:8302'
- '8302:8302/udp'
- '8400:8400'
- '8500:8500'
Сервер должен объявить о себе другим Консулам,
для этого нужен ключик -advertise
.
Ключ -bootstrap
говорит,
что это сам себе сервер,
он начнёт обслуживать запросы,
не дожидаясь,
пока в сети появятся другие Консулы.
У Консула нужно прокинуть много портов.
Какие-то порты используются,
чтобы Консулам собраться в свой кластер.
А Smarm Manager ходит в Консул по порту 8500
— это REST API для доступа к распределённому key-value хранилищу.
А на других узлах Consul должен войти в существующий кластер. Для этого нужно выдать образу другой набор параметров.
command: "-server -advertise {{ inventory_hostname }} -join {{ manager_host }}"
Вам нужен адрес хоста,
где был запущен первый Консул,
чтобы передать его здесь в параметр -join
.
Консулы есть,
менеджер есть,
нужно скомандовать Докерам объединяться.
Для этого нужно снова взять образ swarm
,
только выполнить в нём команду join
.
- name: join swarm cluster
become: yes
docker_service:
project_name: swarm
definition:
version: '2'
services:
node:
image: swarm
command: 'join --advertise={{ inventory_hostname }}:2275 consul://{{ inventory_hostname }}:8500'
restart: unless-stopped
Узел, вступающий в Swarm, должен объявить миру (т.е. менеджеру), на каком хосте и каком порту живёт докер демон. А объявляет он через Консула.
Вступление в Swarm нужно сделать на всех хостах,
которые должны войти в кластер и управляться менеджером.
Формально менеджер (или кластер менеджеров)
не обязан входить в это множество хостов,
он может быть сбоку от самого Swarm кластера.
Но менеджер работает только когда как-то меняется конфигурация кластера:
создаются новые контейнеры.
Так что вполне можно разместить его на одном из узлов кластера.
Тогда на этом узле будут запущены и swarm manage
, и swarm join
.
Почти всё.
Теперь можно запускать docker
или docker-compose
,
заставив их подключаться к Swarm Manager.
Либо через параметр -H
:
$ docker -H {{ manager_host }}:3375 run ...
Либо через переменную окружения DOCKER_HOST
:
$ DOCKER_HOST=tcp://{{ manager_host }}:3375 docker-compose up
Но есть ограничения.
Особенно если вы выполняете эти команды
на совершенно левом хосте,
который не входит в Swarm.
Нельзя билдить контейнеры,
т.е. build
в docker-compose.yml
— запрещён.
Вам придётся сборку делать отдельно,
а собранные образы выкладывать на Docker Hub
или свой Docker Registry.
Внешние volume будут подключены не в файловой системе, где вы запускаете команды, а в файловой системе ноды кластера, где будет запущен контейнер. Значит, если вам нужно подключить какие-то конкретные файлы, вам, вероятно, придётся позаботиться о том, чтобы они оказались на всех нодах кластера, прежде, чем запускать контейнеры. Ansible тут тоже может оказаться весьма полезным.
Линки между контейнерами,
когда вы проставляете link
между сервисами в одном docker-compose.yml
,
как ни странно, работают.
Однако об этом нужно позаботиться заранее.
В Swarm добавился новый тип сетей
— overlay
.
Для контейнеров на разных хостах всё прозрачно,
они видят друг друга в одной сети.
Но для этого Docker Engine должен
договориться с другими Docker Engine
насчёт проброса трафика.
Делает он это опять через Consul,
или Etcd, или Zookeeper.
Докер демону нужно добавить ещё один параметр запуска,
туда, в /etc/default/docker
:
--cluster-store=consul://{{ inventory_hostname }}:8500
Вообще-то рекомендуют для работы overlay сети использовать другой Консул, не тот, что уже используется Swarm Manager. Но и так работает (не для production, как говорится).
Получается Уроборос. Докер демону для работы сети нужен Консул, который запускается в контейнере после старта демона. Но, работает. Видимо потому, что контейнер Консула не использует overlay сеть, Консулы через кучу проброшенных портов друг с другом общаются.
Ну ещё демону нужно заявить о себе. Нужен ещё один параметр:
--cluster-advertise={{ inventory_hostname }}:2275
После добавления параметров и перезапуска демонов, overlay сеть будет работать.
Когда мы деплоим что-нибудь в Swarm,
нельзя заранее угадать,
на каком именно хосте будет запущен контейнер.
По умолчанию он старается запустить равное количество контейнеров
(одного типа?) на всех хостах.
Но если у вас в docker-compose.yml
есть какие-то выделенные сервисы,
тот же балансер,
который должен висеть на порту по вполне известному адресу конкретного хоста,
вам нужно,
чтобы контейнер разворачивался на конкретном хосте.
Это — можно.
У каждого Docker Engine в Swarm есть метки. Как минимум — имя хоста. Но можно добавлять свои метки. Метки — это строки вида ключ-значение. Ключ считается уникальным, поэтому нельзя навесить несколько меток с одинаковым ключом.
Чтобы добавить метку,
нужно передать ещё один параметр запуска многострадальному демону
(опять /etc/default/docker
).
Например, пометим хост с балансером:
--label balancer=yes
Теперь в описание сервиса балансера можно добавить переменную окружения для Swarm:
haproxy:
image: dockercloud/haproxy
restart: unless-stopped
environment:
'constraint:balancer==yes':
DOCKER_HOST: 'tcp://{{ manager_host }}:3375'
links:
- backend
Осторожно, синтаксис — дикий.
constraint:balancer==yes
— это имя переменной окружения.
А значение у неё — пустое.
Ещё раз,
через Swarm Manager можно делать почти всё то же,
что через обычный Docker Engine.
Тот же dockercloud/haproxy
,
как видите,
вполне умеет выдирать из Swarm те контейнеры,
которые нужно балансить,
только адрес менеджера ему нужно передать
через переменную окружения DOCKER_HOST
.
docker -H {{ manager_host }}:3375 info
выдаст подробные сведения о здоровье Swarm кластера:
сколько узлов есть,
сколько контейнеров на каждом запущено и т.п.
docker -H {{ manager_host }}:3375 ps
покажет,
какие именно контейнеры запущены,
на каком хосте именно,
и какие порты на каком хосте открыты.
DOCKER_HOST=tcp://{{ manager_host }}:3375 docker-compose scale ...
создаст нужное количество контейнеров
именно во всём кластере,
равномерно размазав их по доступным узлам.
Порты 2275
и 3375
,
упомянутые выше, — без шифрования.
Но можно настроить
TLS шифрование и авторизацию между всеми узлами кластера.
Ну и напоследок, в Docker Engine v.1.12,
который ныне rc1,
Swarm будет встроен
прямо в Engine.
Вместо запуска swarm
образов с параметрами,
нужно будет запускать команды docker swarm
.
Я не смотрел, как это работает.
Надеюсь, служебных контейнеров понадобится поменьше :)