[О блоге] [наверх] [пред] [2020-08-21 13:50:00+03:00] [6d663c82fdf9b0534da062f02f639730dcc235f1]
Темы: [systemd]

Давно не писал про systemd... ибо он просто не работает

Вчера с коллегами поднимали NSD сервер на современном CentOS.
systemctl start nsd отрабатывает, успешный код возрата, ничего не
говорит. А демона нет. В логах, выясняется, что nsd то в принципе не
запускался потому что :53 порт занят. Если запустить nsd руками, то он
честно возвращает плохой код. Что это значит? Значит что systemd даже с
своей первоочередной задачей не справился, не работает -- отвечает что
всё хорошо, хотя демон честно вышел с ошибкой. Ну и как с этим дерьмом
работать? Вопрос риторический конечно же и для меня это как Windows --
с этим я просто не связываюсь.

    [оставить комментарий]
    комментарий 0:
    From: kmeaw
    Date: 2020-09-11 21:13:28Z
    
    Сталкивался с той же проблемой несколько лет назад, когда писал
    cgroup manager, и хотел уметь запускать его из sysvinit, runit и
    upstart.
    
    Концептуально проблема с том, что сервис может или сам заниматься
    демонизацией, или отдать эту задачу супервизору.
    
    В первом случае это усложняет работу супервизора:
    
    У Upstart есть хак, который подключается через ptrace к процессу и ждёт,
    пока он сделает fork или double fork. В systemd есть Type=forking, но
    деталей реализации я не знаю.
    
    У runit вообще нет никакого адекватного способа супервайзить такие
    процессы. Приходится писать бесконечный цикл, который раз в секунду
    проверяет, запущен ли процесс, и при этом не забывать о возможных гонках
    за pid.
    
    Также программа самостоятельно решает, куда она запишет pid-файл.
    
    Первый случай хорош только для init-скриптов. В этом случае не надо
    использовать start-stop-daemon --background --make-pidfile, а достаточно
    просто запустить программу, а дальше она всё сделает сама.
    
    Ещё далеко не все разработчики умеют правильно демонизировать процессы.
    Кто-то забудет файловые дескрипторы позакрывать или ещё что-нибудь.
    Кажется в nginx была проблема, которая проявлялась, если после
    демонизации поменять размер окна эмулятора терминала - рабочему процессу
    nginx прилетал SIGWINCH, и он завершался.
    
    Во втором случае это усложняет процедуру оповещения пользователя об
    успешном старте:
    
    Пользователь ожидает, что запуск service XXX start выдаст ошибку, если
    что-то где-то неправильно сконфигурировано, и нормальная работа сервиса
    невозможна.
    
    Для этого процесс сначала готовит себе окружение (читает/парсит конфиг,
    загружает секреты и биндит сокеты, требующие root/capabilities, готовит
    сетевые интерфейсы и так далее). В тот момент, когда сервис (почти)
    готов работать, он сообщает супервизору о готовности и переходит в
    рабочий режим.
    
    Upstart предполагает, что процесс пошлёт себе SIGSTOP. Мне это решение
    очень нравится — его легко использовать даже в ограниченных окружениях
    (chroot, namespaces), и процессы обычно так не делают в любых других
    ситуациях.
    
    В systemd есть шина, на которую сервис должен послать сообщение о своей
    готовности. Для этого нужна библиотека, которая умеет к ней подключаться
    и доступ к сокету шины. Я хотел затащить вариант Upstart в systemd, но
    нашёл тикет, в котором кто-то уже предлагал такую возможность, и Леннарт
    Поттеринг закрыл его с WONTFIX с мотивировкой "не вижу, чем вариант с
    raise(SIGSTOP); проще варианта с sd_notify("READY=1");".
    
    Кажется ещё в systemd и upstart есть возможность подождать указанное в
    конфиге время. Если сервис упадёт раньше, чем это время пройдёт, значит
    он сломался.
    
    В случае с init-скриптами обычно вставляют цикл, который проверяет
    живость процесса и готовность сервиса работать — либо по маркерному
    файлу, либо попыткой послать в него ping-запрос.
    
    С runit я нормального решения не придумал. В итоге пошёл на
    TOCTOU-компромисс, и просто вставил вызов configtest перед запуском.
    Правда это никак не решает проблему с тем, что пользователь делает up на
    сервис. Приходится делать обёртку, которая позволяет донести до
    пользователя ошибку.
    
    Наконец, есть альтернативный подход - переложить всю подготовительную
    работу на супервизор. Пусть он кладёт секреты в environment, открывает
    сокеты и запускает configtest. Увы, он не обладает должной
    универсальностью — нельзя предсказать все хотелки сервиса при разработке
    супервизора. Хотя runit/daemontools и systemd пытаются, предоставляя
    tcpsvd (socket activation), envdir (EnvironmentFile=), chpst (куча
    всего в sytsemd.exec) и прочее.
    
    Из всего этого можно сделать вывод, что проблема вполне реальна. И
    приходится либо решать её каждый раз (или в коде демонизации сервиса,
    или в init-скрипте, проверяющим успешность старта), что часто приводит к
    ошибкам, либо положиться на умный супервизор. Ни sysvinit, ни runit не
    предоставляют удобных механизмов для уведомления пользователя. А Upstart
    и systemd требуют кооперации со стороны сервиса, что не всегда приемлимо
    для разработчика, особенно в случае необходимости работать на широком
    спектре систем.
    
    комментарий 1:
    From: Sergey Matveev
    Date: 2020-09-13 13:13:26Z
    
    *** kmeaw [2020-09-12 00:10]:
    >Концептуально проблема с том, что сервис может или сам заниматься
    >демонизацией, или отдать эту задачу супервизору.
    
    Лично я с ходу даже не помню как корректно нужно демонизироваться. Почту
    что закрывать за собой дескрипторы, как минимум. Есть ещё double fork.
    Когда-то знал для чего он нужен, но, так как на таком уровне не работал
    толком, то и забывается оно. В Go вроде как проблематично сделать double
    fork/whatever подходы для демонизации (чтобы легко, без хаков) и я был
    немного разочарован. А с daemontools наоборот выясняется что лучше чтобы
    демонизацией службы самостоятельно не занимались :-)
    
    Насколько понимаю, грубо говоря, в идеале софт или не должен
    демонизироваться сам, либо он должен давать отдельную опцию для этого.
    Но многие так и делают. Одно удобнее для init-скриптов, другой для
    супервизоров уже. Хотя далеко не каждый демон то состоит из одного
    процесса.
    
    >Upstart предполагает, что процесс пошлёт себе SIGSTOP.
    >[...]
    >В systemd есть шина, на которую сервис должен послать сообщение о своей
    >готовности.
    
    Ух, жесть. systemd не перестаёт меня удивлять :-). Но мне просто нужно
    перестать воспринимать systemd-based системы как что-то UNIX-way-ное :-)
    
    >С runit я нормального решения не придумал. В итоге пошёл на
    >TOCTOU-компромисс, и просто вставил вызов configtest перед запуском.
    
    Я вот недавно перевёл nsd и unbound под daemontools и у меня всё очень
    тупо: вызывается отдельно проверка, дальше exec на демона:
    
        # cat /var/service/nsd/run
        #!/bin/sh
        cfg=/usr/local/etc/nsd/nsd.conf
        /usr/local/sbin/nsd-checkconf $cfg
        exec /usr/local/sbin/nsd -d -c $cfg
    
        # cat /var/service/unbound/run
        #!/bin/sh
        cfg=/usr/local/etc/unbound/unbound.conf
        /usr/local/sbin/unbound-checkconf $cfg
        exec /usr/local/sbin/unbound -c $cfg
    
    >Наконец, есть альтернативный подход - переложить всю подготовительную
    >работу на супервизор.[...]
    
    Ага, в daemontools и особенно в runit заметил кучу подобных утилит.
    Безусловно, как вы и пишете, предугадать все хотелки невозможно. Но я за
    то чтобы была маленькая небольшая компактная система, удовлетворяющая
    99% потребностей (определённого класса пользователей, конечно же). А там
    где что-то не укладывается... ну или писать обёртки или вспомогательные
    программы. Если кто-то насильно демонизируется и это мешает, то сделать
    патч для программы, попробовать уговорить её разраба сделать возможно
    оставаться в foreground.
    
    >Из всего этого можно сделать вывод, что проблема вполне реальна. И
    >приходится либо решать её каждый раз (или в коде демонизации сервиса,
    >или в init-скрипте, проверяющим успешность старта), что часто приводит к
    >ошибкам, либо положиться на умный супервизор. Ни sysvinit, ни runit не
    >предоставляют удобных механизмов для уведомления пользователя. А Upstart
    >и systemd требуют кооперации со стороны сервиса, что не всегда приемлимо
    >для разработчика, особенно в случае необходимости работать на широком
    >спектре систем.
    
    Всё так, всё верно! Но... для меня факт остаётся фактом: одного из самых
    популярных демонов я не смог запустить под systemd :-), с родным пакетом
    из CentOS. Ну точнее он падал с тем что не может сделать bind и выходил
    с ошибкой, а я не получал даже плохой return code. Просто уже сколько
    лет CentOS/systemd существуют, но до сих пор так и не нашлись люди
    которые бы "подружили" nsd с systemd в этом дистрибутиве.
    
    Спасибо за массу интересной информации!