Service discovery с помощью Consul

Consul - распределенная система обнаружения и мониторинга сервисов.

Распределенность достигается с помощью протокола gossip, что переводится как "сплетни". Концепцию распространения сплетен можно проиллюстрировать с помощью аналогии служащих распускающих слухи. Предположим, каждый час офисные работники собираются вокруг куллера. Каждая пара сотрудников встречается случайным образом и делится последними сплетнями. В начале дня, Алиса рассказывает новый слух: она комментирует Бобу, что она считает, что Чарли красит свои усы. На следующем заседании, Боб рассказывает это Дэйву, в это же время Алиса повторяет идею Еве. После каждого рандеву количество людей, которые слышали слух, увеличивается примерно в два раза (хотя это не учитывает рассказ сплетен дважды одному и тому же лицу. Возможно, Элис пытается рассказать эту историю Фрэнку, но Фрэнк уже слышал её от Дэйва). Компьютерные системы обычно реализуют этот тип протокола с формой случайного выбора "сверстников": с заданной частотой, каждая машина выбирает другую машину в случайном порядке и распространяют горячие слухи.

Обнаружение сервисов реализовано с помощью протокола DNS. Т.е. зарегистрированный сервис будет доступен по доменному имени. Например:

consul.service.domain.ltd
Зачем?

По мере роста инфраструктуры возникает проблема с обнаружением сервисов и организации взаимодействия между ними. Чем больше сервисов связаны между собой, тем маcштабнее она становится. Первое, что приходит в голову - использовать dns сервер. С помощью него, возможно сконфигурировать статическую инфраструктуру. Как же быть с динамической?

Ответ на этот вопрос попыталась дать компания HashiCorp. Consul - инструмент для обнаружения сервисов, балансировки нагрузки, мониторинга и согласованного хранилища данных (В виде ключ - значение).

Установка

В терминале перейдите в папку /usr/local/bin и загрузите Consul:

cd /usr/local/bin
wget https://releases.hashicorp.com/consul/0.7.1/consul_0.7.1_linux_amd64.zip

Теперь, распакуйте архив используя unzip, после чего удалите его:

unzip *.zip
rm *.zip

Начиная с версии 0.7.0 consul web ui включен в состав пакета. Параметр -ui активирует встроенный интерфейс.

На этом установка завершена и consul готов к настройке.

Настройка

Consul возможно запустить в трех режимах: bootstrap, server, client. Для того, чтобы создать кластер хотя бы один сервер должен быть запущен в режиме boostrap. Разница между режимами server и client заключается в участии узла в consensus quorum при обновлении состояния кластера (Соответсвенно server учавствует, client - нет). Перейдем к базовой настройке:

Создайте папку /etc/consul:

mkdir /etc/consul

В ней будут храниться 3 файла кофигурации, по одному для каждого режима:

/etc/consul/bootstrap.conf:

{
    "protocol": 3,
    "bootstrap": true,
    "server": true,
    "data_dir": "/var/consul/",
    "log_level": "INFO",
    "leave_on_terminate": true,
}

/etc/consul/server.conf:

{
    "protocol": 3,
    "bootstrap": false,
    "server": true,
    "data_dir": "/var/consul",
    "log_level": "INFO",
    "leave_on_terminate": true,
    "start_join": ["consul.service.domain.ltd"]
}

/etc/consul/client.conf:

{
    "protocol": 3,
    "bootstrap": false,
    "server": false,
    "data_dir": "/var/consul",
    "log_level": "INFO",
    "leave_on_terminate": true,
    "start_join": ["consul.service.domain.ltd"]
}

Обновите список серверов доменных имен отредактировав файл /etc/resolv.conf:

nameserver IP_ADDRESS
nameserver BOOTSTRAP_SERVER_IP_ADDRESS
nameserver NET_PROVIDER_IP_ADDRESS

* Подставте ваши IP адреса следующим образом: IP_ADDRESS - локальный адрес сервера; BOOTSTRAP_SERVER_IP_ADDRESS - адрес сервера в режиме bootstrap; NET_PROVIDER_IP_ADDRESS - адрес доменного сервера вашего провайдера

Создайте пользователя consul используя команду:

adduser consul

Далее создайте директорию, в которой consul будет сохранять персистентную информацию и установите её владельцем ранее созданного пользователя:

mkdir /var/consul
chown consul:consul /var/consul

Последним шагом, необходимо создать systemd unit файл для запуска: consul.service:

[Unit]
Description=Consul Service Discovery

[Service]
User=consul
Environment="CONSUL_DOMAIN=domain.ltd"
Environment="CONSUL_DC=dev"
Environment="CONSUL_CONF_FILE=bootstrap.conf"
Restart=always
RestartSec=60
ExecStart=/usr/local/bin/consul agent -ui -domain ${CONSUL_DOMAIN} -dc ${CONSUL_DC} -client 0.0.0.0 -config-file /etc/consul/${CONSUL_CONF_FILE}
ExecStartPost=/bin/bash -c "until curl http://127.0.0.1:8500/v1/agent/services; do sleep 2; done"
ExecStop=/usr/local/bin/consul leave

[Install]
WantedBy=multi-user.target

В зависимости от количества серверов в кластере с помощью unit'а вы можете задавать необходимое количество узлов в режиме клиента и сервера, а также сколько из них должно учавствовать в создании кластера.

Bootstrap Consul кластера

В таблице ниже указаны размеры кворума для кластеров различных размеров. Рекомендуемый размер от 3 до 5 серверов. Кластер с 1 узлом очень нестабилен и может привести к неконсистентности или потере данных в случае падений.

Узлы Размер Кворума Толерантность к неудачам
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 3
7 4 3

Независимо от выбранного размера кластера первым делом необходимо выполнить bootstrap. Сделать это можно запустив на сервере с адресом указанным как BOOTSTRAP_SERVER_IP_ADDRESS unit consul.service с конфигурационым файлом bootstrap.conf. После выполнения этого шага можно запускать все остальные узлы и они автоматически подключатся к кластеру используя доменное имя consul.service.domain.ltd.

Web интерфейс

Если boostrap прошел успешно на http://IP_ADDRESS:8500/ui consul отобразит список сервисов: consul ui

Основная роль интерфейса - отображение актуального состояния кластера. Функционал по управлению сервисами в нем отсутствует.

Регистрация/Дерегистрация сервиса

Consul предоставляет REST API. По умолчанию оно доступно по адресу IP_ADDRESS:8500.

Регистрация сервиса производится по адресу http://IP_ADDRESS:8500/v1/agent/service/register с помощью PUT запроса:

{
  "Datacenter": "dev",
  "Node": "foobar",
  "Address": "192.168.10.10",
  "Service": {
    "ID": "redis1",
    "Service": "redis",
    "Tags": [
      "master",
      "v1"
    ],
    "Address": "127.0.0.1",
    "Port": 8000
  },
  "Check": {
    "Node": "foobar",
    "CheckID": "service:redis1",
    "Name": "Redis health check",
    "Notes": "Script based health check",
    "Status": "passing",
    "ServiceID": "redis1"
  }
}

или в более упрощенном варианте:

{
  "ID": "regis1",
  "Name": "regis1",
  "Tags": ["master","v1"],
  "Address": "192.168.10.10",
  "Port": 8000
}

Полностью запрос регистрации с помощью утилиты curl возможно выполнить следующим образом:

curl -d '{"ID": "redis1","Name": "redis1","Tags": ["master","v1"],"Address": "192.168.10.10","Port": 8000}' http://IP_ADDRESS:8500/v1/agent/service/register

Дерегистрация сервиса производится по адресу http://IP_ADDRESS:8500/v1/agent/service/deregister с добавлением ID сервиса в конец. Например, чтобы дерегистрировать сервис redis1 с помощью утилиты curl:

curl http://IP_ADDRESS:8500/v1/agent/service/deregister/redis1

В случае, если на двух узлах будет зарегистрирован сервис с одинаковым названием consul будет использовать DNS round-robin. Причем IP адреса будут отдаваться базируясь на нагруженности, метрики и других показателях. Другими словами базовый load balancing доступен сразу и без дополнительных насторек.

Недостатки выявленные в ходе использования

В ходе использования consul на production системе, состоящей из 5 серверов выявлено было всего лишь 2 недостатка:

  1. По умолчанию TTL доменного имени равен 0 секунд. Это сделано для того, чтобы в случае миграции сервиса на другой IP переключение работало мгновенно. Однако при использовании высоконагруженной базы данных consul перестал транслировать доменное имя в IP адрес.
  2. Проблема заключается в некоректном выходе из кластера узла. После нескольких таких выходов кластер пытается определить нового лидера, но по какой-то причине не может этого сделать. Помогает только полный перезапуск. Проявляется только после нескольких аварийных остановок consul узла.