Mesos. Cluster Management
Apache Mesos — это централизованная отказоустойчивая система управления кластером. Она разработана для распределенных компьютерных сред c целью обеспечения изоляции ресурсов и удобного управления кластерами подчиненных узлов (mesos slaves). Это новый эффективный способ управления серверной инфраструктурой, но и, как любое техническое решение, не «серебряная пуля».
В некотором смысле суть его работы противоположная уже традиционной виртуализации — вместо деления физической машины на кучу виртуальных, Mesos предлагает их объединять в одно целое, в единый виртуальный ресурс.
Mesos распределяет ресурсы CPU и памяти в кластере для задач в похожей манере, как ядро Linux выделяет ресурсы железа между локальными процессами.
Представим себе, что есть необходимость выполнить различные типы задач. Для этого можно выделить отдельные виртуальные машины (отдельный кластер) для каждого типа. Эти виртуальные машины, вероятно, не будут полностью загруженными и некоторое время будут простаивать, то есть не будут работать с максимальной эффективностью. Если же все виртуальные машины для всех задач объединить в единый кластер, мы можем повысить эффективность использования ресурсов и параллельно с тем повысить скорость их выполнения (в случае если задачи краткосрочные или виртуальные машины не загружены полностью все время). Следующий рисунок, надеюсь, прояснит сказанное:
Но это далеко не все. Кластер Mesos (с фреймворком к нему) способен пересоздавать отдельные ресурсы, в случае их падения, масштабировать ресурсы вручную или автоматически при определенных условиях и т.п.
Пройдемся по компонентам Mesos кластера.
Mesos Masters
Главные контролирующие серверы кластера. Собственно они и отвечают за предоставление ресурсов, распределения задач между действующими Mesos слейвами. Для обеспечения высокого уровня доступности их должно быть несколько и желательно нечетное количество, но конечно больше 1. Это обусловлено уровнем кворума. Активным мастером (лидером) в определенный момент времени может быть только один сервер.
Mesos Slaves
Сервисы (узлы), предоставляющие мощности для выполнения задач. Задачи могут выполняться как в собственных Mesos-контейнерах, так и в Docker.
Frameworks
Сам Mesos — это только «сердце» кластера, он предоставляет только среду для работы (выполнения) задач. Всю логику запуска задач, мониторинг их работы, масштабирование и т.п. выполняют фреймворки. По аналогии с Linux, это такая init/upstart-система для запуска процессов. Мы будем рассматривать работу фреймворка Marathon, который предназначен больше для запуска постоянных задач (долгосрочная работа серверов и т.п.) или краткосрочных. Для запуска задач по графику стоит воспользоваться другим фреймворком — Chronos (по аналогии с cron).
В общем, фреймворков достаточно большое количество и вот самые известные среди них:
Aurora (умеет как запускать задачи по графику, так и запускать долгосрочные задачи). Разработка компании Twitter.
Hadoop
Jenkins
Spark
Torque
ZooKeeper
Демон, отвечающий за координацию Mesos Masters узлов. Он проводит выборы мастера при наличии кворума. Другие узлы кластера получают адрес текущего мастера запросом на группу zookeeper нод типа zk://master-node1:2138, master-node2:2138, master-node3:2138/mesos. Mesos Slaves, в свою очередь, также подключаются только к текущему мастера, используя аналогичный запрос. В нашем туториале, они тоже будут устанавливатся на ноды с Mesos мастерами, но могут также жить и отдельно.
Эта статья будет скорее практической: повторяя за мной вы тоже на выходе сможете получить рабочий Mesos-кластер.
Для будущих узлов я выбрал следующие адреса:
mesos-master1 10.0.3.11
mesos-master2 10.0.3.12
mesos-master3 10.0.3.13
---
mesos-slave1 10.0.3.51
mesos-slave2 10.0.3.52
mesos-slave3 10.0.3.53
Т.е. 3 мастера и 3 слейва. На мастерах также будет находиться фреймворк Marathon, который, по желанию, можно разместить на отдельном узле.
На этапе тестирования лучше выбирать виртуальные машины, работающие на платформах аппаратной виртуализации (VirtualBox, XEN, KVM), ведь, скажем, установить Docker в LXC контейнер пока не очень возможно или затруднительно. Docker мы будем использовать для изоляции задач, запущенных на Mesos слейвах.
Поэтому, имея 6 готовых серверов (виртуальных машин) с Ubuntu 14.04, мы готовы к бою.
MESOS MASTERS / MARATHON INSTALLATION
Выполняем полностью аналогичные действия на всех 3-х Mesos мастер-серверах.
apt-get install software-properties-common
Добавляем Mesos / Marathon-репозитории:
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E56151BF
DISTRO=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
CODENAME=$(lsb_release -cs)
echo "deb http://repos.mesosphere.com/${DISTRO} ${CODENAME} main" | \
sudo tee /etc/apt/sources.list.d/mesosphere.list
Mesos и Marathon требуют Java-машину для работы. Поэтому установим последнюю от Oracle:
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
Проверим работает ли Java:
java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
Хотя, кажется, OpenJDK также подходит.
На каждый из будущих мастеров проинсталлируйте Mesos и Marathon:
apt-get -y install mesos marathon
Между прочим, фреймворк Marathon написан на Scala.
По зависимостям также будет установлено Zookeeper. Укажем ему адреса наших мастер нод:
vim /etc/mesos/zk
zk://10.0.3.11:2181,10.0.3.12:2181,10.0.3.13:2181/mesos
2181 — порт на котором работает Zookeeper.
Для каждой ноды мастера выберем уникальный ID:
vim /etc/zookeeper/conf/myid
1
Для второго и третьего сервера я выбрал 2 и 3 соответственно. Номера можно выбирать от 1 до 255.
Редактируем /etc/zookeeper/conf/zoo.cfg:
vim /etc/zookeeper/conf/zoo.cfg
server.1 = 10.0.3.11: 2888: 3888
server.2 = 10.0.3.12: 2888: 3888
server.3 = 10.0.3.13: 2888: 3888
1,2,3 — ID, что мы указали в /etc/zookeeper/conf/myid каждого сервера.
2888 — порт, использующий Zookeeper для коммуникаций с выбранным мастером, а 3888 — для проведения новых выборов, если с действующим мастером то случилось.
Переходим к настройке кворума. Кворум — это минимальное количество рабочих узлов, необходимо для выбора нового лидера. Эта опция необходима для предотвращения Split-brain кластера. Представим себе, что кластер состоит только из 2-х серверов с кворумом 1. В таком случае только наличие 1 рабочего сервера достаточно для выбора нового лидера: собственно каждый сервер может сам выбрать себе лидером. В случае падения одного из серверов такое поведение более чем логична. Однако, что будет, когда только сетевая связь между ними нарушится? Правильно: вероятный вариант, когда каждый из серверов по очереди перетягивать на себя основной трафик. В случае если же такой кластер состоит из баз — то возможна вообще потеря эталонных данных и будет даже не понятно с чего восстанавливаться.
Итак, наличие 3 серверов — это минимум для обеспечения высокой доступности. Значение кворума в таком случае — 2. Если только один сервер перестанет быть доступным — остальные 2 смогут кого-то выбрать между собой. Если же два сервера испытывают поломки или узлы не видеть друг друга в сети — группа, для сохранения данных, вообще не будет проводить избрание нового мастера до достижения необходимого кворума (появления одного из серверов в сети).
Почему не имеет особого смысла выбирать 4 (четное количество) серверов? Потому что в таком случае, как и в случае с 3-ому серверами, только отсутствие одного сервера некритична для работы кластера: падение второго сервера будет фатальной для кластера по причине возможного Split-brain. Но в случае 5 серверов (и уровне кворума 3) уже падения 2 серверов не поломают кластер. Вот так. Так что лучше всего выбирать кластеры с 5 и более узлов, а то кто знает, что может произойти во время технического обслуживания на одном из хостов.
В случае, если Zookeeper будет вынесен отдельно, Mesos-мастеров может быть любое, в т.ч. и парное, количество.
Указываем одинаковый уровень кворума для мастеров:
echo "2" > /etc/mesos-master/quorum
Указываем IP соответственно для каждого узла:
echo 10.0.3.11 | tee /etc/mesos-master/ip
Если у серверов отсутствует доменнейм — копируем IP-адрес для использования ее в качестве имени хоста:
cp /etc/mesos-master/ip /etc/mesos-master/hostname
Аналогично для 10.0.3.12 и 10.0.3.13.
Настраиваем фреймворк Marathon. Создадим директорию для конфигурационных файлов и скопируем в нее хостнейм:
mkdir -p /etc/marathon/conf
cp /etc/mesos-master/hostname /etc/marathon/conf
Скопируем для Marathon настройки Zookeeper:
cp /etc/mesos/zk /etc/marathon/conf/master
cp /etc/marathon/conf/master /etc/marathon/conf/zk
Последний несколько отредактируем:
vim /etc/marathon/conf/zk
zk://10.0.3.11:2181,10.0.3.12:2181,10.0.3.13:2181/marathon
И наконец запрещаем загрузки демону mesos-slave на мастерах:
echo manual | sudo tee /etc/init/mesos-slave.override
stop mesos-master
Перезапускаем сервисы на всех узлах:
restart mesos-master
restart marathon
Откроем веб-панель Mesos на порту 5050:
В случае, если лидером был избран другой сервер, состоится переадресация на другой сервер:
Marathon имеет приятный темный интерфейс и работает он на порту 8080:
MESOS SLAVES INSTALLATION
Как и для установки Mesos мастеров, добавим репозитории и установим необходимые пакеты:
apt-get install software-properties-common
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E56151BF
DISTRO=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
CODENAME=$(lsb_release -cs)
echo "deb http://repos.mesosphere.com/${DISTRO} ${CODENAME} main" | \
tee /etc/apt/sources.list.d/mesosphere.list
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
Проверяем коректно ли была установлена Java:
java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
Необходимо также запретить запуск процессов Zookeeper и Mesos-master, т.к. они здесь не нужны:
echo manual | sudo tee /etc/init/zookeeper.override
echo manual | sudo tee /etc/init/mesos-master.override
stop zookeeper
stop mesos-master
Укажем домены и IP-адреса для слейва:
echo 10.0.3.51 | tee /etc/mesos-slave/ip
cp /etc/mesos-slave/ip /etc/mesos-slave/hostname
Аналогичное действие также необходимо выполнить для 10.0.3.52 и 10.0.3.53 (конечно с отдельным адресом для каждого сервера).
И описать все мастера в /etc/mesos/zk:
vim /etc/mesos/zk
zk://10.0.3.11:2181,10.0.3.12:2181,10.0.3.13:2181/mesos
Слейв будут время от времени опрашивать Zookeeper на предмет текущего лидера и подключаться к нему, предоставляя свои ресурсы.
Во время запуска слейва, может возникнуть следующая ошибка:
Failed to create a containerizer: Could not create MesosContainerizer:
Failed to create launcher: Failed to create Linux launcher: Failed to mount
cgroups hierarchy at '/sys/fs/cgroup/freezer': 'freezer' is already
attached to another hierarchy
В таком случае нужно внести изменения в /etc/default/mesos-slave:
vim /etc/default/mesos-slave
...
MESOS_LAUNCHER=posix
...
И запустить mesos-slave снова:
start mesos-master
Если все будет выполнено верно, Слейв подключатся в качестве ресурсов для текущего лидера:
Кластер готов! Запустим какую-то задачу на Marathon. Для этого откроем Marathon на любом мастер-узле, нажмем синюю кнопку Create Application и введем все как на рисунке:
Задача начала выполняться:
В веб-панели Mesos сразу появится активная задача, которую поставил фреймворк, и история завершенных задач. Дело в том, что эта задача будет завершаться и начинаться снова, ведь она краткосрочная. Именно поэтому в Completed Tasks будет приведен полный листинг всех старых задач:
Эту же задачу можно выполнить используя API Marathon, описав ее в формате JSON:
cd /tmp
vim hello2.json
{
"id": "hello2",
"cmd": "echo hello; sleep 10",
"mem": 16,
"cpus": 0.1,
"instances": 1,
"disk": 0.0,
"ports": [0]
}
curl -i -H 'Content-Type: application/json' -d@hello2.json 10.0.3.11:8080/v2/apps
HTTP/1.1 201 Created
Date: Tue, 21 Jun 2016 14:21:31 GMT
X-Marathon-Leader: http://10.0.3.11:8080
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Location: http://10.0.3.11:8080/v2/apps/hello2
Content-Type: application/json; qs=2
Transfer-Encoding: chunked
Server: Jetty(9.3.z-SNAPSHOT)
{"id":"/hello2","cmd":"echo hello; sleep 10","args":null,"user":null,"env":{},"instances":1,"cpus":0.1,"mem":16,"disk":0,"executor":"","constraints":[],"uris":[],"fetch":[],"storeUrls":[],"ports":[0],"portDefinitions":[{"port":0,"protocol":"tcp","labels":{}}],"requirePorts":false,"backoffSeconds":1,"backoffFactor":1.15,"maxLaunchDelaySeconds":3600,"container":null,"healthChecks":[],"readinessChecks":[],"dependencies":[],"upgradeStrategy":{"minimumHealthCapacity":1,"maximumOverCapacity":1},"labels":{},"acceptedResourceRoles":null,"ipAddress":null,"version":"2016-06-21T14:21:31.665Z","residency":null,"tasksStaged":0,"tasksRunning":0,"tasksHealthy":0,"tasksUnhealthy":0,"deployments":[{"id":"13bea032-9120-45e7-b082-c7d3b7d0ad01"}],"tasks":[]}%
В этом (и предыдущем) примере, будет выводиться hello, после чего будет задержка в 10 секунд и все это по кругу. Задаче будет выделено 16МБ памяти и 0.1 CPU.
Вывод запущенных задач можно наблюдать, нажав на ссылку Sandbox в последней колонке основной панели действующего Mesos-мастера:
DOCKER
Для лучшего уровня изоляции и дополнительных возможностей в Mesos была интегрирована поддержка Docker. Кто с ним не знаком — советую предварительно сделать это.
С активацией Docker в Mesos также особо нет ничего сложного. Сначала необходимо проинсталлировать сам Docker на все слейвах кластера:
apt-get install apt-transport-https ca-certificates
apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo "deb https://apt.dockerproject.org/repo ubuntu-precise main" | tee /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install docker-engine
Для проверки корректности установки запустим тестовый контейнер hello-world:
docker run hello-world
Указываем новый тип контейнеризации для Mesos Slaves:
echo "docker,mesos" | sudo tee /etc/mesos-slave/containerizers
Создание нового контейнера, обаза которого еще нет в локальном кэше, может занять больше времени. Поэтому поднимем значение таймаута регистрации нового контейнера в фреймворка:
echo "5mins" | sudo tee /etc/mesos-slave/executor_registration_timeout
Ну и, как обычно, перегрузим сервис после подобных изменений:
service mesos-slave restart
Создадим новую задачу в Marathon и запустим ее в Docker-контейнере. JSON выглядеть следующим образом:
vim /tmp/Docker.json
{
"container": {
"type": "DOCKER",
"docker": {
"image": "libmesos/ubuntu"
}
},
"id": "ubuntu",
"cpus": 0.5,
"mem": 128,
"uris": [],
"cmd": "while sleep 10; do date -u +%T; done"
}
То есть в контейнере будет запущен вечный цикл while с выводом текущей даты. Несложно, правда?
Docker.json можно прямо влить через веб-панель Marathon, активировав переключатель JSON при создании новой задачи:
Или по желанию ввести данные в отдельные поля:
Через некоторое время, в зависимости от скорости интернет-соединения, контейнер будет запущен. Существование его можно наблюдать на Mesos-слейве, который получил задачу на выполнение:
root@mesos-slave1:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
81f39fc7474a libmesos/ubuntu "/bin/sh -c 'while sl" 2 minutes ago Up 2 minutes mesos-4e1e9267-ecf7-4b98-848b-f9a7a30ad209-S2.5c1831a6-f856-48f9-aea2-74e5cb5f067f
root@mesos-slave1:~#
Создадим немного более сложную задачу Marathon, уже с healthcheck-ами. В случае неудовлетворительной проверки работы контейнера, последний будет пересоздан фреймворком:
{
"cmd": "env && python3 -m http.server $PORT0",
"container": {
"docker": {
"image": "python:3"
},
"type": "DOCKER"
},
"cpus": 0.25,
"healthChecks": [
{
"gracePeriodSeconds": 3,
"intervalSeconds": 10,
"maxConsecutiveFailures": 3,
"path": "/",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 5
}
],
"id": "python-app",
"instances": 2,
"mem": 50,
"ports": [
0
],
"upgradeStrategy": {
"minimumHealthCapacity": 0.5
}
}
Теперь будет запущено целых два инстанса python-app, то есть 2 веб-сервера Python с перебросом порта.
Добавим его также через веб-панель Marathon:
Теперь в панели Marathon можем наблюдать, что появились проверки работы инстанса:
На Mesos мастера появились новые долгосрочные задачи:
Можно также увидеть какой фреймворк поставил их на выполнение:
Однако как узнать порт, который назначен для новых веб-серверов Python? Есть несколько способов и один из них — запрос к API Marathon:
curl -X GET -H "Content-Type: application/json" 10.0.3.12:8080/v2/tasks | python -m json.tool
...
{
...
{
"appId": "/python-app",
"healthCheckResults": [
{
"alive": true,
"consecutiveFailures": 0,
"firstSuccess": "2016-06-24T10:35:20.785Z",
"lastFailure": null,
"lastFailureCause": null,
"lastSuccess": "2016-06-24T12:53:31.372Z",
"taskId": "python-app.53d6ccef-39f7-11e6-a2b6-0800272ca725"
}
],
"host": "10.0.3.51",
"id": "python-app.53d6ccef-39f7-11e6-a2b6-0800272ca725",
"ipAddresses": [
{
"ipAddress": "10.0.3.51",
"protocol": "IPv4"
}
],
"ports": [
31319
],
"servicePorts": [
10001
],
"slaveId": "4e1e9267-ecf7-4b98-848b-f9a7a30ad209-S2",
"stagedAt": "2016-06-24T10:35:10.767Z",
"startedAt": "2016-06-24T10:35:11.788Z",
"version": "2016-06-24T10:35:10.702Z"
},
{
"appId": "/python-app",
"healthCheckResults": [
{
"alive": true,
"consecutiveFailures": 0,
"firstSuccess": "2016-06-24T10:35:20.789Z",
"lastFailure": null,
"lastFailureCause": null,
"lastSuccess": "2016-06-24T12:53:31.371Z",
"taskId": "python-app.53d6a5de-39f7-11e6-a2b6-0800272ca725"
}
],
"host": "10.0.3.52",
"id": "python-app.53d6a5de-39f7-11e6-a2b6-0800272ca725",
"ipAddresses": [
{
"ipAddress": "10.0.3.52",
"protocol": "IPv4"
}
],
"ports": [
31307
],
"servicePorts": [
10001
],
"slaveId": "4e1e9267-ecf7-4b98-848b-f9a7a30ad209-S2",
"stagedAt": "2016-06-24T10:35:10.766Z",
"startedAt": "2016-06-24T10:35:11.784Z",
"version": "2016-06-24T10:35:10.702Z"
}
]
}
По адресам http://10.0.3.52:31307 и http://10.0.3.51:31319 новоиспеченные сервера будут ждать подключений:
Аналогично номера портов можно узнать в панеле Marathon.
Новые контейнеры на конечных хостах:
root@mesos-slave1:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d5e439d61456 python:3 "/bin/sh -c 'env && p" 2 hours ago Up 2 hours mesos-4e1e9267-ecf7-4b98-848b-f9a7a30ad209-S2.150ac995-bf3c-4ecc-a79c-afc1c617afe2
...
root@mesos-slave2:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fa55f8cb759 python:3 "/bin/sh -c 'env && p" 2 hours ago Up 2 hours mesos-4e1e9267-ecf7-4b98-848b-f9a7a30ad209-S2.9392fec2-23c1-4c05-a576-60e9350b9b20
...
Есть также возможность указывать статические порты для каждого нового задания Marathon. Это реализуется за счет режима Bridget Network Mode. Корректный JSON для создания задачи иметь следующий вид:
{
"id": "bridged-webapp",
"cmd": "python3 -m http.server 8080",
"cpus": 0.5,
"mem": 64,
"disk": 0,
"instances": 1,
"container": {
"type": "DOCKER",
"volumes": [],
"docker": {
"image": "python:3",
"network": "BRIDGE",
"portMappings": [
{
"containerPort": 8080,
"hostPort": 31240,
"servicePort": 9000,
"protocol": "tcp",
"labels": {}
},
{
"containerPort": 161,
"hostPort": 31241,
"servicePort": 10000,
"protocol": "udp",
"labels": {}
}
],
"privileged": false,
"parameters": [],
"forcePullImage": false
}
},
"healthChecks": [
{
"path": "/",
"protocol": "HTTP",
"portIndex": 0,
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"timeoutSeconds": 20,
"maxConsecutiveFailures": 3,
"ignoreHttp1xx": false
}
],
"portDefinitions": [
{
"port": 9000,
"protocol": "tcp",
"labels": {}
},
{
"port": 10000,
"protocol": "tcp",
"labels": {}
}
]
}
Поэтому tcp-порт контейнера 8080 (containerPort) будет переадресован в порт 31240 (hostPort) на слейв-машине. Аналогично с udp — 161-й в 31241. Конечно, именно в этом случае, причин делать переадресацию udp совсем нет и эта возможность приведена только для примера.
MESOS-DNS
Очевидно, что доступ по IP-адресам не совсем удобен. Более того, слейв, на котором будет запущен каждый следующий контейнер с задачей, будет избираться случайным образом. Поэтому было бы совсем не лишним иметь возможность автоматически привязывать к контейнерам DNS-имена.
С этим может помочь Mesos-DNS. Это DNS-сервер для Mesos кластера, который использует API Mesos мастера для получения имен запущенных задач и IP-адресов слейв, на которых запущены задачи.
Имя домена по умолчанию будет формироваться следующим образом: имя задачи в Mesos +.marathon.mesos. MESOS-DNS будет обслуживать только эту зону — все остальные будут перенаправляться на стандартный DNS-сервер.
Mesos-DNS написан на языке Go и распространяется в виде готового скомпилированного бинарного файла. Для которого в идеале необходимо написать init или systemd скрипт (в зависимости от версии дистрибутива), однако готовые рекомендации есть в сети https://github.com/mesosphere/mesos-dns-pkg/tree/master/common.
Для тестирования Mesos-DNS я создал отдельный сервер с адресом 10.0.3.60, хотя с таким же успехом его можно создать в контейнере с Marathon.
Загружаем на новый сервер последний релиз Mesos-DNS в директорию /usr/sbin и переименовываем бинарник:
cd /usr/sbin
wget https://github.com/mesosphere/mesos-dns/releases/download/v0.5.2/mesos-dns-v0.5.2-linux-amd64
mv mesos-dns-v0.5.2-linux-amd64 mesos-dns
chmod +x mesos-dns
Создаем конфигурационный файл:
vim /etc/mesos-dns/config.json
{
"zk": "zk://10.0.3.11:2181,10.0.3.12:2181,10.0.3.13:2181/mesos",
"masters": ["10.0.3.11:5050","10.0.3.12:5050","10.0.3.13:5050"],
"refreshSeconds": 60,
"ttl": 60,
"domain": "mesos",
"port": 53,
"resolvers": ["8.8.8.8","8.8.4.4"],
"timeout": 5,
"email": "root.mesos-dns.mesos"
}
То есть Mesos-DNS с помощью запроса на Zookeeper (zk) будет узнавать информацию о действующем мастере и опрашивать его с частотой раз в минуту (refreshSeconds). В случае запроса всех остальных доменов кроме зоны mesos — запросы будут переадресованы на DNS-серверы Google (параметр resolvers). Работать сервис будет на стандартном 53 порту, как и любой другой DNS-сервер.
Параметр masters не обязателен. Сначала Mesos-DNS будет искать лидера, используя запрос на Zookeeper сервера и, если они не доступны, будет проходиться по списку серверов, указанных в masters.
Вот хорошая статья, которая описывает все возможные варианты опций http://mesosphere.github.io/mesos-dns/docs/configuration-parameters.html
Этого достаточно, поэтому запускаем Mesos-DNS:
/usr/sbin/mesos-dns -config=/etc/mesos-dns/config.json
2016/07/01 11:58:23 Connected to 10.0.3.11:2181
2016/07/01 11:58:23 Authenticated: id=96155239082295306, timeout=40000
Конечно, также ко всем нод кластера Mesos стоит добавить адрес сервера Mesos-DNS в качестве основной в /etc/resolv.conf, на первую позицию:
vim /etc/resolv.conf
nameserver 10.0.3.60
nameserver 8.8.8.8
nameserver 8.8.4.4
После изменения resolv.conf, стоит убедиться, что все имена резовляться именно через 10.0.3.60:
dig i.ua
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> i.ua
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24579
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;i.ua. IN A
;; ANSWER SECTION:
i.ua. 2403 IN A 91.198.36.14
;; Query time: 58 msec
;; SERVER: 10.0.3.60#53(10.0.3.60)
;; WHEN: Mon Jun 27 16:20:12 CEST 2016
;; MSG SIZE rcvd: 49
Для демонстрации работы Mesos-DNS запустим через Marathon следующую задачу:
cat nginx.json
{
"id": "nginx",
"container": {
"type": "DOCKER",
"docker": {
"image": "nginx:1.7.7",
"network": "HOST"
}
},
"instances": 1,
"cpus": 0.1,
"mem": 60,
"constraints": [
[
"hostname",
"UNIQUE"
]
]
}
curl -X POST -H "Content-Type: application/json" http://10.0.3.11:8080/v2/apps -d@nginx.json
Эта задача установит контейнер с Nginx, а Mesos-DNS зарегистрирует для него имя nginx.marathon.mesos:
dig +short nginx.marathon.mesos
10.0.3.53
При масштабировании задачи nginx (то есть при создании дополнительных инстансов), Mesos-DNS распознает это и создаст дополнительные A-записи для того же домена:
dig +short nginx.marathon.mesos
10.0.3.53
10.0.3.51
Таким образом будет работать балансирования между двумя нодамы на уровне DNS.
Стоит заметить, что Mesos-DNS, кроме А-записей, также создает SRV-запись в DNS для каждой задачи (контейнера), работающий в качестве сервера. SRV-запись связывает название сервиса и хостнейм-IP-порт на котором он доступен. Проверим SRV-запись для задачи nginx, что мы запустили раньше (не масштабируемую до двух инстанс):
dig _nginx._tcp.marathon.mesos SRV
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> _nginx._tcp.marathon.mesos SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11956
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; QUESTION SECTION:
;_nginx._tcp.marathon.mesos. IN SRV
;; ANSWER SECTION:
_nginx._tcp.marathon.mesos. 60 IN SRV 0 0 31514 nginx-9g7b9-s0.marathon.mesos.
;; ADDITIONAL SECTION:
nginx-9g7b9-s0.marathon.mesos. 60 IN A 10.0.3.51
;; Query time: 2 msec
;; SERVER: 10.0.3.60#53(10.0.3.60)
;; WHEN: Fri Jul 01 12:03:37 CEST 2016
;; MSG SIZE rcvd: 124
Для того, чтобы не вносить изменения в настройки resolv.conf каждого сервера — можно внести изменения к внутреннему DNS инфраструктуры. В случае Bind9 эти изменения будут выглядеть так:
vim /etc/bind/named.conf.local
zone "mesos" {
type forward;
forward only;
forwarders { 192.168.0.100 port 8053; };
};
А в конфиге Mesos-DNS (представим, что он сейчас по адресу 192.168.0.100) следует внести следующие изменения:
vim /etc/mesos-dns/config.json
...
"externalon": false,
"port": 8053,
...
«externalon»: false указывает на то, что Mesos-DNS имеет отказывать в обслуживанию запросам, пришли не с домена mesos.
После внесенных изменений необходимо перегрузить Bind (или что там у вас) и Mesos-DNS.
Mesos-DNS также имеет API https://docs.mesosphere.com/1.7/usage/service-discovery/mesos-dns/http-interface/, который может помочь в решении задач автоматизации.
Mesos-DNS, кроме создания записей для работающих задач, автоматически создает записи (A и SRV) также для Mesos Slaves, Mesos Masters (и отдельно для лидера среди них), фреймворков. Все для нашего с вами удобства.
MARATHON-LB
Несмотря на преимущества, в Mesos-DNS также есть и определенные ограничения, среди которых:
- DNS не делает привязки к портам на которых работают сервисы в контейнерах. Их нужно или выбирать статически при постановке задачи (следить за их использованием может быть не такой уж и простой задачей) или каждый раз узнавать новый порт через Marathon для доступа к конечным ресурсам. Mesos-DNS умеет генерировать также SRV-записи в DNS (с указанием конечного хостнейму и порта), однако «из коробки» с этим умеют работать далеко не все программы.
- DNS не имеет быстрого failover (функция переключения на резервный узел).
- Записи в локальных DNS-кэшах могут храниться достаточно долго (как минимум время TTL). Хотя сам Mesos-DNS опрашивает Mesos Master API довольно часто.
- Отсутствуют Health-check проверки сервисов в конечных контейнерах. То есть, в случае нескольких инстансов, падение одного из них останется незамеченным для Mesos-DNS до полного его пересоздание фреймворком Marathon.
- Некоторые программы и библиотеки не работают корректно с несколькими A-записями, что накладывает серьезные ограничения на масштабирование задач.
То есть большинство проблем возникают вследствие самой природы DNS.
Именно для устранения этих недостатков был начат подпроект Marathon-lb. Marathon-lb — это скрипт на языке Python, который опрашивает Marathon API и на основе полученных данных (адрес Mesos слейв, на котором физически находится контейнер и порт работы сервиса) создает конфигурационный файл HAproxy и делает reload его процесса.
Однако стоит отметить, что Marathon-lb работает только с Marathon, в отличие от Mesos-DNS. Поэтому в случае других фреймворков нужно будет искать другие программные решения.
На рисунке изображено балансирование запросов двумя пулами балансировщиков — Internal (для доступов с внутренней сети) и External (для доступов из сети Internet). Балансировщик (кроме ELB) — это HAproxy и Marathon-lb. ELB — это балансировщик в терминологии Amazon AWS.
Для настройки Marathon-lb я использовал отдельную виртуальную машину с адресом 10.0.3.61. В официальной документации также приведены варианты запуска Marathon-lb в docker-контейнере, в качестве задачи, запущенной с Marathon
Настройка Marathon-lb и HAproxy проходит довольно не сложно. Нужен последний стабильный релиз HAproxy и для Ubuntu 14.04 это версия 1.6:
apt-get install software-properties-common
add-apt-repository ppa:vbernat/haproxy-1.6
apt-get update
apt-get install haproxy
Загружаем код проекта Marathon-lb:
mkdir /marathon-lb/
cd /marathon-lb/
git clone https://github.com/mesosphere/marathon-lb .
Установим необходимые для работы python-пакеты:
apt install python3-pip
pip install -r requirements.txt
Генерируем ключи, которые требует по-умолчанию marathon-lb:
cd /etc/ssl
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
cat key.pem >> mesosphere.com.pem
cat cert.pem >> mesosphere.com.pem
Выполняем первый запуск marathon-lb:
/marathon-lb/marathon_lb.py --marathon http://my_marathon_ip:8080 --group internal
Если не возникло серьезных ошибок — можно переходить к созданию задачи в Marathon:
{
"id": "nginx-internal",
"container": {
"type": "DOCKER",
"docker": {
"image": "nginx:1.7.7",
"network": "BRIDGE",
"portMappings": [
{ "hostPort": 0, "containerPort": 80, "servicePort": 10001 }
],
"forcePullImage":true
}
},
"instances": 1,
"cpus": 0.1,
"mem": 65,
"healthChecks": [{
"protocol": "HTTP",
"path": "/",
"portIndex": 0,
"timeoutSeconds": 10,
"gracePeriodSeconds": 10,
"intervalSeconds": 2,
"maxConsecutiveFailures": 10
}],
"labels":{
"HAPROXY_GROUP":"internal"
}
}
Сразу после перехода задачи в режим Running, опросим Marathon:
/marathon-lb/marathon_lb.py --marathon http://10.0.3.11:8080 --group internal
marathon_lb: fetching apps
marathon_lb: GET http://10.0.3.11:8080/v2/apps?embed=apps.tasks
marathon_lb: got apps ['/nginx-internal']
marathon_lb: setting default value for HAPROXY_BACKEND_REDIRECT_HTTP_TO_HTTPS
...
marathon_lb: setting default value for HAPROXY_BACKEND_HTTP_HEALTHCHECK_OPTIONS
marathon_lb: generating config
marathon_lb: HAProxy dir is /etc/haproxy
marathon_lb: configuring app /nginx-internal
marathon_lb: frontend at *:10001 with backend nginx-internal_10001
marathon_lb: adding virtual host for app with id /nginx-internal
marathon_lb: backend server 10.0.3.52:31187 on 10.0.3.52
marathon_lb: reading running config from /etc/haproxy/haproxy.cfg
marathon_lb: running config is different from generated config - reloading
marathon_lb: writing config to temp file /tmp/tmp02nxplxl
marathon_lb: checking config with command: ['haproxy', '-f', '/tmp/tmp02nxplxl', '-c']
Configuration file is valid
marathon_lb: moving temp file /tmp/tmp02nxplxl to /etc/haproxy/haproxy.cfg
marathon_lb: No reload command provided, trying to find out how to reload the configuration
marathon_lb: we seem to be running on a sysvinit based system
marathon_lb: reloading using /etc/init.d/haproxy reload
* Reloading haproxy haproxy
marathon_lb: reload finished, took 0.02593827247619629 seconds
Название группы должно совпадать с ярлыком «HAPROXY_GROUP» в поставленной задаче. Это сделано для возможности использовать несколько балансировщиков, которые будут обслуживать разные группы.
С последнего вывода видим в haproxy.cfg было добавлено проксирование из порта 10001 в адрес 10.0.3.52:31187.
И HAproxy действительно открыл соответствующий порт:
root@mesos-lb# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 0.0.0.0:10001 0.0.0.0:* LISTEN 10285/haproxy
...
Конфигурация haproxy.cfg теперь выглядит так:
cat /etc/haproxy/haproxy.cfg
...
frontend nginx-internal_10001
bind *:10001
mode http
use_backend nginx-internal_10001
backend nginx-internal_10001
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
option httpchk GET /
timeout check 10s
server 10_0_3_52_31187 10.0.3.52:31187 check inter 2s fall 11
Соответственно, если будет 2 инстансы в задачи на Marathon — то будет и 2 адреса в бэкенд bash nginx-internal_10001
.
Но опять таки, помнить и использовать номер порта соединения — не самая лучшая затея. Поэтому идеальный вариант — это использование virtual host mapping для HAproxy. Суть этого всего в том, что в зависимости от запрошенного домена в HAproxy, будет происходить адресация на конечный слейв и порт к нему.
Поставим еще одну задачу для того чтобы проверить все это в деле:
{
"Id": "nginx-external",
"Container" {
"Type": "DOCKER",
"Docker" {
"Image": "nginx: 1.7.7",
"Network": "BRIDGE",
"PortMappings": [
{ "HostPort": 0, "containerPort": 80, "servicePort" 10000}
],
"ForcePullImage": true
}
}
"Instances": 1,
"Cpus": 0.1,
"Mem": 65,
"HealthChecks": [{
"Protocol": "HTTP",
"Path": "/",
"PortIndex": 0,
"TimeoutSeconds": 10
"GracePeriodSeconds": 10
"IntervalSeconds": 2,
"MaxConsecutiveFailures": 10
}],
"Labels" {
"HAPROXY_GROUP": "external",
"HAPROXY_0_VHOST": "nginx.external.com"
}
}
Итак, мы добавили дополнительный ярлык »HAPROXY_0_VHOST» с указанием домена, который имеет проксуваты HAproxy.
Выждав минуту, запускаем marathon_lb.py:
/marathon-lb/marathon_lb.py --marathon Http://10.0.3.11:8080 --group external
marathon_lb: fetching apps
marathon_lb: GET http://10.0.3.11:8080/v2/apps?embed=apps.tasks
marathon_lb: got apps [ '/ nginx-internal', '/ nginx-external']
marathon_lb: setting default value for HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH
...
marathon_lb: generating config
marathon_lb: HAProxy dir is / etc / haproxy
marathon_lb: configuring app / nginx-external
marathon_lb: frontend at * 10000 with backend nginx-external_10000
marathon_lb: adding virtual host for app with hostname nginx.external.com
marathon_lb: adding virtual host for app with id / nginx-external
marathon_lb: backend server 10.0.3.53:31980 on 10.0.3.53
marathon_lb: reading running config from /etc/haproxy/haproxy.cfg
marathon_lb: running config is different from generated config - reloading
marathon_lb: writing config to temp file / tmp / tmpcqyorq8x
marathon_lb: checking config with command: [ 'haproxy "," -f "," / tmp / tmpcqyorq8x "," -c']
Configuration file is valid
marathon_lb: moving temp file / tmp / tmpcqyorq8x to /etc/haproxy/haproxy.cfg
marathon_lb: No reload command provided, trying to find out how to reload the configuration
marathon_lb: we seem to be running on a sysvinit based system
marathon_lb: reloading using /etc/init.d/haproxy reload
* Reloading haproxy haproxy
marathon_lb: reload finished, took 0.02756667137145996 seconds
И проверим конфигурационный файл HAproxy:
cat /etc/haproxy/haproxy.cfg
...
frontend marathon_http_in
bind * 80
mode http
acl host_nginx_external_com_nginx-external hdr (host) -i nginx.external.com
use_backend nginx-external_10000 if host_nginx_external_com_nginx-external
frontend marathon_http_appid_in
bind *: 9091
mode http
acl app__nginx-external hdr (x-marathon-app-id) -i / nginx-external
use_backend nginx-external_10000 if app__nginx-external
frontend marathon_https_in
bind * 443 ssl crt /etc/ssl/mesosphere.com.pem
mode http
use_backend nginx-external_10000 if {ssl_fc_sni nginx.external.com}
frontend nginx-external_10000
bind * 10000
mode http
use_backend nginx-external_10000
backend nginx-external_10000
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port% [dst_port]
http-request add-header X-Forwarded-Proto https if {ssl_fc}
option httpchk GET /
timeout check 10s
server 10_0_3_53_31980 10.0.3.53:31980 check inter 2s fall 11
Поэтому теперь, если будет запрошен домен nginx.external.com, который предварительно необходимо привязать к серверу HAproxy, запрос будет переадресован на порт и хост задачи на Mesos слейве.
Проверим результат в браузере:
Ярлыки HAproxy также отражаются в Marathon:
Кроме этого, Marathon-lb поддерживает настройки SSL для HAproxy, sticky SSL, а данные для построения haproxy.cfg вообще можно получать не опросу Marathon по API, а подпиской на Event Bus и т.п.
AUTOSCALING
Тот, кто работал с платформой Amazon AWS, знает эту прекрасную возможность увеличивать или уменьшать количество виртуальных машин в зависимости от нагрузки. Mesos Cluster также имеет такую функцию.
Первый вариант реализации — это marathon-autoscale. Скрипт по API Marathon может следить за использованием CPU/памяти задачи и поднимать количество инстансов в зависисмости от заданных условий.
Другой, более интеллектуальный, — marathon-lb-autoscale. Этот вариант основан на опросе Marathon-lb сервиса. В случае превышения определенного уровня запросов в единицу времени — скрипт будет поднимать количество инстансов задачи в Marathon.
Ну вот и все. Надеюсь вы не устали читать все это. Конечно же, в статье могут быть нето