Свой облачный хостинг за 5 минут. Часть 2: Service Discovery
Привет Хабр! В предыдущей статье я рассказал как построить свой облачный хостинг за 5 минут, используя Ansible, Docker и Docker Swarm. В этой части я расскажу о том, как сервисы, запущенные в облаке, находят друг друга, как происходит балансировка нагрузки между ними и обеспечивается их отказоустойчивость.
Это вводная статья, здесь мы сосредоточимся на обзоре инструментов, которые будут решать проблему «обнаружения сервисов» в нашем облаке. В следующей части мы приступим к практике, поэтому я решил дать вам время по ближе ознакомиться с ними.
Проблема
Давайте разберем наиболее типичную проблему и ее распространенное решение — у нас есть веб-приложение и мы должны обеспечить балансировку нагрузки и его отказоустойчивость.
Мы можем запустить несколько копий нашего веб-приложения, которые будет мониторить Supervisor. Supervisor будет перезапускать наше веб-приложение, если возникнут какие-то ошибки, а также будет добавлять такие события в журнал. Проблему балансировки нагрузки решит установка Nginx. Конфигурация Nginx будет выглядеть примерно так:
upstream app {
server 192.168.1.2:8080 max_fails=3 fail_timeout=5s;
server 192.168.1.2:8081 max_fails=3 fail_timeout=5s;
server 192.168.1.2:8082 max_fails=3 fail_timeout=5s;
}
server {
location / {
proxy_pass http://app;
health_check;
}
}
Работать указанная конфигурация будет так — если в течении 5 секунд число неудачных попыток при обращении к одному из веб-приложений достигнет 3-ех, то такое приложение отметится как неработоспособное на 5 секунд (если оно упало с ошибкой, то Supervisor перезапустит его). Таким образом вся нагрузка равномерно распределиться только между рабочими копиями приложений.
Недостатки
На самом деле это хорошая конфигурация и если у вас немного приложений и нагрузка более-менее равномерная, тогда лучше использовать именно её.
Но мы строим облако, где будет запущено то или иное приложение — мы не знаем. Нагрузка у нас может меняться для разных сайтов/веб-приложений по разному, поэтому неплохо бы иметь возможность менять количество запущенных копий наших приложений в зависимости от ситуации. Другими словами — мы не можем заранее настроить Nginx/Apache/etc на такую конфигурацию.
Было бы круто, если бы Nginx и другие наши сервисы приспособились к динамической природе нашего облака. Решением именно этой задачи мы и займемся в этой статье.
Требования
Нам нужно место, где наши сервисы смогут регистрировать себя и получать информацию друг о друге. Docker Swarm, который мы начали использовать в предыдущей статье, «из коробки» умеет работать с etcd, Consul и Zookeeper.
Нам необходимо, что бы наши сервисы автоматически регистрировались и удалялись из вышеуказанных систем (мы же не будем обучать этому каждое приложение). Для этих целей мы используем Registrator (ниже рассмотрим его более подробно), который «из коробки» работает с Consul, etcd и SkyDNS 2 (поддержка Zookeeper в планах).
Наши сервисы должны иметь возможность находить друг друга с помощью DNS запросов. Эту задачу могут решить Consul и SkyDNS 2 (который работает в паре с etcd).
Мониторинг здоровья сервисов нам тоже необходим. Он доступен нам в Consul (который мы и будем использовать) «из коробки» и его поддерживает Registrator (он должен передавать информацию о том, как должен происходить мониторинг того или иного сервиса).
Последнее, но не менее важное — нужен сервис для автоматической конфигурации наших составляющих. Если мы запустили 10 копий одного веб-приложения и 20 копий другого, он должен понимать и немедленно реагировать на это (меняя конфигурацию Nginx, например). Выполнять эту роль будет Consul Template (ниже рассмотрим его более подробно).
Как вы видите, есть разные варианты решения нашей проблемы. Прежде, чем написать эту статью, я поработал на своей конфигурации чуть больше месяца и не встретил никаких проблем.
Consul
Из перечисленных выше вариантов (Consul, Zookeeper, etcd), Consul является наиболее самостоятельным проектом, который в состоянии решить нашу проблему обнаружения сервисов «из коробки».
Не смотря на то, что Consul, Zookeeper и etcd расположены здесь в одном ряду, я бы не стал их сравнивать между собой. Все 3 проекта реализуют распределенное key/value хранилище и на этом их общие черты заканчиваются.
Consul нас обеспечит DNS сервером, которого нет в Zookeeper и etcd (можно добавить с помощью SkyDNS 2). Более того, Consul даст нам мониторинг здоровья (которым не могут похвастаться ни etcd, ни Zookeeper), что также необходимо для полноценного Service Discovery.
В нагрузку с Consul мы получим Web UI (демо которого можно глянуть уже сейчас) и качественную официальную документацию.
Registrator
Registrator получает информацию от Docker'а о запуске/остановке контейнеров (через сокет-соединение, с помощью Docker API) и добавляет/удаляет их в/из Consul'a.
Информацию о том или ином сервисе Registrator автоматически получает на основе опубликованных портов и из переменных окружения Docker контейнера. Другими словами — это работает с любыми контейнерами, которые у вас есть и требует дополнительного конфигурирования только в том случае, если необходимо переопределить параметры полученные автоматически.
И раз все наши сервисы работают исключительно в Docker контейнерах (и сам Registrator в том числе), то в Consul'е всегда будет информация о всех запущенных сервисах нашего облака.
Это все конечно круто, но еще круче то, что Registrator может рассказать Consul'у, как проверять здоровье наших сервисов. Делается это с помощью тех же переменных окружения.
Если же используется Consul Key-value Store (который тоже поддерживается Registrator’ом и использует, например, Docker Swarm для сохранения информации о Docker нодах), такой функции нет.
Давайте рассмотрим пример:
$ docker run -d --name nginx.0 -p 4443:443 -p 8000:80 \
-e "SERVICE_443_NAME=https" \
-e "SERVICE_443_CHECK_SCRIPT=curl --silent --fail https://our-https-site.com" \
-e "SERVICE_443_CHECK_INTERVAL=5s" \
-e "SERVICE_80_NAME=http" \
-e "SERVICE_80_CHECK_HTTP=/health/endpoint/path" \
-e "SERVICE_80_CHECK_INTERVAL=15s" \
-e "SERVICE_80_CHECK_TIMEOUT=3s" \
-e "SERVICE_TAGS=www" nginx
После подобного запуска, список наших сервисов у Consul'а будет выглядеть следующим образом:
{
"services": [
{
"id": "hostname:nginx.0:443",
"name": "https",
"tags": [
"www"
],
"address": "192.168.1.102",
"port": 4443,
"checks": [
{
"script" : "curl --silent --fail https://our-https-site.com",
"interval": "5s"
}
]
},
{
"id": "hostname:nginx.0:80",
"name": "http",
"tags": [
"www"
],
"address": "192.168.1.102",
"port": 8000,
"checks": [
{
"http": "/health/endpoint/path",
"interval": "15s",
"timeout": "3s"
}
]
},
...
]
}
Как вы видите, на основе опубликованных портов Registrator сделал вывод, что зарегистрировать надо 2 сервиса (http и https). Более того, у Consul'a теперь есть вся необходимая информация о том, как проверять здоровье этих сервисов.
В первом случае будет выполняться команда »curl --silent --fail our-https-site.com» каждые 5 секунд и результат проверки будет зависеть от кода выхода данной комманды.
Во втором случае — каждые 15 секунд Consul будет дергать переданный нами URL. Если код ответа сервера будет 2xx, то наш сервис «здоров», если 429 Too Many Requests, то в «экстренном состоянии», если все остальное, то «земля ему пухом».
Больше примеров и более подробную информацию вы может почерпнуть из официальной документации.
Consul Template
Мы решили где хранить информацию о всех сервисах нашего облака, а также как она будет туда попадать и автоматически там обновляться. Но мы еще не разобрались, как же будем получать информацию от туда и как, в последствии, будем её передавать нашим сервисам. Именно этим и будет заниматься Consul Template.
Для этого надо взять конфигурационный файл нашего приложения (которое мы хотим настроить) и сделать из него шаблон, согласно правилам HashiCorp Configuration Language.
Давайте рассмотрим простой пример с конфигурационным файлом Nginx:
upstream app {
least_conn;
# list of all healthy services
{{range service "tag1.cool-app" "passing"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60s weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
}
...
После того, как мы объясним Consul Template где находится данный шаблон, куда положить результат и какую комманду выполнить (он это тоже умеет) при его изменении (в данном случае перезагрузить Nginx), начнётся магия. В данном случае Consul Template получит адреса и номера портов всех копий приложения «cool-app», которые помечены тегом «tag1» и находятся в «здоровом» состоянии и добавит их в конфигурационный файл. Если таких приложений нет, тогда, как вы уже догадались, останется все, что находится после {{else}}.
При каждом добавлении и удалении сервиса «cool-app» с тегом «tag1» конфигурационный файл будет перезаписываться, а после этого Nginx будет перезагружен. Все это происходить автоматически и не требует вмешательства, мы просто запускаем нужное количество копий нашего приложения и не о чём не беспокоимся.
Больше примеров вы можете найти в официальной документации.
Заключение
На сегодняшний день существует достаточное количество инструментов для решения проблемы обнаружения сервисов, но не так много средств, которые могли бы решить данную проблему «из коробки» и сразу обеспечить нас всем необходимым.
В следующей части, которая будет совсем скоро, я опубликую набор сценариев для Ansible, которые сконфигурируют за нас все вышеописанные инструменты и мы сможем приступить в практике.
На этом все. Всем спасибо за внимание. Стабильных вам облаков и удачи!
P.S. Я ищу дизайнера, backend и frontend программистов в стартап, подробности у меня в профиле.