четверг, 21 мая 2015 г.

systemd-nspawn для разработчика

На данный момент я работаю в проекте который в качестве целевой платформы установки использует Debian 7 при этом хочется переехать на Debain 8, а сам я сижу на Arch Linux. В результате часто требуется быстренько оказаться в правильном окружении и пособирать свои последние правки. Чаще всего для этого я использовал VirtualBox как наиболее просто конфигурируемое решение, но всегда хотелось побаловаться с лёгкими контейнерами. Правда, всегда отпугивала скорость разворачивания таких решений. Я экспериментировал с LXC и systemd-nspawn, но в первом случае разворачивание виртуалки занимает больше времени чем хочется, а во втором случае всё гладко только если гостевая система использует systemd.

Но вот, свершилось, Debain 8 использует systemd по умолчанию и его можно быстренько разворачивать. Правда, кто-то успел немного подгадить с другой стороны. Теперь в арче сервис systemd-nspawn@ по умолчанию предлагает иметь противоестественные сношения с настройкой идеологически правильного разделения сети между контейнерами добавив ключ --network-veth. Раньше этот сервис позволял запустить контейнер так, что он просто видел хостовый интерфейс и им пользовался, что совершенно не подходит для облачных решений (для которых есть отличные и мощные проекты, такие как OpenVZ и LXC), но очень удобно для нужд разработчиков (а именно для этих целей и позиционировалась разработка systemd-nspawn). Дабы раз и навсегда искоренить этот печальный недостаток, копируем этот сервис из /usr/lib/systemd/system в /etc/systemd/system и уничтожаем использование ненужной "идеологической правильности".

Итак создаём гостевуху:

yaourt -S debootstrap # Если ещё не установлен
sudo debootstrap jessie /var/lib/container/debian8 http://mirror.yandex.ru/debian
sudo systemd-nspawn -D /var/lib/container/debian8
Мы оказались в сверх-минималистичном дебиане с целью задать пароль пользователю root и установить пакет dbus (без него утилита machinectl не сможет подключиться к запущенному контейнеру). Выполнив эти две нехитрые операции нажимаем Ctrl+D чтобы вернуться в основное окружение и честно загрузив контейнер входим в него через парадное крыльцо для дальнейшей настройки:
sudo systemctl start systemd-nspawn@debian8
sudo machinectl login debian8

Вот готово почти всё. Точнее для одноразового использования готово вообще всё. А вот если контейнер планируется использовать для тестов регулярно, то имеет смысл поставить туда ssh и научить systemd автоматически запускать виртуалку при попытке в неё зайти по этому самому ssh. Для этого пишем файл на хосте
/etc/systemd/system/systemd-nspawn@debian8.socket:

[Unit]
Description=The SSH socket of debian8-nspawn container

[Socket]
ListenStream=4022

[Install]
WantedBy=sockets.target
А в гостевой ситеме надо отключить безусловный автозапуск SSH и настроить его активацию через сокет добавив два файла:
/etc/systemd/system/sshd.socket:
[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=4022
Accept=yes

[Install]
WantedBy=sockets.target
и /etc/systemd/system/sshd@.service:
[Unit]
Description=SSH Per-Connection Server for %I

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket

[Install]
WantedBy=multi-user.target
Alias=sshd.service
После чего в контейнере переключаем SSH на активацию через сокет:
sudo systemctl disable sshd.service
sudo systemctl enable sshd.socket
И на хосте активируем сокет запуска машины:
sudo systemctl enable systemd-nspawn@debian8.socket

Ну вот теперь можно настроить профиль в Konsole и быстренько заходить в виртуалку в два клика. Запуск контейнера до состояния принятия ssh соединения у меня занимает около 2х секунд, что несравнимо меньше времени ожидания старта VirtualBox виртуалки, а сборка кода с помощью ninja эффективно параллелиться по всем процессорам демонстрируя великолепную производительность.

понедельник, 11 мая 2015 г.

RAII вокруг C в одну строку

Работая C++ программистом я очень часто сталкиваюсь с библиотеками написанными на C. В этом языке (в отличии от C++) есть простой и удобный способ поддерживать прямую ABI совместимость библиотек. В публичных хедерах имеем только forward-declaration структуры и функции принимающие её через указатель. А все её потроха оставляем исключительно внутри библиотеки. Так работает libevent, sqlite и очень многие другие беблиотеки написанные на C. Шаблон pImpl стремиться скопировать эту модель достижения стабильности бинарного интерфейса в C++, но не закрывает всех проблем.

В результате такого подхода в коде библиотек есть набор структур неизвестной природы, набор функций для создания и инициализации этих структур, а так же набор функций для их уничтожения. Как не сложно заметить, тут есть контсруктор, деструктор и класс. В большинстве случаев в работе с такими библиотеками из C++ всегдя заводятся классы оборачивающие эти C-шные интерфейсы, так как ручной вызов функций 'деструкторов' может быть пропущен при пролёте исключений. При этом пишется много тривиального нудного кода. Но...

В C++11 появляетя удобный класс которые способен устранить необходимость написания таких RAII обёрток для каждой новой сущности, это std::unique_ptr. Так на хабре можно найти пример exception-safe обёртки вокруг libevent'овского веб сервера использующего только unique_ptr для своевременной чистки ресурсов. Правда код при этом получается не особо читабельным, ибо всюду мы видим множество длинных специализаций этого шаблонного типа использующих decltype для проведение сеансов гадания по руке указателю на функцию с целью выведения её типа.

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

auto dir = scoped(::opendir(path), ::closedir);  
for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
}  
выглядит куда понятней чем такой:
 std::unique_ptr<DIR, decltype(::closedir)> dir{::opendir(path), ::closedir};  
 for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
 }  
Определение переменной dir в первом случае компакто и мгновенно разбирается глазами. Да и функция scoped пишется один раз и всегда спокойно кочует из проекта в проект позволяя в одну строку прямо по месту создавать RAII обёртки вокруг C-шных структур:
 template<typename T, typename Del = std::default_delete<T>>  
 inline auto scoped(T *t, Del &&del = std::default_delete<T>()) {  
   return std::unique_ptr<T, Del>{t, del};  
 }