Socket Activated Containers (Unicorn + Systemd)
У клиента есть большое количество медийных спец проектов (~250). Это виджеты, лендинги, апишки и т.д. С 2012 года это все живет на 1 машине со связкой Nginx + Passenger + Ruby.
Все хорошо, за исключением момента обновления ОС, когда все проблемы со старыми/новыми версиями пакетов вылезают и заявляют о себе в полный голос.
Казалось бы, идеальная история для контейнеров, но есть одно но. Passenger или PHP-FPM умеют то, что из коробки нет даже у Kubernetes — это старт по входящему трафику.
На просторах сети это называется (гуглится) — Socket Activation. На Network или Unix сокет приходит пакет, сервис запускается. Покопавшись на Stack Overflow и некоторых админских форумах, понял, что мысль о том, что контейнер, можно только по необходимости поднимать интересует многих. Такие запросы есть у больших ребят — привет велосипеды (читай свои решения).
Оказывается то, что нужно написали уже лет 7 назад. Пост про реализацию функционала в systemd датирован 2011 годом. Ну и в 2013 уже вышел пост про старт контейнера с помощью той же функциональности. Есть одно но, там используются стандартные средства systemd для виртуализации, нас же интересуют более популярные на данный момент Docker.
Во всех статьях говорили о proxy сервисе, который принимает трафик и отвечает за запуск контейнера. Gочему нельзя сокет использовать сразу в контейнере? Вопрос этот возникает, потому что на каждый сервис придется делать 3 конфига systemd и минимум 2 сокета, что как мне казалось много и не слишком красиво. Посмотрел потом на конфиги kubernetes и 3 конфига systemd показались маленькими, простыми и понятными.
Но так что с сокетом то? А вот я не нашел способа как его пробросить в контейнер. Если кто знает, с радостью послушаю. Проблема не в софте (unicorn и puma), который может переиспользовать проброшенный сокет. Есть стандартные процедуры для этого. Через ENV переменные передаются ID процесса (LISTEN_PID) и номер слушающего сокета (LISTEN_FDS), после чего софт не должен пытаться открыть новый, а переиспользовать (подключиться) к сокету с соответствующими координатами.
Проблема заключается в том, что ID процесса и ссылку на сокет будут переданы с host машины, контейнер естественно ничего о них не знает.
Шарить данные с хост машиной конечно можно, но безопасно ли?
Сделал gist с файлами для тестов. Простейшее Rack приложение и набор сервисов для systemd. Чтобы это все завелось надо завести отдельного пользователя, назвал его sact
, и сложить Gemfile, Gemfile.lock, unicorn.rb, config.ru
файлы в /apps/sact/sact-git
. Установить docker, сложить systemd сервисный файлы в /etc/systemd/system
и перезагрузить демона через systemctl daemon-reload
. Также понадобиться nginx. Но думаю для тех кто это читает не составит большого труда разобраться.
Если обратиться на 80 порт localhost
curl localhost
, то получим Simple app for test
. Небольшая задержка на старте нас не пугает, потому что подразумевается что к сервисы обращаются не часто.
Что я не успел сделать — отслеживание трафика за определенный момент времени с целью остановить сервис. По идее это просто парсинг логов, если за последние N минут ничего не было, то останавливаем сервис с контейнером. Если мы соберемся делать такое решение на наших 250+ проектов, то обязательно напишу.
Stay tuned…
P.S. Где что подсматривал:
- От разработчика systemd про активацию контейнеров.
- Это на Python, но можно посмотреть как пробрасывается Socket из Systemd
- А это пример на Ruby как работать с сокетом Sytemd
- Откуда взял гифку