Некоторое время назад мы довольно подробно начали рассказывать об одной из базовых облачных технологий Яндекса — Elliptics. Сегодня настала очередь поговорить о другой — той самой, под которой работают «эльфы» и которая делает мечту о своем облаке чуть ближе к реальности. Речь пойдет о Cocaine.
Cocaine (Configurable Omnipotent Custom Applications Integrated Network Engine) — это PaaS-система (Platform-as-a-Service) с открытым исходным кодом, являющаяся по сути app engine и позволяющая создавать собственные облачные хостинги приложений — такие, как Google AppEngine, OpenShift, CloudFoundry или Heroku.
Всем известно, что облака могут решить все инфраструктурные проблемы, превратить издержки в прибыль и насытить вашу жизнь бесконечной радостью и счастьем на веки веков. Единственным препятствием на пути к этим целям являются, собственно, облака. IaaS, PaaS, SaaS? Whatever-as-a-Service? Какой именно загадочный набор букв нужно выбрать, чтобы всё наконец стало хорошо?
Мы потратили немало времени, изучая эти вопросы, отбирая лучшие, на наш взгляд, идеи и концепции, чтобы построить такую облачную платформу, которую хотелось бы немедленно установить, настроить и успешно использовать.
Если вас заинтересовало — добро пожаловать под кат.
История
История появления на свет данной технологии тесно связана именно с Heroku. Давным-давно, когда облачные технологии не были так популярны, но уже была Heroku, ею заинтересовался основатель нашего проекта Андрей Сибирёв.
Heroku на тот момент представлял собой app engine, которая поддерживала лишь Ruby, но сама идея была революционной. Подумать только: можно было написать свое рельсовое приложение, залить его в облако и совершенно не думать об инфраструктурных проблемах. Нужна база данных? Нет проблем — есть add-on’ы! Стянуть логи? Опять без проблем! Также магическим образом решались проблемы балансировки нагрузки. Сайт молод и развивается? Хорошо, у него есть ресурсы. Сайт уже популярен? Хорошо, облако поможет ему “раздуться” под нагрузкой и выжить. К тому же быстрый деплой через git уже с самых ранних пор приучал приобщаться к прекрасному.
Звучит круто. Во всяком случае, так Heroku позиционировал себя. В реальности все было, конечно, мрачнее.
У них была прекрасная идея, была реализация, но практически нигде не было описано, как она вообще работает. Как известно, если хочешь разобраться в чем-то неведомом, новом, поставь себя на место разработчика. Как бы ты написал с учетом увиденных плюшек и недостатков? Так был начат open-source проект облачного app engine’а. Забегая вперед, скажу, что, по всей видимости, такой же логикой руководствовались иностранные разработчики, что дало толчок к разработке еще нескольких аналогичных продуктов: OpenShift и CloudFoundry. О них будет рассказано чуть позднее.
Cocaine изначально был гаражным just-for-fun проектом, но всё изменилось, когда в Яндексе возникла потребность в какой-нибудь раздуваемой платформе для приложений, которая могла бы справится с нагрузкой “может быть 10, а может быть и 100 000 RPS” для внутренних целей. Тем временем на подходе был Я.Браузер с такими же потребностями, и стало ясно, что проекту пора вылезать из гаража.
На время оставим повествование в сторону и окунемся в мир сухой статистики и неоспоримых утверждений.
Где Яндекс использует Cocaine
Яндекс.Браузер — весь браузерный бэкэнд работает через Cocaine. Например, “Умная строка”, “Быстрые ссылки”, “Любимые сайты”. Внутренняя инфраструктура Яндекса.
Поддерживаемые платформы
На данный момент Cocaine может быть развернут из пакетов на следующих линуксовых дистрибутивах:
- Ubuntu 12.04 (Precise Pangolin) и старше
- RHEL 6 и производных.
Поддержки Windows, к сожалению, нет, и не планируется, но это не значит, что пользователи, например, C# не смогут воспользоваться облачными сервисами, которые крутятся где-то в другом месте — достаточно написать подходящий фреймворк (что это такое будет рассказано далее).
Из исходников Cocaine может быть собран практически на любой *nix системе, в которой есть boost 1.46+ и компилятор с поддержкой C++11 стандарта уровня GCC 4.4 (то есть, практически никакой).
В качестве бонуса, Cocaine успешно собирается из исходников на Mac OS X. Поддержка технологии Docker (будет рассмотрен далее) требует линуксовое ядро не младше 3.8.
Why so Cocainy?
Давайте поподробнее рассмотрим, чем же Cocaine отличается от его основных конкурентов.
Начнем с Heroku.
Heroku прежде всего не open-source технология со своей ценовой политикой, что может отпугнуть некоторых клиентов. Но за счет явной монетизации, у Heroku более развита поддержка, есть хорошая (теперь) документация с картинками. Удобнейшая система деплоинга приложений, основанная на git, с самого начала заставляет наводить порядок в коде и дает возможность сделать откат в случае факапа.
Куча аддонов позволяет легко удовлетворить потребности ваших приложений в различных базах данных, кэшировании, очередях, синхронизации, логгировании и многом другом.
Сами приложения при запуске полностью изолируются друг от друга в процессах, называемых dyno, которые распределены по специальной dyno grid, которая состоит из нескольких серверов. Dyno запускаются по мере необходимости в зависимости от нагрузки и убиваются при отсутствии таковой — таким образом в Heroku происходит балансировка нагрузки и обеспечение отказоустойчивости.
Несмотря на изначальную поддержку одних лишь Ruby, сейчас Heroku предоставляет возможность писать приложения на таких языках как Java, Closure, Node.js, Scala, Python и (внезапно) PHP.
Разработка CloudFoundry и OpenShift начиналась практически одновременно с Cocaine. Оба являются open-source проектами с сильной поддержкой больших компаний — VMware и RedHat соответственно. CloudFoundry на данный момент уже успел пережить одно перерождение. При этом поддержка языков ограничивается Java, Scala, Groovy, Ruby и JavaScript.
У OpenShift с этим получше — поддерживается в дополнение Python, Perl и есть возможность самому сделать поддержку требуемых языков. К сожалению, приложения между собой могут общаться только через http, что несколько ограничивает сферу применения Облака.
Cocaine при этом выгодно отличается от них:
- присутствием адаптеров к различным языкам (фреймворков), что очень сильно упрощает работу с Облаком;
- возможностью приложений общаться между собой не только через http, что, например, дает возможность построить свою платформу облачных вычислений;
- сильной поддержкой изоляции приложений — технологию Docker, кроме нас, официально поддерживает только OpenShift.
Краткий принцип работы
Теперь давайте рассмотрим, как же на самом деле работает наше облако, попутно обсудив причины, повлиявшие на выбор того или иного решения.
Постулируем следующее неоспоримое утверждение: облака призваны решить инфраструктурные задачи.
Одной из таких задач является обеспечение отказоустойчивости. Вряд ли бы кого-нибудь сильно впечатлила облачная платформа, работающая только на одной машине. В таком случае облако умирает вместе с машиной. Облако Cocaine’а состоит из одной и более независимых машин, на которых установлен сервер “cocaine-runtime”. На самом деле, это все, что необходимо для минимального функционирования платформы.
Мы решили не идти путем CloudFoundry и заводить на каждую логическую деталь облака отдельную программу. Так, например, то, что в других облаках называется HealthManager — контроль жизни запущенных приложений — у нас объединено в единую сущность. Такое решение, с одной стороны, упрощает администрирование ноды и уменьшает количество точек отказа, но с другой, если “падает” cocaine-runtime, то целая нода выбывает из списка на некоторое время. Но испытание временем и нагрузками показали, что выбор такого решения был удачным.
Представьте, у вас есть десяток машин, на каждой из которых работает cocaine-runtime. Но эти машины ничего не знают друг о друге. Когда рассматривались варианты выбора схемы построения топологии облака, было рассмотрено два варианта: все знают обо всех и несколько знают обо всех. В первом случае гарантировалась бы идентичность всех нод: если какая-то нода умрет, ничего существенного не изменится. Но одновременно с этим происходит смешение логики, т.к. тут по сути задействованы две сущности: первая — агрегация узлов и управление ими; вторая — управление своим узлом. Во втором случае полностью разделяется логика cocaine-runtime агрегирующих нод от остальных, но эту ноду нужно было бы сконфигурировать особым образом. В конечном итоге был выбран второй вариант.
Пользователи не знают ничего о местонахождении сервисов или приложений, к которым они обращаются, — известен только адрес аггрегирующей ноды и имя приложения. Нода сама выбирает оптимальную для запроса машину, на которой он и исполняется.
Облачный app engine не назывался бы так, если бы не умел запускать приложения. На самом деле cocaine-runtime’у все равно, что запускать. Единственное требование — чтобы файл был исполняемым. Другое дело, что это приложение будет практически сразу убито, т.к. не сможет ответить на простое handshake сообщение и, в принципе, нет никаких общих средств для того, чтобы контролировать приложение снаружи. Если оно работает, это еще не значит, что оно не зависло, к примеру. И тут мы вплотную подходим к теме контроля жизни приложений (или воркеров).
Запущенное приложение нужно каким-то образом контролировать. На самом деле, необходим самый минимум: приложение должно время от времени оповещать сервер о том, что оно живо heartbeat командами и красиво умирать при terminate команде. Если приложение не отвечает слишком долго, то оно считается умершим и ему посылается SIGKILL, чтобы добить. Другими словами, воркер должен реализовывать специальный несложный протокол. Для удобства разработки приложений были сделаны адаптеры под различные языки, которые мы называем фреймворками.
Пятиминутка истории
Фреймворки представляют из себя специальное API, реализующее наиболее нативный для конкретного языка способ коммуникации с облаком. Например, под фреймворком скрыта реализация внутреннего протокола, позволяющая очень просто осуществить вызов облачного сервиса и работу с ним. Также реализованы средства для упрощения работы с асинхронными вызовами. На данный момент уже реализована поддержка:
- C++
- Java
- Python
- Ruby
- Node.js
- Go
Есть средства для управления всем этим облачным богатством как через консольный интерфейс, так и через веб. Их реализация во многом очень похожа на аналогичные инструменты Heroku или CloudFoundry — приложения можно деплоить, запускать, останавливать, управлять и настраивать, а также собирать статистику и смотреть логи. Тут придумать что-то новое действительно сложно, хотя инструменты Amazon EC2 пока для нас являются примером в плане простоты и удобства, мы стремимся к ней.
Архитектура
авайте сделаем шаг назад и посмотрим на облачную инфраструктуру свысока. Что из себя представляет облако? Обычно есть несколько машин, которые предоставляют ресурсы. Эти машины связываются каким-то образом в единую сущность каким-то управляющим элементом. Предоставляемые общие ресурсы используются для организации различных сервисов и пользовательских приложений. Ничего не напоминает?
Существует категория программного обеспечения, в которую входят программы, занимающиеся управлением инфраструктуры одного (как правило) компьютера и предоставлением его ресурсов пользователю. В них есть четкое разделение на модули, есть управляющая сущность, есть пользовательское пространство. Речь идет, конечно, об операционных системах. Мы подумали, почему бы не сделать нечто похожее, но в случае с облачной инфраструктурой?
Именно так построен Cocaine.
В центре всего находится ядро, в которое входят основные иерархии сервисов, драйверы и API. Сервисом по сути является некая библиотека, написанная на C++, подключаемая к ядру, которая предоставляет Облаку некую службу. Сервисы запускаются одновременно с cocaine-runtime и живут в отдельном потоке в единственном экземпляре все время его работы.
Основным сервисом является сервис Node, позволяющий управлять запуском, остановкой и работой приложений. Изначально запущенное приложение не имеет воркеров — экземпляров этого приложения — они начнут спавниться в момент подачи и увеличения нагрузки на приложение.
У каждого приложения есть Engine, который занимается диспатчингом очереди сообщений между воркерами и клиентами, управлением и балансировкой количества этих воркеров.
Сервис Locator позволяет связывать независимые ноды в единый кластер, а также осуществлять поиск запрашиваемого клиентом приложения или сервиса в Облаке. Например, если какое-то приложение запрашивает сервис Logger, то именно Locator решает, какой именно endpoint отдавать ему. Вместе с endpoint также отдается таблица диспетчиризации методов сервиса. Кстати о ней: в отличие от RPC механизма CORBA или Thrift, мы решили избавиться от обязательного описания интерфейсов (IDL) сервисов (и приложений). Вместо этого, мы загружаем таблицу методов при каждом резолвинге, а далее фреймворки реализуют нативный для каждого языка способ работы с ней. Все, что нужно для этого — лишь имя запрашиваемого сервиса и, возможно, версия.
Выиграли ли мы от этого? Определенно, да! Убрав необходимость определять IDL для каждого приложения мы серьезно облегчили их разработку под динамические языки, такие как Ruby или Python. Если у какого-то приложения изменилось API нет необходимости переписывать стабы под все фреймворки — в случае динамически типизированных языков методы вообще создаются динамически.
Продолжаем о сервисах
Вряд ли есть хоть одно облако без средств логгирования — у нас оно реализовано в виде сервиса Logger, представляющим собой единую точку транспорта логов со всей ноды.
Наконец, сервис Storage представляет собой абстрактный интерфейс для хранения чего угодно — например, самих приложений, манифестов, профилей и прочей информации, необходимой в том числе самим приложениям.
Архитектура сервисов открыта для расширения и аналогична модулям ядра linux. Можно написать собственные сервисы, а также расширить уже существующую иерархию.
Особняком стоят драйверы, назначение которых состоит в генерации событий, обрабатываемых облаком. Например, таким событием может быть изменение какого-то файла или каталога, таймаут или иное событие. Можно (как вариант) написать драйвер к АЦП, которая будет сбрасывать сигналы на обработку по мере их поступления.
В отличие от многих аналогичных PaaS, в которых приложения и сервисы могут общаться между собой только через HTTP, в Cocaine общение происходит через механизм RPC поверх бинарного HTTP/2.0-like протокола. Это дает нам возможность, например, взаимодействовать между различными приложениями напрямую, используя средства, предоставляемые фреймворками. Более подробно об этом можно прочитать здесь. Разумеется, сам HTTP интерфейс никуда не делся. Для каждого фреймворка предоставляются нативные средства, упрощающие разработку HTTP приложений. Например, в случае Python, есть flask-подобные декораторы.
Снаружи до приложений можно достучаться также не только по HTTP интерфейсу (хотя такая возможность предоставляется), а например, через ZeroMQ или собственные расширения.
Масштабирование и изоляция
Давайте посмотрим более подробно, как же на самом деле реализована распределенность Облака и как оно пытается жить под нагрузкой.
При запуске кластера отдельные ноды (машины с запущенным cocaine-runtime) узнают друг о друге при помощи внутреннего сервиса Locator, который регистрирует каждую машину в multicast группе, организуя тем самым коммуникацию между всеми нодами и отдельной нодой, сконфигурированной под Metalocator, являющийся входной точкой для облака и осуществляющий базовую балансировку нагрузки. Если на какой-то отдельной машине появится новое приложение или размножится уже существующее, металокатор об этом сразу узнает и направит на него нагрузку.
Облако при помощи вышеупомянутого Engine также осуществляет мониторинг очереди сообщений и контроль нагрузки. При увеличении нагрузки на приложения, платформа автоматически запустит требуемое количество инстансов этого приложения, руководствуясь своими внутренними представлениями о нагруженности нод и доступности слотов на различных машинах. Под слотами в данной ситуации понимается какой-то унифицированный ресурс машины.
Система спавнинга и изолирования приложений вынесена в отдельную сущность, называемой Isolate. В стандартной конфигурации облако будет запускать новые инстансы приложений используя обычные процессы. Если в качестве плагина установлена поддержка Docker’а, то облако будет запускать новые приложения в отдельном изолированном контейнере.
Изоляция? Легко!
Docker — это open-source технология, предоставляющая простой и эффективный способ для создания легковесных, переносимых и самодостаточных контейнеров из любых приложений. Однажды созданный, такой контейнер будет одинаково хорошо работать практически в любой среде, начиная от ноутбуков разработчика и тестировщика и заканчивая кластерами из тысяч серверов. Это, например, позволяет проводить изолирование приложений вместе с их бинарными зависимостями и больше не думать о том, что твое приложение начнет себя вести по-другому в облаке из-за других версий библиотек, либо вообще не встанет.
В основе технологии Docker лежат обычные Linux Containers (LXC), которые предоставляют способность запустить приложение в изолированной среде благодаря использованию namespaces и cgroups. В отличие от сред с полной виртуализацией, таких как Xen или KVM, контейнеры используют общее ядро и не предоставляют возможности эмуляции устройств, но при этом их использование не влечёт дополнительных накладных расходов и запускаются они практически мгновенно. Помимо непосредственно контейнеризации, Docker предоставляет инструменты, упрощающие конфигурирование сети, распространение и развертывание образов приложений. Одним из таких инструментов является создание образов приложений из нескольких независимых слоев.
При разработке приложений для Cocaine’а, тянущих за собой бинарные зависимости (например, приложения на C++ или Python), технология Docker может оказаться очень полезной для изоляции этих бинарных зависимостей.
Erosion resistance
Представьте себе, что вы создали приложение, залили его на сервер, настроили и оставили на произвол судьбы. Через неделю вы решаете проверить как оно работает и с горестью обнаруживаете, что приложение упало давным-давно по абсолютно различным причинам:
- спонтанные обновления операционной системы или зависимых библиотек;
- переполнение диска логами;
- зависание или падение самого приложения;
- проблемы с самими железом сервера.
Эти эффекты известны под названием “Software erosion”.
Cocaine является erosion-resistance технологией. От спонтанных обновлений библиотек и ОС ваши приложение спасает система изоляции. В случае падения или зависания приложения, оно будет автоматически перезапущено самим облаком, а точнее системой, называемой в других облачных платформах как health manager. Проблемы с самим железом нивелируются самой облачностью.
Плагины и сервисы
Про плагины и сервисы уже было кратко рассказано ранее в контексте ядра платформы. Плагины представляют собой модули расширения архитектуры. Например, поддержка Docker’а осуществлена как раз с помощью плагина. Они подразделяются на:
- Isolate — система изоляции приложений
- Driver — система генерации внешних событий для приложений
- Storage — система хранения.
- Logging — система логгирования.
- Gateway — система балансировки
- Services — остальные пользовательские сервисы
С помощью плагинов можно менять реализацию вышеперечисленных частей системы, сделав лишь изменение в конфигурационном файле. Хотите заменить системы логгирования с Syslog на Logstash? Отлично, меняем три строчки в конфиге, код при этом остается неизменным. Может, не устраивает дефолтная система балансировки и вам хочется IPVS? Запросто! Ну а что если не хватает предустановленных плагинов и хочется реализовать что-то новое? Что ж, писать их совершенно несложно, а мы будем очень рады пул реквестам.
Самым последним пунктом в плагинах стоят сервисы. Если строго, то сервисы представляют собой абстрактные фронтенды к различным частям инфраструктуры.
Например, сервис UrlFetch позволяет приложениям делать http запросы и организовывать контроль над этими запросами, т.к в общем случае приложениям не разрешается выходить наружу из контейнера из соображений безопасности.
С помощью плагина Logstash можно настроить агрегацию логов со всего кластера, их унифицированную обработку, индексацию и хранение в том же Elasticsearch с последующим удобным отображением в Kibana (что мы и делаем в Яндексе).
С помощью плагина Elliptics можно осуществлять хранение всей информации в нашей одноименной системе. В качестве альтернативы реализованы MongoDB plugin и Elasticsearch сервис, которые позволяют использовать эти технологию в качестве системы хранения информации.
Заключение
Это вводная статья начинает цикл статей об облачной платформе Cocaine. В дальнейшем будет рассмотрена более детально архитектура платформы, ее достоинства и недостатки (куда уж без них?).
Мы покажем вам, как развернуть собственное облако из исходников или пакетов и как его сконфигурировать. Затем последует описание фреймворков с примерами реальных приложений, написанных с их помощью. Далее последует описание уже написанных плагинов и сервисов и будет рассмотрен пример написание собственного плагина или сервиса (а может и оба). В заключение мы рассмотрим более подробно Cocaine’овый протокол и как можно добавить поддержку другого языка, то есть написать собственный фреймворк.
Разработку мы ведем на github. Вся самая актуальная информация доступна именно здесь. В разделе Wiki есть документация о том, как быстренько собрать Cocaine из исходников, есть описание внутренних деталей. Если вас заинтересовала платформа, вы можете попробовать ее в деле через готовый vagrant образ. Если есть вопросы, задавайте.
Автор: 3Hren