Допустим, есть у нас распределённое приложение, в которое входят два вида сервисов: web
и db
. Для большей надёжности они запускаются сразу в нескольких экземплярах, на разных хостах, иногда приходят в онлайн, иногда уходят, какие-то подвисли, какие-то ещё нет… В общем, хаос. Как в таком случае отдельно взятому сервису web
отыскать себе живой db
?
Самое очевидное решение — придумать какое-нибудь отдельное хранилище типа ключ-значение, в котором сервисы будут себя регистрировать при запуске: кто такой и куда слать запросы. Правда, сервисы иногда будут выключаться… Значит, перед выключением они должны себя «разрегистрировать».
А если сервис «упал», или сети не стало, он же тогда не сможет себя удалить из списков… Ну тогда можно ввести ещё один сервис, который будет проверять, живы ли ещё все остальные. А если..
Всего два абзаца текста, а решение с выделенным хранилищем уже превращается в отдельный проект. Всё-таки, поиск и регистрация сервисов — это нетривиальная задача.
Но её и не обязательно решать самому, когда для этого уже есть инструменты. Например, Consul.
Поиск и регистрация сервисов с Consul
В прошлый раз мы разбирались, как можно использовать хранилище Consul для конфигурации приложения. Но хранить данные — не единственное, что он умеет. Колсул понимает концепцию сервисов приложения и умеет их регистрировать через своих агентов, чтобы затем раздавать списки всем желающим.
Вот как это работает. Мы устаналиваем Консул-агент на каждом хосте, где планируют жить сервисы. Задача этого агента — знать, какие сервисы рядом с ним работают, и живы ли они. Потом эту информацию он отдаёт Консул-серверу, а тот, собрав данные со всего кластера, делится ей со всеми через DNS, HTTP API запросы или даже через UI.
План
Сегодня мы построим маленький кластер таких агентов. Подсунем им пару-тройку сервисов для знакомства, а затем попробуем собрать списки сервисов через HTTP или DNS запросы.
Я думаю, что трёх виртуальных машин для этого хватит: одна для Consul сервера, и две других для агентов с сервисами. Так как у меня локально уже установлены VirtualBox и docker-machine, я буду использовать их для надругательства над хостами, но любой другой гипервизор с провизором виртуальных машин тоже бы подошли.
Ещё нам нужны несколько сервисов в качестве морских свинок. Конечно, можно было бы просто установить nginx и mysql и рассказать о них Консул-агенту. Но на самом деле Консулу абсолютно плевать, установлен ли сервис на самом деле. Ему задают несколько правил, по которым тот проверяет, здоров сервис, или уже того, и это всё, что его интересует. В следующем посте мы посмотрим, как можно сконфигурировать эти правила, но для сегодняшнего эксперимента мы можем вообще не задать ни одной, что делает сервис перманентно живым. Даже, если его нет.
Но куча слов написано, ничего не сделано — пора за работу.
Создаём кластер
Установка Consul сервера
Начнём с начала: нам нужна виртуальная машина. Следующей командой можно создать вполне себе готовый Linux хост по имени consul-server
, используя VirtualBox и docker-machine:
1 2 3 |
docker-machine create -d virtualbox consul-server # Running pre-create checks... # Creating machine... |
Это займём от силы минуту-две, и в результате получится готовая машина, в которую мы тут же зайдём по SSH для «установки» Consul:
1 2 3 4 5 6 7 8 9 10 |
$ docker-machine ssh consul-server docker@consul-server:~$ wget https://releases.hashicorp.com/consul/0.7.5/consul_0.7.5_linux_amd64.zip # Connecting to releases.hashicorp.com (151.101.21.183:443) # .. docker@consul-server:~$ unzip consul_0.7.5_linux_amd64.zip # Archive: consul_0.7.5_linux_amd64.zip # ... docker@consul-server:~$ ./consul -v # Consul v0.7.5 |
Легкотня.
Настройка сервера
Сейчас будет немного сложнее. То есть, всё просто, но очень много.
Наш Консул должен уметь три вещи:
- выглядеть как сервер,
- раздавать какой-нибудь UI,
- принимать входящие соединения от других Консул-агентов, чтобы собрать кластер.
Во-первых, чтобы сделать из Consul агента Consul сервер нужно просто передать -server
параметр при запуске. Я видел документацию, этого действительно хватает.
Во-вторых, -ui
параметр включает веб-UI. Серьёзно. Правда, слушать он будет на 127.0.0.1
, что для сервиса внутри виртуальной машины — неудобство. Если найти внешнюю апйишку этой машины, то её можно будет передать через -client
параметр, и тогда UI станет доступен снаружи.
Найти же внешнюю айпишку — просто. Изнутри её можно узнать, посмотрев настройки eth1 сетевого интерфейса (например, ifconfig eth1
). А снаружи — через docker-machine ip consul-server
.
В-третьих, сервер должен уметь принимать запросы от других Консул-агентов. Внешнюю айпишку из предыдущего шага можно так же передать в качестве -advertise %ip%
параметра, и задача решена.
Наконец, Consul откажется запускаться, если ему не указать, где хранить данные. Чтобы его заткнуть, хватит какой-нибудь временной папки, переданной через -data-dir
параметр.
Итоговая команда запуска сервера будет выглядеть вот так:
1 2 3 4 |
./consul agent -server \ -ui -client 192.168.99.104 \ -data-dir /tmp/consul \ -advertise 192.168.99.104 |
Запускаем, даём серверу немного времени назначить и выиграть выборы лидера кластера, и идём любоваться на web UI по айпишке виртуальной машины и порту 8500:
«Nodes» страница показывает все хосты, входящие в кластер. Пока там только один, но это не надолго.
Установка Consul агентов
Пришло время сделать серверу компанию. Создадим-ка ещё два хоста — host-1
и host-2
и установим обычные Консул-агенты на нём. Процесс полностью идентичен установке сервера вплоть до момента его запуска, так что начнём сразу оттуда.
В конфигурации обычного агента есть несколько отличий от серверной. Во-первых, -server
больше не нужен. Во-вторых, UI нам тоже больше не нужен — он же есть у сервера.
В-третьих, так как агент должен подсоединиться к существующему кластеру, ему нужно указать айпишку кого-то, кто уже там. consul-server — и есть кластер, его адрес — 192.168.99.104
, так что передаём его в -retry-join
параметр, и работа сделана.
Итоговые команды запуска агентов выглядят так:
1 2 3 4 5 6 |
# host-1 docker@host-1:~$ ./consul agent -retry-join 192.168.99.104 -advertise 192.168.99.105 -data-dir /tmp/consul # ... # host-2 docker@host-2:~$ ./consul agent -retry-join 192.168.99.104 -advertise 192.168.99.106 -data-dir /tmp/consul # ... |
После запуска оба новых агента и хоста появятся в UI сервера:
Та-дам! Консул-кластер готов. Дело за сервисами.
Регистрация сервисов
Кто-то должен рассказать Consul агентам, что на их хостах завелись сервисы, чтобы те могли доложить об этом Consul серверам. Есть несколько способов, как это сделать, но для простоты мы будем статически задавать «местные» сервисы через конфигурационные файлы.
Конфигурация — это обычный JSON. Каждому сервису в нём можно задать имя, айпишку, порт и список проверок, определяющих, жив ли сервис. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "service": { "name": "redis", "tags": ["primary"], "address": "", "port": 8000, "enableTagOverride": false, "checks": [ { "script": "/usr/local/bin/check_redis.py", "interval": "10s" } ] } } |
Но на самом деле имя — единственный обязательный параметр в этой конфигурации, так что мы можем создать какой-нибудь services.json
только с именами и передать его агентам на обоих хостах. Например:
1 2 3 4 5 6 7 |
{ "services": [{ "name": "web" }, { "name": "db" }] } |
1 2 3 4 5 |
docker@host-2:~$ ./consul agent \ -retry-join 192.168.99.104 \ -advertise 192.168.99.106 \ -data-dir /tmp/consul \ -config-file services.json |
После этого мы можем снова пойти на UI сервера, но в этот раз заглянуть на страницу «services»:
Теперь там аж пять сервисов: 2 web, 2 db и сам Consul. Все сервисы отмечены как доступные, так что сторонние приложения (да и сами сервисы) могут делать поисковые запросы к Consul, чтобы узнать, где кто находится.
Делаем поисковые запросы
Самый простой поисковый запрос мы уже попробовали: сходили на UI и посмотрели своими глазами. Есть и более масштабируемый способ — сделать запрос по HTTP API или DNS.
Поиск сервисов через HTTP API
Мы можем получить имена всех сервисов кластера такой простой командой:
1 2 3 4 5 6 |
curl http://192.168.99.104:8500/v1/catalog/services?pretty { "consul": [], "db": [], "web": [] } |
Конечно, кроме имён тут ничего нет, но для адресов есть другая точка входа:
1 2 3 4 5 6 7 8 9 10 11 |
curl http://192.168.99.104:8500/v1/catalog/service/db?pretty #[ # { # "Node": "host-1", # "Address": "192.168.99.105", # ... # }, # { # "Node": "host-2", # "Address": "192.168.99.106", #... |
Здорово, правда? Через чистый HTTP можно разузнать всё о сервисах, которые работают в данном кластере, и как к ним доступиться. Если какой-то сервис упадёт, то его не будет и в результатах запроса.
Поиск сервисов через DNS
Consul сервер ещё может подрабатывать и DNS сервисом. Тот слушает на порту 8600, и если отправить ему сервис-запрос через какую-нибудь dig
утилиту, то можно узнать много интересного.
Допустим, нам всё ещё интересно, где находится db
сервис:
1 2 3 4 5 6 7 8 9 10 |
dig @192.168.99.104 -p 8600 db.service.consul SRV #... #;; ANSWER SECTION: #db.service.consul. 0 IN SRV 1 1 0 host-1.node.dc1.consul. #db.service.consul. 0 IN SRV 1 1 0 host-2.node.dc1.consul. # #;; ADDITIONAL SECTION: #host-1.node.dc1.consul. 0 IN A 192.168.99.105 #host-2.node.dc1.consul. 0 IN A 192.168.99.106 #... |
В ответе есть и полные имена сервисов, и адреса хостов, на которых они живут. Если бы мы запустили этот же запрос ещё раз, то порядок сервисов в ответе стал бы другим. Эту особенность можно использовать для балансировки нагрузки.
Итого
Сегодня мы сделали много всего: создали виртуальные машины, настроили Консул-кластер, зарегистрировали в нём сервисы и поискали их через HTTP API и DNS. И хотя это было много отдельных шагов, все они были относительно несложные: запустить экзешник, найти внешний айпи адрес, задать JSON с сервисами, и т.п. — не ракету построить.
Конечно, задать сервис в конфигурации через "name": "web"
— не то же самое, что действительно установить nginx на хосте. Но Консулу, на самом деле, по барабану. Если его проверки говорят, что с сервисом всё в порядке, значит он существует, и с ним действительно всё в порядке. Как эти проверки выглядят, мы и посмотрим в следующий раз.
А для первого консула не нужно задавать опцию -bootstrap? Без неё у меня по кругу валило
{code}
2019/11/12 14:49:39 [ERR] agent: failed to sync remote state: No cluster leader
2019/11/12 14:49:48 [ERR] agent: coordinate update error: No cluster leader
{code}
когда задал опцию -bootstrap
$ ./consul agent -server -ui -client 192.168.99.109 -data-dir /tmp/consul -advertise 192.168.99.109 -bootstrap
то кластер создался.
Возможно, теперь этот параметр действительно нужен. Я практически уверен, что когда пост создавался, оно работало без него, но всё-таки прошло 2 года — в нашей индустрии практически вечность.