Комментарии 14
интересно про то как пришли к тому что бы завернуть erlang в контейнеры
Тут все просто: относительная дешевизна и невежество девопсов.
Я написал несчетное количество костылей, с тех пор, как было принято решение «контейнеры — это круто». В вопросы бизнеса я не суюсь, я решаю задачи в том поле, которое растёт вокруг меня, да и мне лично любопытно решать не решенные до меня задачи.
Сначала контейнеры убили эрланговский failover, и нужно было придумывать свой на рафте. Потом rolling updates привели к собственной реализации epmd, которая отличает «старые» инстансы от «новых». Теперь вот — сохраняем горячий кэш. Весело же.
Контейнеры, и их оркестраторы вроде Кубернетес популярны потому что они предоставляют какие-то унифицированные правила для различных приложений, не важно на каком ЯП они написаны или с использованием какого фреймворка.
Например, для любых приложений можно сделать rolling update версий, или service discovery, или балансировку запросов в приложения, или обновление конфигурации. И будет это работать примерно одинаково , хоть у вас C++, хоть python. Не нужно для каждого фреймворка поддерживать свой велосипед, хотя в каждом конкретном случае этот велосипед может быть конечно лучше, чем какое-то обобщенное решение.
Разумеется не нужно все пихать в контейнеры просто ради контейнеров. Если вы точно знаете, что без контейнеров это работает лучше - пусть так и будет. Но нужно не забывать, что обслуживание любого кастома отнимает больше ресурсов, и требует больше знаний от команды эксплуатации.
Я абсолютный ноль в Erlang, я скорее из невежественных девопсов. Меня заинтересовал момент про сохранение стейта процесса при перезапуске/миграции, но я не уловил куда этот стейт сохраняется. Выгружается в другую ноду кластера на другом хосте? Или все в рамках одного контейнера или хоста происходит. Можете немного развернуть? Возможно тут есть проблема в терминологии, что считать хостом, и что процессом.
Кубернетис — это «ad hoc, informally-specified, bug-ridden, slow implementation of half of Erlang supervision trees». Понятно, что они популярны: идея-то блистательная, и проверенная: ей 40 лет скоро исполнится. Но на bare metal — я запустил приложение в 2015, и в прошлом году мы его перезапускали, чтобы OS под ним обновить. Чего мне лично катастрофически не хватает в контейнерах — возможности раскатки обновлений без необходимости перезапускать приложение, или, в терминологии эрланга, — hot code upgrade.
я не уловил куда этот стейт сохраняется. Выгружается в другую ноду кластера на другом хосте?
Да, вы всё верно поняли. В процессе роллинг-апдейта поднимается новая нода, туда переносится процесс и устаревшая нода отмирает. Такой «hot code upgrade» для бедных :)
———
Update: вот кусочек кода, который проверяет, хотим ли мы перенести процесс локально, просто в другой супервизор, или на другую ноду, — и переносит соответственно либо локальным вызовом, либо удаленным:
case node(to_dynamic_supervisor_pid) do
^me ->
DynamicSupervisor.start_child(
to_dynamic_supervisor_pid, worker_child_spec)
remote ->
:rpc.block_call(
remote, DynamicSupervisor, :start_child, [worker_child_spec])
end
Чего мне лично катастрофически не хватает в контейнерах — возможности раскатки обновлений без необходимости перезапускать приложение, или, в терминологии эрланга, — hot code upgrade.
Делается. В контейнер можно замапить overlay FS из того, что в постоянной FS контейнера, и примонтированного снаружи файлового дерева. Я это не настраивал, но использовал: у нас таким образом база данных хранилась - если есть файлы текущего состояния, они берутся из внешнего дерева, если нет - нижний слой оверлея хранит стартовое состояние.
Если у вас текущая версия приложения foo-1.8, а надо докатить до foo-1.9, вы в FS (заранее таким образом собранную из слоёв, вот тут надо подготовиться) подкладываете foo-1.9, и внутри контейнера оно мгновенно становится видно. Аналогично relup-конфиг. Дальше отдаёте известным вам образом команду апгрейда. Ну а понравился результат - готовите на следующий старт новую версию контейнера, чтобы 1.9 было по умолчанию.
Хаки есть, но девять лет (из моего примера) аптайма контейнера — это что-то из области фантастики.
Мне это напомнило, как ещё в 2000-м году или около того в одной из ещё фидошных эхообластей народ из разных ISP мерялся аптаймом их систем - 100 дней, 200, 300, 400, ура!, а потом пришёл кто-то из Зенона и сказал "а мы сервера по крону перезагружаем, чтобы не накапливалось проблем, и на меряния аптаймом смотрим как на детские игры" :) И как-то все сразу попустились.
9 лет не апгрейдить ОС... ну если это совсем внутренняя система за тремя файрволлами - верю. Но всё равно даже просто проверка, что оно встанет в случае чего - это перезагрузка. А то, что вы можете залить на файлуху новую версию приложения и сказать "прыгай!" и что это проще на бэйрметале чем в контейнере - кто бы спорил, я не буду:)
близкая аналогия: засунуть erlang в контейнер чем то схоже с тем что бы засунуть оркестратор+контейнеры в контейнер.
контейнеры rolling update vs Erlang hot code upgrade.(свои плюсы и минусы).
но эта статья как пример создания велосипеда для поддержки работы erlang в контейнере.
Ну не прям уж велосипеда; как я говорил, перенос процесса в другое дерево супервизоров — один из верхних пунктов в erlang/elixir faq, да и я тут повозился в сохранением ets/process dictionary не только при переносе, но и при падениях gen_server
, так что даже без костылей для контейнеров — какую-никакую добавочную стоимость оно имеет :)
но я не уловил куда этот стейт сохраняется. Выгружается в другую ноду кластера на другом хосте?
1. Есть понятие "Erlang node". Это один процесс ОС. Внутри него энное количество тредов ОС (для начала считаем, что один на ядро процессора плюс несколько на асинхронные действия), которые гоняют несчётное количество процессов Erlang (не путать с процессами ОС). Именно о переносе процессов Erlang идёт речь.
2. Эрланг-нода может быть: совсем самостоятельной; включённой в родные для эрланга сетевые взаимодействия с обменом данными не через UDP/TCP/etc. персональными связями доверия между двумя нодами; наконец, включённой в кластер, где все друг другу доверяют. Несколько нод на одном экземпляре TCP/IP стека живут под руководством epmd, который умеет указывать, куда приходить за коннектом к конкретной ноде (заданной по имени); epmd нормально держится один на стек, независимо от количества нод. Обсуждаемый вопрос в первую очередь относится к режиму кластера, где тотальное взаимное доверие.
В кластере есть механизм назначения процессам Erlang уникальных в пределах кластера идентификаторов, то есть такой сетевой межнодовый pid у каждого процесса. (Чуть упрощаю детали, тут и раньше, не важно для начального понимания.)
3. Кто и как распределяет эрланг-ноды по экземплярам TCP/IP стека, то есть физическим узлам или контейнерам уровня докера, кубера и прочих - вопрос за пределами обсуждаемого. Считаем, что кто-то есть и он умеет сделать, чтобы (универсально говорим) контейнер мог запустить erl с нужными модулями в FS, командной строкой и параметрами для вхождения в кластер - имя головных нод (epmd ищет по имени), IP (если отличается от отвечаемого в netdb) и кукой (общий пароль) для включения. Дальше нужен полный доступ всех нод со всеми (full mesh). Нормально - одна нода на контейнер, хотя ничто не мешает запустить хоть все в одном контейнере. Зачем? Например, для теста, или когда есть проблема, что какая-то часть процессов должна жить, когда остальные парализованы (бывало у нас, что нода раздувалась до сотен гигабайт и её сносило OOM killerʼом - а другая, выделенная для управления и контроля, оставалась за счёт этого жива).
И вот тут начинаем говорить про то, что процесс иногда можно переместить на другую ноду (считаем без извращений - в другой контейнер). Теперь можно смотреть в статью:)
Зачем перемещать - вопрос интересный. В большинстве случаев таки перенос на ходу не делают. Просто запускается новая версия вместо старой, новая регистрируется в каком-то глобальном справочнике (модуль global или своя замена) и начинает участвовать во взаимодействии, а текущие данные берутся из mnesia (база данных с состоянием на диске) или набираются по ходу. Есть штатные средства миграции (но обычно не один процесс, а "приложение"). Но у них всех специфики и ограничения.
В общем и целом, всё Erlang+OTP интересная и в чём-то очень вкусная разработка. Но и недостатков вагон.
Теперь про это:
Например, для любых приложений можно сделать rolling update версий, или service discovery, или балансировку запросов в приложения, или обновление конфигурации.
Да, собственные механизмы Erlang с механизмом Kubernetes и аналогов плохо совместимы. Они решают сходные задачи, но совсем иначе. С приходом сначала докера, потом кубера оказалось, что подавляющему большинству девопов проще работать в стиле кубера, и собственные механизмы Erlang не нужны. Например, Erlang позволяет обновлять версию почти всего на ходу, без рестарта ноды. Но это мало кем сейчас используется. Можно подсунуть FS с новым кодом в запущенный контейнер, но обычно делают именно на rolling update версии контейнеров. Аналогично с миграцией, проще сделать, что клиент переконнектился, а состояние в БД в отдельном пуле контейнеров, чем переносить один процесс кастомными механизмами.
Перенос процесса с одной ноды на другую