Pull to refresh

Экстренная реанимация epmd

Reading time3 min
Views9K
(Проблема, по всей видимости, чрезвычайно экзотическая, но в плане «как оно внутри устроено» достаточно познавательная.)

Вот, допустим, работает у вас приложение, написанное на Эрланге (ну, скажем, тот же ejabberd). Давно работает, хорошо работает, но в один прекрасный день вы пытаетесь запустить управляющий скрипт (ejabberdctl, соответственно), а он вам выдает «nodedown» или еще что-нибудь страшное в этом духе, мол, не отзывается никто. При этом само приложение прекрасно отзывается на все клиентские запросы и слыхом не слыхивало о том, что оно down. По внезапному наитию вы запускаете epmd -names и — о, ужас! — получаете пустой список.

Программы на Эрланге используют для связи между собой нотацию node@host, физически же каждый узел (читай — системный процесс) открывает для этого случайный высокий порт. Задача сервиса epmd — связать между собой логическую адресацию по имени и физическую адресацию по номеру порта. Своего рода аналог DNS, с той разницей, что без реестра epmd кластер на Эрланге разваливается на кучку отдельных глухонемых узлов — что у нас только что по какой-то загадочной причине и произошло. Можно, конечно, начать искать виновных, но сначала все-таки неплохо бы поднять систему на место.

Что в такой ситуации делать? Можно, конечно, просто насильно перезапустить приложение, но, с одной стороны, клиенты отвалятся, с другой, такой красивый uptime жалко… Вот если б можно было как-нибудь восстановить реестр на живой системе, а?..

Never fear! Крохи информации, откопанные в интернетах, сообщают, что epmd якобы достаточно кинуть один-единственный правильный пакет; скажем, накропать быстренько программку на С… Ну, на низкий уровень нам идти совсем не обязательно, с сокетами и на самом Эрланге неплохо работается, но хотелось бы еще проще. Хочется — пожалуйста: натыкаемся на модуль erl_epmd и видим в нем — слава открытым исходникам! — волшебную функцию register_node/2, принимающую имя узла и номер порта.

С помощью netstat узнаем, какой порт используется нашим приложением — пусть будет 23456 — и запускаем в терминале эмулятор:

$ erl

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

> erl_epmd:start().

… но, поскольку узел все же анонимный, сначала надо руками запустить ген-сервер. Теперь вызываем нашу волшебную функцию:

> erl_epmd:register_node( ejabberd, 23456 ).

Теперь мы снова можем общаться с потерянным узлом. Для проверки запускаем еще раз epmd -names и радуе…

Так, стоп, не радуемся. Все дело в том, что, хоть epmd и принимает без лишних вопросов регистрацию любого свободного имени на любой используемый эрлом порт, он также запоминает, с какого соединения пришел этот запрос на регистрацию, и как только соединение отвалится, имя опять освободится. Поэтому сейчас для корректной работы системы придется оставить запущенным никому не нужный временный эмулятор. Непорядок? Непорядок. Надо бы как-то пробраться внутрь приложения и вызвать register_node оттуда…

Элементарно, Ватсон (когда уже знаешь, как). Запускаем второй эмулятор, на этот раз с именем:

$ erl -sname repair

… и пингуем узел с приложением:

repair@hostname> net_adm:ping( ejabberd@hostname ).

(Если получили pong, все по плану. Если получили pang, закрываем ставшую бесполезной статью и начинаем с криками бегать по комнате.)
А вот теперь фокус. Заключается он в том, что соединение между узлами, будучи установленным — что мы только что сделали — остается открытым и дальше, и для общения по нему реестр epmd уже не нужен. Поэтому мы теперь идем обратно в первый эмулятор и прибиваем его:

> halt().

Что мы только что сделали? Мы, помимо прочего, закрыли соединение к epmd, и он освободил имя ejabberd для перерегистрации. (Посмотрите еще раз в epmd -names, если не верите.)
Теперь по нашему открытому соединению, оставшемуся висеть под потолком без страховки, мы можем, пользуясь прекрасным модулем rpc, зайти на узел приложения и блистательно завершить сеанс одновременной игры на трех процессах:

repair@hostname> rpc:call( ejabberd@hostname, erl_epmd, register_node, [ejabberd, 23456] ).
repair@hostname> halt().

И усе. Закрываем терминалы и делаем то, ради чего в самом начале вообще полезли в систему.

Вопросы, уточнения, исправления приветствуются.
Tags:
Hubs:
+5
Comments12

Articles