Translate

середу, 29 червня 2016 р.

Mesos. Cluster Management

Apache Mesos - це централізована відмовостійка система управління кластером. Вона розроблена для розподілених комп’ютерних середовищ задля забезпечення ізоляції ресурсів та зручного управління кластерами підлеглих вузлів (mesos slaves). Це новий ефективніший спосіб управління серверною інфраструктурою.

У певному сенсі суть його роботи протилежна вже традиційній віртуалізації - замість ділення фізичної машини на купу віртуальних, Mesos пропонує їх об’єднувати в одне ціле, в єдиний віртуальний ресурс.

Mesos розподіляє ресурси CPU та пам’яті в кластері для задач в схожій манері як ядро Linux виділяє ресурси заліза між локальними процесами. По-суті, у випадку Mesos, це і є мікросервісами.

Уявімо собі, що є необхідність виконати різні типи задач. Для цього можна виділити окремі віртуальні машини (окремий кластер) для кожного типу. Ці віртуальні машини ймовірно не будуть повністю завантаженими і певний час будуть простоювати, тобто не працюватимуть з максимальною ефективністю. Якщо ж всі віртуальні машини для всіх задач об’єднати в єдиний кластер, ми можемо підвищити ефективність використання ресурсів і паралельно з тим підвищити швидкість їх виконання (у разі якщо задачі короткострокові чи віртуальні машини не завантажені повністю увесь час). Наступний малюнок, надіюсь, прояснить сказане:

Але це далеко не все. Кластер 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-майстрів може бути парною, наприклад два, адже за уникнення Split-brain відповідатимуть саме Zookeeper-ноди.


У цій статті піде мова про 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 і більше вузлів, а то хтозна, що може статись під час технічного обслуговування на одному із хостів.

Вказуємо однаковий рівень кворуму для майстрів:

# 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 має приємний темний інтерфейс:

 

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 -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-slave

Якщо все буде виконано вірно, слейви підключаться у якості ресурсів до поточного лідера:



Кластер готовий! Запустимо якусь задачу на Marathon.  Для цього відкриємо Marathon на будь-якому майстер-вузлі, натиснемо синю кнопку Create Application та введемо все як на малюнку:


Задача почала виконуватись:


У веб-панелі Mesos одразу з'явиться активна задача, котру поставив фреймворк, та історія завершених задач. Річ у тім, що ця задача буде завершуватись та починатись знову (завдяки перестворенню контейнера додатку Marathon-ом), адже вона короткострокова. Саме тому в 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 я писав трохи більше ніж рік тому, і стаття лишилась досить таки актуальною. Раджу ознайомитись за необхідністю.

Із активацією 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. Це реалізується за рахунок режиму Bridged 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 і поширюється у вигляді готового cкомпільваного бінарного файлу. Для якого в ідеалі необхідно написати 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 адреси в бекенді 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.


Посилання

Getting Stated

https://www.digitalocean.com/community/tutorials/how-to-configure-a-production-ready-mesosphere-cluster-on-ubuntu-14-04
https://open.mesosphere.com/advanced-course/introduction/
https://open.mesosphere.com/getting-started/install/
http://iankent.uk/blog/a-quick-introduction-to-apache-mesos/
http://frankhinek.com/tag/mesos/
https://mesosphere.github.io/marathon/docs/service-discovery-load-balancing.html
https://mesosphere.github.io/marathon/
https://mesosphere.github.io/marathon/docs/ports.html
https://beingasysadmin.wordpress.com/2014/06/27/managing-docker-clusters-using-mesos-and-marathon/
http://mesos.readthedocs.io/en/latest/

Docker

http://mesos.apache.org/documentation/latest/containerizer/#Composing
http://mesos.apache.org/documentation/latest/docker-containerizer/
https://mesosphere.github.io/marathon/docs/native-docker.html

Mesos-DNS

https://tech.plista.com/devops/mesos-dns/
http://programmableinfrastructure.com/guides/service-discovery/mesos-dns-haproxy-marathon/
http://mesosphere.github.io/mesos-dns/docs/tutorial-systemd.html
http://mesosphere.github.io/mesos-dns/docs/configuration-parameters.html
https://mesosphere.github.io/mesos-dns/docs/tutorial.html
https://mesosphere.github.io/mesos-dns/docs/tutorial-forward.html
https://github.com/mesosphere/mesos-dns

Marathon-lb

https://mesosphere.com/blog/2015/12/04/dcos-marathon-lb/
https://mesosphere.com/blog/2015/12/13/service-discovery-and-load-balancing-with-dcos-and-marathon-lb-part-2/
https://docs.mesosphere.com/1.7/usage/service-discovery/marathon-lb/
https://github.com/mesosphere/marathon-lb
https://dcos.io/docs/1.7/usage/service-discovery/marathon-lb/usage/

Autoscaling

https://docs.mesosphere.com/1.7/usage/tutorials/autoscaling/
https://docs.mesosphere.com/1.7/usage/tutorials/autoscaling/cpu-memory/
https://docs.mesosphere.com/1.7/usage/tutorials/autoscaling/requests-second/
https://github.com/mesosphere/marathon-autoscale

Other

https://clusterhq.com/2016/04/15/resilient-riak-mesos/
https://github.com/CiscoCloud/mesos-consul/blob/master/README.md#comparisons-to-other-discovery-software
http://programmableinfrastructure.com/guides/load-balancing/traefik/
https://opensource.com/business/14/8/interview-chris-aniszczyk-twitter-apache-mesos
http://www.slideshare.net/akirillov/data-processing-platforms-architectures-with-spark-mesos-akka-cassandra-and-kafka
http://www.slideshare.net/mesosphere/scaling-like-twitter-with-apache-mesos
http://www.slideshare.net/subicura/mesos-on-coreos
https://www.youtube.com/watch?v=RciM1U_zltM
http://www.slideshare.net/JuliaMateo1/deploying-a-dockerized-distributed-application-in-mesos

2 коментарі: