Для двух последних постов про Consul мне пришлось делать простую, но при этом нудную вещь: создавать Консул-кластер руками. Все три виртуальные машины. Дважды. То есть нужно было создать три VM, на каждую скачать Consul, распаковать, узнать IP адрес, скопировать файл настроек… В общем, скука.
С другой стороны, создание контейнеров в Docker — без пяти минут маленьких виртуальных машин — полностью автоматизировано. Есть Dockerfile для конфигурации одного контейнера, есть docker-compose для целой стайки. В общем, красота. Вот бы существовало что-нибудь похожее для конфигурации хостов целиком.
И похожее, оказывается, существует. Оно называется Vagrant, и там даже есть свой Vagrantfile для конфигураций. Почти как в Докер.
Что такое Vagrant
Согласно официальной документации, Vagrant — это инструмент для создания и конфигурации виртуальных сред. Виртуальных машин, другими словами. Если вдруг нам требуется создать и настроить виртуальную машину для разработки, тестов или каких-нибудь задач в продакшене, то можно либо сделать её руками (и забыть через неделю, что именно и как там настраивалось), либо занести шаги настроек в Vagrantfile и потом создавать хосты пачками. А ещё Vagrantfile можно хранить в git, тем самым отслеживая почему и как он менялся, делиться им с коллегами по цеху или даже тестировать конфигурацию хоста локально, прежде чем развернуть его в продакшен-облаке. Ведь тот же Vagrantfile, который пять минут назад создавал VM в VirtualBox, с минимальными изменениями можно использовать, чтобы развернуть её в AWS, Google или Azure.
План на сегодня
Так как я уже два раза создавал кластер из трёх практически идентичных машин, есть очень большая вероятность, что это случится и в третий раз. Так что почему бы не автоматизировать процесс?
А что, создадим Vagrantfile, а он сам развернёт три виртуальные машины в VirtualBox, установит и настроит Consul агентов, и сделает из них кластер. Романтика.
Что нам потребуется
Для игр в кластер и автоматизацию нам понадобятся Vagrant и VirtualBox. Процесс установки там простой, а закачки — вообще тривиальный. Работать я буду на Маке, но на Windows и основных породах Линукса процесс был бы точно таким же.
Шаг 0. Создаём пустую VM
Для создания виртуальной машины нам нужен Vagrantfile. С него и начнём:
1 2 3 4 5 |
vagrant init ubuntu/xenial64 --minimal #A `Vagrantfile` has been placed in this directory. You are now #ready to `vagrant up` your first virtual environment! Please read #the comments in the Vagrantfile as well as documentation on #`vagrantup.com` for more information on using Vagrant. |
Тут всё просто: init
создаёт новый файл. Дальше идёт имя бокса — ubuntu/xenial64
. Бокс (box) — это то, на базе чего создавать машину. Как базовый образ в Docker. В нашем случае это 64-битная Ubuntu 16.04 LTS. Наконец, так как init
очень любит добавлять комментарии в Vagrantfile, а я — нет, --minimal
параметр оставит в файле только полезное.
Итак, делаем в консоли vagrant up
и через минуту-другую у нас будет полностью рабочая Убунта:
1 2 3 4 |
vagrant up #Bringing machine 'default' up with 'virtualbox' provider... #==> default: Importing base box 'ubuntu/xenial64'... #.. |
Vagrant использует VirtualBox в качестве провайдера для своих машин, но вообще-то это можно менять. Например, для Google Compute Engine я бы добавил такой параметр --provider=google
.
После того, как машина запустилась, в неё можно зайти по SSH:
1 2 3 4 5 6 7 8 9 |
vagrant status #Current machine states: # #default running (virtualbox) #... vagrant ssh #Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-72-generic x86_64) #... |
Шаг 1. Настраиваем VM
Теперь пришло время для настройки Консул-агентов, и логично начать с Консул-сервера. Вот, что нам нужно сделать с виртуальной машиной:
- Дать ей вменяемое название, например
consul-server.
- Скачать и распаковать бинарники с Consul.
- Задать статический IP адрес (иначе как остальные члены кластера будут находить сервер?).
- Зарегистрировать и запустить Consul как Ubuntu сервис.
Шаг 1.1. Задаём имя хоста
Для этого придётся лезть в Vagrantfile. Для начала, вот что для нас создал init --minimal
:
1 2 3 |
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" end |
Чтобы задать имя хоста, нужно добавить всего одну написанную на Ruby строку:
1 2 3 4 |
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" config.vm.hostname = "consul-server" end |
Теперь перезапускаем машину через vagrant reload
, заходим по SSH ещё раз и любуемся результатом:
1 2 3 4 5 6 7 |
vagrant reload #==> default: Attempting graceful shutdown of VM... #... vagrant ssh #... ubuntu@consul-server:~$ hostname #consul-server |
Шаг 1.2. Устанавливаем Consul через shell
Следующий шаг немного сложнее. В определённых кругах процесс настройки сервера называется провиз.. провижин… provisioning… настройкой. Процесс настройки сервера называется настройкой, да. Вагрант умеет делать это разными способами. Например, shell провижинер (ну не учился я на переводчика) — запускает шелл-файл. file
— просто копирует файл или папку с хоста туда, куда скажут. Есть ещё ansible
, chef
и даже puppet
провижинеры, которые делают вообще всё, что угодно.
Мне настраивать линуксовый хост проще с шелла, так что его и будем использовать:
1 2 3 |
#... config.vm.provision "shell", path: "provision.sh" #... |
path
указывает на скрипт с инструкциями, который тоже нужно создать:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash # update and unzip apt-get -y update && apt-get install -y unzip # install consul cd /home/ubuntu version='0.8.0' wget https://releases.hashicorp.com/consul/${version}/consul_${version}_linux_amd64.zip -O consul.zip unzip consul.zip rm consul.zip # make consul executable chmod +x consul |
Правда, если мы снова вызовем vagrant reload
— ничего не изменится. Всё потому, что Vagrant настраивает машину только при запуске. Но его можно переубедить, выполнив vagrant reload --provision
(или vagrant provision
, если перезапуск не нужен). Запускаем, ждём, и вот он, установленный Consul:
1 2 3 4 |
vagrant reload --provision vagrant ssh ubuntu@consul-server:~$ ./consul version # Consul v0.8.0 |
Шаг 1.2.1. Опционально: делаем скрипт настроек идемпотентным
Есть старый джедайский трюк, который позволяет запускать скрипт с настройками несколько раз, и тот оказывается достаточно умным, чтобы не переустанавливать всё то, что уже установлено. Такой скрипт называется идемпотентным. Это очень удобно. Ведь если скрипт — в процессе разработки, то перезапускать его приходится часто.
Прямо сейчас настройки сводятся к следующему:
- установить unzip
- установить consul
Если provision.sh
научить не переустанавливать ни то, ни другое, он станет и идемпотентней, и удобней. И это тоже просто сделать:
1 2 3 4 5 6 7 8 9 10 |
#.. # update and unzip dpkg -s unzip &>/dev/null || { apt-get -y update && apt-get install -y unzip } # install consul if [ ! -f /home/ubuntu/consul ]; then #... fi |
Шаг 1.3. Задаём статический IP
Легкотня. Две строки в Vagrantfile и магия готова. Тут важно не перестараться и выбрать адрес из правильного диапазона.
1 2 3 4 5 6 |
#... config.vm.hostname = "consul-server" serverIp = "192.168.99.100" config.vm.network "private_network", ip: serverIp #... |
Шаг 1.4. Запускаем Consul в качестве Ubuntu сервиса
Это немного тяжело, но без автозапуска ведь весь пример будет бесполезен.
Чтобы превратить Consul в сервис Убунты, его нужно зарегистрировать в systemd. Можно скачать заготовку настроек сервиса отсюда, а затем скопировать её в /etc/systemd/system/
папку гостевой машины. Затем, так как consul
теперь будет запускаться под root, по-хорошему его нужно перенести из домашней папки куда-нибудь в /usr/local/bin
.
Чтобы не уводить фокус статьи в сторону, я опущу большинство деталей и оставлю только пару важных моментов (но все сырцы всё равно есть на github).
- Работая с VirtualBox, Vagrant монтирует папку с Vagrantfile к гостевой машине к
/vagrant
. Я использовал это поведение вprovision.sh
, чтобы скопировать скачанный ранееconsul.service
к остальным сервисам гостевой оси:
1cp /vagrant/consul.service /etc/systemd/system/consul.service - В том же
consul.service
Consul агент запускается с опцией, заставляющей его искать свои настройки в/etc/systemd/system/consul.d/
. Это просто прекрасно, так как теперь я могу создать какой-нибудьinit.json
на лету и скопировать его в ту папку для каждого из агентов. Для сервера это будет выглядеть так:
1234567891011121314#...serverInit = %({"server": true,"ui": true,"advertise_addr": "#{serverIp}","client_addr": "#{serverIp}","data_dir": "/tmp/consul","bootstrap_expect": 1})config.vm.provision "shell", inline: "echo '#{serverInit}' > /etc/systemd/system/consul.d/init.json"#... - Наконец, сервис нужно запустить. Это всего лишь ещё одна строка в Vagrantfile (но можно было бы и в
provision.sh
:
123#...config.vm.provision "shell", inline: "service consul start"#...
Шаг 1.5. Пробный запуск
Если вы ещё не спите, то самое время запустить vagrant reload --provision
и зайти на 192.168.99.100:8500
в самом лучшем из браузеров (скоро это снова будет IE, муа-ха-ха!):
Теперь там живёт Consul server, настоящий и рабочий. Машину с ним можно остановить, удалить, надругать или совершить иное непотребство, но потом снова выполнить vagrant up
и у нас снова появится рабочий Consul сервер. Это чудесно.
Шаг 2. Добавляем машин в кластер
Vagrantfile может описывать несколько машин. Это же Ruby, там хоть hello world пиши. Дополнительные машины задаются через функцию define
:
1 2 3 4 5 6 7 |
config.vm.define "host1" do |host| host.vm.hostname = "first-host" end config.vm.define "host2" do |host| host.vm.hostname = "second-host" end |
Прежде чем мы начнём создавать компанию Consul серверу, уже запущенную VM стоит удалить при помощи vagrant destroy -f
.
Затем, думаю, стоит немного развлечь себя рефакторингом. Ведь обычные Консул-агенты похожи на Консул-сервер практически один-в-один. Перенеся общую часть в отдельную функцию, можно избежать греха копипасты.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#... def create_consul_host(config, hostname, ip, initJson) config.vm.define hostname do |host| host.vm.hostname = hostname host.vm.provision "shell", path: "provision.sh" host.vm.network "private_network", ip: ip host.vm.provision "shell", inline: "echo '#{initJson}' > /etc/systemd/system/consul.d/init.json" host.vm.provision "shell", inline: "service consul start" end end #... |
При помощи этой функции воссоздать Консул-сервер можно в одну строку:
1 2 3 |
#... create_consul_host config, "consul-server", serverIp, serverInit #... |
А теперь только посмотрите, как просто добавить ещё две машины в наш уютный кластер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#... for host_number in 1..2 hostname="host-#{host_number}" clientIp="192.168.99.10#{host_number}" clientInit = %( { "advertise_addr": "#{clientIp}", "retry_join": ["#{serverIp}"], "data_dir": "/tmp/consul" } ) create_consul_host config, hostname, clientIp, clientInit end #... |
Ну здорово же! Мы создаём виртуальные машины в цикле! Когда это перестало быть офигенным?
Но на всякий случай убедимся, что кластер действительно работает:
1 2 3 4 |
vagrant up #Bringing machine 'consul-server' up with 'virtualbox' provider... #Bringing machine 'host-1' up with 'virtualbox' provider... #Bringing machine 'host-2' up with 'virtualbox' provider... |
Да, всё работает. Кластер из трёх машин создался за несколько минут в полностью автоматическом режиме. Мне больше не придётся делать это вручную.
Здесь должно было быть заключение…
Но в полночь оно в голову не лезет.
Разве что такое наблюдение. Я сужу по себе, конечно, но почему-то автоматизировать создание хостов — тяжело. И дело даже не в технологической части проблемы, а чисто в психологии. Машины же большие, как можно вообще пытаться их создавать не руками? И почему-то всегда кажется, что каждая машина — уникальная. Штучный товар.
Потом машина начинает жить своей жизнью. Софт на ней устаревает, появляется надобность создать похожую машину для чего-нибудь ещё, или мигрировать в другое облако, но уже никто не помнит, что на ней установлено, и как вообще она работает.
С другой стороны, если машину создавать из файла конфигурации — это меняет всё. Хосты становятся предсказуемыми, документированными, а создавать их можно где угодно и пачками. И Вагрант делает это возможным.
У меня на работе есть примеры как из одного, так и из другого лагеря. Например, CI сервера создаются программно и из конфигурации. Если нужно что-то обновить — я просто обновляю конфигурацию, удаляю старые сервера, и добавляю новые.
С другой стороны, образ виртуальных машин для тестов создавался руками год назад. Мой страшный сон — что его нужно будет пересоздать на свежем Windows Server. Какие сертификаты там должны быть, какие протоколы включены — я уже без понятия.
Все исходники к этому посту можно найти на гитхабе.
Здоровский набор статьей по консулу, спасибо
Не уловил только — в статье кластер консула ставился локально?
1) Если нет, непонятно почему не использовать для оркестрации описанные в статье тулы (ansible, chief) которые для этого и предназначены?
2) по ходу в твоей схеме на ремоут хост машине можно поставить не сам консул, а докер контейнер с консулом, а порт 8500 прокинуть на хост машину
Пожалуйста!
Весь кластер физически находился на локальной машине, в трёх виртуальных убунтах на VirtualBox. Ansible можно было бы использовать для конфигурации (Vagrant его поддерживает), но всё-таки фокус был именно на Vagrant, так что не в этот раз. Тут даже консул оказался почти случайно — просто он и логически связывает с предыдущим постом, и кластер с ним выглядит лучше и убедительнее, чем просто hello-world.
И да, с родным докер контейнер с консулом и docker-compose этот кластер тоже можно сделать. У меня в каком-то посте докерный консул даже мелькал (нашёл — http://dotsandbrackets.com/multi-host-docker-network-without-swarm-ru/, правда, тот контейнер всё равно в виртуалке. Но с проброшенным 8500м портом!).
Но опять же, весь фокус был на виртуальных машинах и vagrant. Даже если этот фокус получился смазанным 🙂
Very good article! I really enjoyed reading it!
Please, continue that way!
отличная статья, спасибо!