Pull to refresh
477.71
Яндекс
Как мы делаем Яндекс

Свой Cocaine. Облачная платформа Яндекса

Reading time12 min
Views54K
Мы уже рассказывали на Хабре про облачную инфраструктуру Яндекса. Сегодня пришёл черёд от слов перейти к делу — мы хотим по шагам показать, как можно развернуть собственное облако на Elliptics и Cocaine.



Схема


Давайте рассмотрим установку небольшого облачка, в котором можно запустить тестовое приложение использующее flask.

Это облачко состоит из следующих элементов:
  • cocaine-runtime, запускающий приложения в Docker;
  • Docker-registry для хранения образов приложений;
  • Elliptics в качестве распределенного хранилища приложений, а также конфигурации облака;
  • агрегирующая нода cocaine-runtime — единая точка входа в облако для клиентского кокаинового кода;
  • HTTP-frontend как альтернативный способ для доступа к приложениям.


На каждом этапе будем проводить проверки, тестирующие успешность выполнения этапа.



Как видно, нам понадобится 5 (можно виртуальных) машинок с ядром не ниже 3.8 для поддержки Docker. Необходимые пакеты можно найти в репозитории.

Как добавить репозиторий
Создадим /etc/apt/sources.list.d/reverbrain.list следующего содержания:
deb http://repo.reverbrain.com/precise/ current/amd64/
deb http://repo.reverbrain.com/precise/ current/all/

Подтянем ключ:
curl -O http://repo.reverbrain.com/REVERBRAIN.GPG
sudo apt-key add REVERBRAIN.GPG

Посмотрим, какие пакеты, относящиеся к кокаину, стали доступны:
apt-get update
apt-cache search cocaine


5 минут на Elliptics


Мне придется в двух словах описать, как развернуть инсталляцию Elliptics из одной машины для демонстрации работы нашего кластера. Сразу замечу, что это ни в коем случае нельзя рассматривать в качестве руководства по разворачиванию этой Elliptics для боевого применения. Об этом вам обязательно расскажут ребята, которые и занимаются разработкой Elliptics. Также информацию можно найти в документации.

В паре команд это выглядит так:
sudo apt-get install elliptics=2.24.14.31 elliptics-client=2.24.14.31
mkdir /tmp/history/ && mkdir /tmp/root
cp /usr/share/doc/elliptics/examples/ioserv.conf  ./tst_ioserv.conf


Далее в tst_ioserv.conf нам придется изменить буквально 3 строки. Итак:
group = 1
addr = localhost:1025:2-0 192.168.50.201:1025:2-1 // здесь подставьте свой IP
indexes_shard_count = 16


После этого запускаем:
dnet_ioserv -c tst_ioserv.conf



Настройка cocaine-runtime + Docker

Приступим к реализации нашего мини-облака с конфигурации машин, непосредственно запускающих код нашего приложения. Ядром инсталляции будет безусловно cocaine-runtime. Также необходимо установить сам Docker и cocaine-plugin для работы с Docker.

Уточню, что хотя наша цель — запустить приложение в docker-контейнере, мы также запустим приложение как обычный процесс.

Контейнеризация — есть благо. Она решает проблемы с окружением для приложения:
  • зависимости всегда с нами;
  • нет конфликтов между зависимостями двух приложений;
  • неизменность окружения в dev, test, prod-средах.

Запуск без контейнера применим и удобен, когда нет зависимостей (например, приложение на go) или технической возможности использовать контейнеры (например, ядро ниже требуемой версии и обновить его нельзя).

После подключения репозитория установим необходимые пакеты:
sudo apt-get install cocaine-runtime libcocaine-core2 libcocaine-plugin-docker2 libcocaine-plugin-elliptics=2.24.14.31 elliptics-client=2.24.14.31



Для управления приложениями нам необходима утилита cocaine-tool. Проще всего ее установить из PyPI — стандартного репозитория Python пакетов.

Необходимо установить биндинг в Python для msgpack.
sudo apt-get install msgpack-python


А затем cocaine-tools.
sudo pip install cocaine-tools


А почему msgpack-python не из PyPI?
Msgpack-python имеет две реализации. Одна «pure-Python», вторая — на Cython. При установке из PyPI делается попытка скомпилировать бинарную версию, а в случае неудачи ставится чистая реализация. Плохо то, что эти реализации несовместимы между собой. Мы брали за эталон Cython версию. Чаще всего какая-то из необходимых зависимостей отсутствует и устанавливается чистая реализация, что — в зависимости от версии — приводит к непонятным ошибкам. Так было не всегда, и возможно ситуация исправится в будущем.


Docker устанавливаем по описанию в официальной документации любым удобным способом. Использован следующий путь:
curl -s https://get.docker.io/ubuntu/ | sudo sh



После успешной инсталяции пакетов cocaine-runtime стартует с дефолтной конфигурацией, которую нам необходимо изменить.
Останавливаем cocaine-runtime и переходим к модификации конфигурации.
sudo service cocaine-runtime stop



Дефолтный файл конфигурации расположен в /etc/cocaine/cocaine-default.conf и выглядит так:
{
    "version": 2,
    "paths": {
        "plugins": "/usr/lib/cocaine",
        "runtime": "/var/run/cocaine"
    },
    "services": {
        "logging": {
            "type": "logging"
        },
        "storage": {
            "type": "storage",
            "args": {
                "backend": "core"
            }
        },
        "node": {
            "type": "node",
            "args": {
                "runlist": "default"
            }
        }
    },
    "storages": {
        "core": {
            "type": "files",
            "args": {
                "path": "/var/lib/cocaine"
            }
        },
        "cache": {
            "type": "files",
            "args": {
                "path": "/var/cache/cocaine"
            }
        }
    },
    "loggers": {
        "core": {
            "type": "syslog",
            "args": {
                "identity": "cocaine",
                "verbosity": "info"
            }
        }
    }
}


Сделаем конфигурацию облачной. Для этого необходимо настроить 2 сущности:
  1. Сконфигурить работу с распределенным стораджем (в нашем примере — Elliptics). Все дело в том, что cocaine-runtime вычитывает из storage список запускаемых приложений, профили запуска приложений, манифесты приложений. Если использовать присутствующий «из коробки» файловый storage, то придется поддерживать консистентность этих данных на каждой ноде, что крайне неудобно. В случае распределенного стораджа этот вопрос решается сам собой.
  2. Заставить runtime сообщать информацию о себе агрегирующим нодам облака.

Для экспериментов удобно модифицировать копию предоставляемого конфига «по умолчанию» (например, /etc/cocaine/cocaine-cloud.conf).

Заставить cocaine-runtime сообщать агрегирующим нодам о запущенных на нем приложениях и сервисах можно, добавив секцию network в конфигурацию на одном уровне вложенности с services.

  "network" : {
        "group": "224.168.2.9"
   },
  "services": {
  ...
  }


Единственный параметр group описывает адрес multicast-группы, в которую будет производиться рассылка оповещений.
Конфигурация storage выполняется модификацией секций services/storage и storages/core:

"storage": {
            "type": "elliptics"
        },
"storages" : {        
"core": {
            "type": "elliptics",
            "args": {
                "nodes" : {
			"192.168.50.201" : 1025
		},
		"io-thread-num" : 8,
		"wait-timeout" : 30,
		"check-timeout" : 60,
		"net-thread-num" : 8,
		"groups" : [1],
		"verbosity" : 2
            }
        }
}


Реализации storage на технологии Elliptics обеспечивается соответствующим плагином. К слову, все плагины из наших пакетов ставятся в /usr/lib/cocaine, где их по умолчанию ищет cocaine-runtime.

Теперь заставим стартовать кокаин с этим конфигом по дефолту. Создаем
/etc/default/cocaine-runtime:
CONFIG_PATH="/etc/cocaine/cocaine-cloud.conf"

Запустим cocaine-runtime с новым конфигом, убедимся в том, что процесс работает.


После запуска на порту 10053 сервис-локатор этой ноды будет ожидать запросов о доступности приложения. Это своего рода DNS для облачных приложений и сервисов, то есть по имени сервиса или приложения вы узнаете куда надо отправлять запросы, чтобы они были обработаны.

Запросить информацию о приложениях на этой ноде можно при помощи команды cocaine-tool info. Вывод этой команды пока не потрясает воображение, но запомните его — совсем скоро он изменится.

Напомним, что помимо cocaine-runtime и плагинов, мы установили Docker. Проверим, что он так же успешно запустился:


Предполагаем, что на второй машине будет проделаны аналогичные действия. Итого мы имеем 2 машинки с запущенным cocaine-runtime, которые смотрят в общий storage — это прекрасно!

В этой точке уже вполне можно запускать приложения. Это, конечно, пока совсем не облако, но все же. Засучив рукава, приступим.

В первую очередь нам необходимо доставить код приложения в кокаиновый storage. Эта задача решается с легкостью при помощи cocaine-tool.

Склонируем репозиторий с примером и перейдем внутрь каталога.
git clone git@github.com:cocaine/cocaine-framework-python.git -b v0.11
cd cocaine-framework-python/examples/flask/



Можно заметить, что помимо кода приложения, здесь расположен manifest.json:
{
  "slave": "main.py"
}


Основная задача этого файла указать платформе, что запускать. Также можно передать environment.

Для загрузки кода приложения, которое хотим запустить как процесс, в облако необходимо выполнить следующую команду:
cocaine-tool app upload --name example

cocaine-tool запакует Ваше приложение в архив и загрузить в облачное хранилище. А потом мы посмотрим список загруженных приложений.



Вот оно, наше приложение! Главное, не забыть установить flask, который требует наше приложение. Вот этот недостаток запуска без контейнера.
sudo apt-get install python-flask


Далее необходимо настроить выделяемые для приложения ресурсы, тип изоляции, управление количеством воркеров и тд. Для этой цели служат профили. Профиль может быть связан с несколькими приложениями.

Простейший профиль — пустой JSON {}. В этом случае используются параметры по умолчанию.

Описание всех доступных опций можно найти в описании. Более интересный пример рассмотрим далее, когда будем запускать приложение в контейнере.

Назовем наш профиль default и загрузим его в облачное хранилище. Проверим, что он загрузился правильно.


Наконец-то настал момент, когда можно дернуть рубильник и приложение запустится:
cocaine-tool app start --name example --profile default



Теперь-то вывод команды cocaine-tool info изменился. В нем показано, какие приложения запущены на данной ноде и статистика о приложениях.

Попробуем послать сообщение в наше приложение. Для этой цели достаточно вот такого небольшого скрипта на питоне:
#!/usr/bin/env python

from cocaine.services import Service
app = Service("example")
print(app.enqueue("write", "DATA").get())
print(app.enqueue("read", "DATA").get())


Приложение все еще запущено только на одной ноде, но можно сходить на вторую и запустить его и там. Теперь можно продолжить строить инфраструктуру для приложений в Docker.

Docker-registry


Docker-registry — это ваше приватное хранилище образов (контейнеров) приложений. Более подробно о нем можно почитать в официальной документации docker.

Docker-registry поддерживает множество бекендов для хранения образов. Ассортимент начинается с записи образов на локальную файловую систему и простирается до хранения образов в Elliptics.

Самый простой способ запустить Docker-registry — это использовать Docker. Для этого нам нужно поставить Docker (смотри выше) и дать команду запусти нужный образ.
sudo docker run -p 5000:5000 registry


Образ registry в первый раз загрузится из репозитория, закэшируется и в следующий раз будет запускаться быстро.

После запуска Docker-registry будет слушать порт 5000. Пингуем его:
curl "http://192.168.50.4:5000/_ping"


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

Сборка и деплой приложения в контейнере


Деплой приложения в случае контейнера будет мало отличаться от «неконтейнерного». Самое главное отличие в том, что для сборки образа необходим Dockerfile. По сути он состоит из набора shell-команд, которые необходимо выполнить для формирования внутреннего состояния образа. После этого образ заливается в Docker-registry.

Далее любой Docker, который хочет запустить контейнер, может загрузить себе образ из registry. Синтаксис Dockerfile, доступные команды можно найти здесь. Вместо Dockerfile вы можете предоставить Chef-рецепт или Puppet-манифест, которые будут выполнены внутри контейнера при сборке.

В профиле для этого приложения необходимо указать тип изоляции Docker. Для этого создадим новый профиль.
{
    "queue-limit": 1000,
    "pool-limit": 10,
    "isolate": {
        "type": "docker",
        "args": {
            "memory_limit": 1000000000,
            "endpoint": "unix:///var/run/docker.sock",
            "registry": "registry.cloud.net:5000",
            "cpu_shares": 0
        }
 
    },
    "concurrency": 200
}

Профиль мы разместили в файле именем docker-profile.json, загрузим его с именем docker-profile
cocaine-tool profile upload --name docker-profile --profile=docker-profile.json


В этом примере мы имеем готовый Dockerfile, и загрузка приложение в облако производиться так:
sudo cocaine-tool app upload --docker=unix:///var/run/docker.sock --registry=registry.cloud.net:5000 --manifest manifest-docker.json --name example-docker --timeout 20000

Это не мгновенный процесс, все зависит от размеров вашего образа и сети. Но в результате вы должны увидеть аналогичную надпись. Далее приложение запускается.


Упущен из виду один момент — о том, какие приложения cocaine-runtime запускает при старте и как узнаёт об этом. Для этого есть runlist. По сути своей этот ассоциативный массив из приложений и их профилей, который cocane-runtime читает при старте. Он располагается, как и профили, в storage. Так как наборы запускаемых приложений на разных нодах облака могут отличаться, то и ранлисты на них могут быть разные. Имя ранлиста указывается в конфиге cocaine-runtime в параметре runlist сервиса node.

Конфигурирование агрегирующей ноды


По сути своей эта часть конфигурации очень похожа на пункт «Настройка cocaine-runtime». Это закономерно, так как в роли агрегирующей ноды выступает cocaine-runtime. Всю работу по балансировке в нем выполняет gateway plugin.

У нас есть две реализации данного плагина. Первая называется adhoc. Она доступна «из коробки» и не требует установки дополнительных пакетов. Вторая реализация, используемая нами в боевых условиях, называется ipvs. Как видно из названия, она использует одноименную технологию. Adhoc разумно применять в случае, когда нет возможности использовать ipvs. И безусловно, можно написать свою реализацию данного плагина, чтобы использовать для балансировки понравившуюся вам технологию.

От слов к делу. Устанавливаем пакеты:
sudo apt-get install cocaine-runtime libcocaine-core2 libcocaine-plugin-ipvs2 libcocaine-plugin-elliptics=2.24.14.31 elliptics-client=2.24.14.31



Если нет возможности использовать ipvs, то последний из приведенных пакетов ставить нет необходимости. Различия в базовой конфигурации будут незначительны и будут указаны отдельно.

Сделать runtime агрегирующей нодой позволит добаление секции network с подсекцией gateway. Предполагаем, что как и прежде, правим копию конфига «по умолчанию». Сервис storage конфигурируем аналогично предыдущему пункту — он скоро понадобится.
    "network": {
        "group": "224.168.2.9",
        "gateway": {
             "type": "ipvs" // adhoc
        }
    },
    "services"...


После перезапуска sudo service cocane-runtime restart должен стартовать процесс:
cocaine /usr/bin/cocaine-runtime --daemonize --configuration /etc/cocaine/cocaine-gateway.conf --pidfile /var/run/cocaine/runtime.pid


На самом деле gateway принимает еще ряд параметров (о них можно прочитать в документации).

Давайте откроем лог (по умолчанию он пишется в syslog). Можно заметить, что было зарегистрировано подключение двух нод, а потом одна из них выключилась. Вы можете проделать аналогичные операции с вашими бекендами и убедиться, что discovery работает.


Cocaine-native-proxy


Cocaine-native-proxy позволяет ходить в облачные приложения по HTTP. Установка и настройка её предельно просты.
apt-get install cocaine-native-proxy


Затем нам необходимо указать список агрегирующих нод в секции locators. В нашем случае такая нода одна. Строго говоря, там можно указать адреса локаторов и нод не работающих в режиме агрегации. Это плохо по той причине, что такая нода ничего не знает о сервисах и приложениях на других нодах, а значит, вы не будете иметь к ним доступ. Более подробное описание параметров можно найти на github.com/cocaine/cocaine-native-proxy/blob/master/README.md
{
    "endpoints": [
        "0.0.0.0:8080"
    ],
    "backlog": 2048,
    "threads": 2,
    "application": {
        "locators": ["192.168.50.103:10053"],
        "service_pool": 5,
        "reconnect_timeout": 180,
        "request_timeout": 5
    }
}


Точка входа в облако в нашей инсталяции — машина с агрегируещей нодой. Несмотря на то, что HTTP интерфейс вынесен наружу через cocaine-native-proxy, на самом деле эта прокси тоже использует агрегирующую ноду. Мы её указывали в конфиге в секции locators. Давайте же попробуем позвать наши приложения двумя способами. HTTP клиент будет использовать обработчик события http, а из клиентского кода на Python обработаем события write и read.

В клиентском коде теперь указывается адрес точки входа в облако. В моем случае — 192.168.50.103.

#!/usr/bin/env python

from cocaine.services import Service
app = Service("example", host="192.168.50.103")
print(app.enqueue("write", "DATA").get())
print(app.enqueue("read", "DATA").get())


В качестве обработчика события http вызывается приложение на flask. Сейчас в HTTP-proxy поддерживается два способа определения того, в какое приложение какое событие послать. Первый способ предполагает, что переданный URL построен по шаблону: ///tail?arg=1&args=2. Соответственно URL обрезается в начале. В приложение он поступит в виде /tail?arg=1&args=2. Это очень простой и примитивный способ.

При втором способе решение принимается на основе специальных заголовков X-Cocaine-Service, X-Cocaine-Event. Их можно проставлять при помощи nginx.

Таким образом, следующие два запроса попадают в одинаковые обработчики:
curl "http://localhost:8080/read" -H "X-Cocaine-Service: example" -H "X-Cocaine-Event: http"
curl "http://localhost:8080/example/http/read"


Балансировка между версиями приложения



Наше облако позволяет запускать одновременно несколько версий одного приложения и производить балансировки нагрузки между ними. Идея заключается в том, что мы объединяем несколько приложений в группу и назначаем им веса в рамках этой группы. Таким образом доступ к приложениям будет осуществляться не по имени приложения, а по имени группы. А в рамках группы выбор будет сделан исходя из веса. Это очень полезная возможность. Располагая ей, можно вводить новые версии приложений в облако, подавая на них лишь часть нагрузки (скажем, 5%). А затем увеличивая эту долю. При этом можно не удалять предыдущую версию приложения. Ведь в случае обнаружения бага в новой версии, можно так же легко вернуть нагрузку обратно, выполнив при этом лишь небольшое число команд.

Попробуем воспроизвести этот процесс. Для этого создадим группу для приложения example, скажем exampleGroup. Добавим в неё приложение example с весом 1000. И попросим ноду обновить списки групп. В логах это видно будет по записи
service/locator: adding group 'exampleGroup'



После этого то же самое приложение будет доступно по имени exampleGroup. Для того чтобы проверить, предлагаю изменить тестовое приложение, заменив в app.py приветствие в ручке hello:

@app.route('/')
def hello(name=None):
    return "HELLO! I'm version #2"


Затем зальём это приложение и запустим его и отредактируем роутинг группу, добавив приложение example2.


Если подергать приложение
curl "http://localhost:8080/exampleGroup/http/"


То можно заметить, что ответы от разных версий чередуются. Если выставить вес example2 равным 0, то нагрузка на него через HTTP-proxy перестанет идти через некоторое время. Оно характеризуется периодом, через который HTTP-proxy обновляет соединения до приложений. Используя клиентский код, изменения заметите сразу.

Вместо заключения


Вот так легко и быстро, как опытный повар готовит спагетти, мы приготовили кокаиновое облако. Теперь при наличии инфраструктуры хочется рассказать о том, как писать приложения для Cocaine на различных языках, как писать свои cервисы, как сделать фреймворк. Но об этом в другой раз.
Tags:
Hubs:
Total votes 101: ↑92 and ↓9+83
Comments65

Articles

Information

Website
www.ya.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия