Использование runit для своих сервисов

    Супервизор сервисов runit позиционируется как замена стандартным скриптам инициализации Unix.

    Но на практике оказалось, что runit идеален для управления сервисами безотносительно инициализации и т.п.

    Введение


    Супервизор берёт на себя такой функционал, как:
    • превращение любого процесса в демон;
    • логгирование вывода процесса и ротирование логов;
    • запуск, остановка, рестарт, запрос состояния, управляющие скрипты для init.d;
    • выключение и запуск сервисов автоматически при появлении новых сервисов в списке либо удалении старых из списка;
    • возможность ведения нескольких независимых списков сервисов одновременно (например, для каждого пользователя отдельно и для системы в целом);
    • удобный API для управления сервисами.

    Для большинства операционных систем runit уже входит в репозитории пакетов (apt-get install runit). Кроме того, мы имеем уже готовый набор рецептов для популярных сервисов (nginx, apache etc.).

    Далее рассмотрим стандартную схему runit (которая используется по умолчанию):

    Демонизация


    Каждый сервис описывается отдельным каталогом /etc/sv/<название сервиса>.

    обычно достаточно иметь в этом каталоге исполняемый скрипт run вида
    #!/bin/bash
    exec 2>&1
    
    exec your_running_command


    Важно, чтобы your_running_command не демонизировала себя (не отсоединялась от стандартных потоков stdin, stdout, stderr).

    Переадресация ошибок в стандартный вывод нужна для их логгирования. Выполняется логгирование, если в каталоге /etc/sv/<название сервиса>/log/ разместить файл run вида
    #!/bin/bash
    LOG_FOLDER=/var/log/<название сервиса>
    mkdir -p $LOG_FOLDER
    exec svlogd -tt $LOG_FOLDER


    Сервисы, расположенные в каталоге /etc/sv/, не выполняются, пока ссылки на них не будут размещены в каталоге /etc/service/.

    Как только вы сделаете ln -s /etc/sv/<название сервиса> /etc/service/<название сервиса>, сервис runsvdir увидит новый сервис, и запустит его. Более того, в случае остановки сервиса он будет автоматически перезапущен. Это даёт гораздо более быструю реакцию на остановку сервиса по сравнению с использованием сервисов мониторинга (god или monit).

    Ротирование логов


    При использовании svlogd логи размещаются в папке, которую Вы указали при её запуске. При этом текущий лог находится в файле current, и периодически выполняется ротирование логов в этой папке.

    Управление


    Запускать, останавливать, перезапускать сервисы можно с помощью команды sv (start|stop|restart...) <название сервиса>.

    Кроме того, при запуске сервиса появится каталог /etc/service/<название сервиса>/supervise, в котором будут расположены очень полезные файлы и потоки:
    • pid — идентификатор процесса Unix;
    • stat — человеко-читаемое состояние сервиса
    • status — машинно-читаемое состояние процесса
    • control — поток управления
    • и так далее...

    Можно отметить, что для остановки или запуска процесса достаточно открыть поток control на запись, и отправить туда символ d (от down) или u (от up) соответственно.

    В стиле init.d

    Нет ничего проще, чем поддержать управление в стиле init.d.

    Просто делаем ln -s /usr/bin/sv /etc/init.d/<название сервиса>. sv поймёт, что его вызвали в стиле init.d, и готов будет принимать команды вида /etc/init.d/<название сервиса> start etc. Совсем немного магии, правда?

    Настройка сервисов


    В комплекте с runit поставляется утилита chpst, которая позволяет выполнять сервисы с дополнительной настройкой (ограничивать размер памяти, запускать из под определенного пользователя, с другим уровнем nice и т.д.).

    Резюме


    runit оказался настолько удобен и надёжен для организации своих сервисов, что мы стараемcя переводить все наши демоны под runit, заодно отказываясь от различного геморроя в виде пакетов демонизации а ля daemons gem. Также на части машин мы избавились от Monit (где требовался лишь мониторинг процессов).

    Крайне рекомендую к прочтению комментарий от powerman, а также статью про Web-интерфейс к runit.
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 54
    • 0
      Спасибо! Похоже полезная вещица, нужно попробовать для своих самоделок.
      • +1
        Забыл ещё дописать про управление в стиле init.d. Дописал, очень удобная магия :)
        • 0
          спасибо большое… завтра-же буду экспериментировать на моей железяки…
      • 0
        /о пользе пробелов/

        Прочит название как r-unit, думал: юнит-тестирование добралось до демонов…
        • 0
          Поскольку с runit демонами становятся обычные процессы, то их отладка становится таким же простым делом, как и для остальных недемонических процессов.

          и никто не мешает писать юнит-тесты вне зависимости от природы программного продукта :)
        • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          С upstart мирно соседствует? У нас в перемешку debian и ubuntu server, поэтому сейчас опакечиваю в стандартные init скрипты. А так можно было бы про runit задуматься.
        • 0
          Не ради холиварчика, а ради объективности :) Проверил в репозитариях ContOS, нет runit. Что подразумевается под большинством?
          • 0
            Например, Debian Lenny, MacOS X, FreeBSD, OpenBSD, Fedora.

            CentOS сейчас не использую, так что даже не знал, что его там нет.
            • 0
              Сейчас проверил, в народно любимой ubuntu есть.
          • 0
            очень похоже на daemontools

            cr.yp.to/daemontools.html
          • 0
            Акжан, а как насчет например reload'a типа nginx/apache — сначала тестим конфиг, потом, если всё ок — релоад? можно/нужно отдельные скрипты типа reload/restart/whatever класть?
            • +1
              У nginx есть уникальная фича обновления/рестарта сервиса без обрыва текущих соединений — к сожалению, с runit она не совместима.

              Что касается простых вещей типа проверки синтаксиса конфига перед рестартом сервиса — это должно делаться без проблем т.к. runit позволяет установить юзерские скрипты-хуки на отправку сервису конкретных команд/сигналов. Так что вы можете написать скрипт в пару строк, который перехватит отправку сервису команды t(erm), проверит синтаксис конфига и либо пропустит команду t(erm), либо нет. Впрочем, лично я думаю, что это лишнее: если админ допускает синтаксические ошибки при редактировании конфига веб-сервера, то лучше всего этого админа не допускать до продакшн серверов, чем писать такие хуки.
              • 0
                powerman ответил полноценно.

                runit знает о сигналах hup, usr1 etc. и позволяет управлять с их помощью.

                но, касательно nginx, runit не позволяет работать с помощью сигнала WINCH.
            • 0
              Спасибо, пойду соберу rpm-ку для CentOS :)
              • 0
                надеюсь, попадёт в официальную репу :)
              • +20
                Было бы неплохо упомянуть, что пакет runit это развитие (форк) DJBшного пакета daemontools. Софт DJB отличается надёжностью и простотой, но DJB очень не любит добавлять ненужные лично ему фичи — дабы не усложнять и не плодить баги. Пакет runit разрабатывается с учётом принципов DJB, автор тоже крайне неохотно добавляет новые фичи и стремится сохранить простоту кода, но всё-таки фич там больше и пользоваться runit удобнее, чем daemontools.

                Стабильность runit, в принципе, не хуже, чем у daemontools — фактически я знаю только про один баг, который проявляется буквально у нескольких пользователей (включая меня, к сожалению), и который много лет не могут поймать (автору runit этот баг повторить не удаётся). Впрочем, для этого бага есть workaround, так что в результате всё работает много лет без нареканий.

                Было бы неплохо упомянуть, что runit состоит из нескольких независимых частей: процесс N1 (т.е. замена стандартному /sbin/init), супервизор сервисов (который описан в статей) и утилита для управления логами. И можно пользоваться как всеми тремя составляющими, так и отдельно любой из них.

                Мы много лет используем runit и для загрузки систем (вместо стандартного /sbin/init) и для запуска всех сервисов (вместо скриптов в /etc/init.d/). Для Gentoo пакет с runit, с примерами скриптов для загрузки системы и пакеты со скриптами для типовых сервисов доступны в моём оверлее для portage.

                Так же мы используем супервизор сервисов из runit для запуска и управления всеми сервисами необходимыми для наших веб-проектов, при этом и супервизор и сервисы запускаются под обычным юзерским аккаунтом.

                В статье не упомянуто одно из основных достоинств runit — он не использует .pid-файлы для контроля сервисов, т.к. это не атомарно и не надёжно. Вместо этого каждый сервис контролируется отдельным процессом-родителем, который в случае креша сервиса гарантированно и моментально получит SIGCHLD и перезапустит сервис.

                Что касается каталогов /etc/sv/ и /etc/service/ — насколько я помню официальную позицию автора runit, рекомендуется использовать немного другие каталоги. Но вообще пути к каталогам нигде не прошиты, указываются параметром и можно выбирать их по своему вкусу — например, у меня это /service/ и /var/service/.

                Что касается «демонизирования», то в статье это описано некорректно. Во-первых runit ничего не демонизирует (процесс демонизации несколько шире чем просто запуск процесса в фоне). Во-вторых runit не требует того, чтобы сервисы не демонизировали себя, в частности не отцеплялись от STDIN/OUT/ERR — он требует только того, чтобы сервисы не форкались в фоновый процесс. Логи сервисы не обязаны выводить на STDOUT/ERR, хотя это предпочтительный вариант. Впрочем, даже если сервисы выводят логи в файлы самостоятельно (как apache и mysql, к примеру), то это не мешает на месте файлов access_log, etc. разместить FIFO-шки, из которых будут читать svlogd-сервисы и выводить логи в традиционном для runit формате (у нас всё работает именно так).
                • 0
                  Спасибо за столь хороший комментарий.
                • –1
                  Хорошая статья. Спасибо.
                  • 0
                    Что там с демонизацией? Что то типа tcpserv есть? Если да то как работает — форк на каждый запрос?
                    • 0
                      Демонизации (в чистом виде) нет.

                      Подробнее в комменте от poweman выше.
                      • 0
                        Если я правильно догадался, что такое tcpserv, то такая утилита есть в DJBшном пакете ucspi-tcp, называется tcpserver.
                    • 0
                      runit c вебинтерфесом это supervisord.org/ для серверных ферм гораздо удобнее за счет управления через xml-rpc
                      • 0
                        Никто не мешает использовать REST-сигналы с runit-man.

                        решения наподобие supervisord обладают своими преимуществами и недостатками.

                        очень долго их расписывать. В общем случае нужна как система мониторинга локально на машине (runit), так и удалённая (например, cacti/nagios).
                        • 0
                          где написано что что то мешает?

                          зачем использовать runit + нашлепку сбоку на ruby если можно поставить supervisord который все умеет сам без постороенней помощи. По возможностям они абсолютно идентичны. runit хорошь там где мало ресурсов в embedded или на vps, но он не предназначен для распределенного управления.

                          runit это не мониторинг, это супервайзер для процессов.

                          • 0
                            runit — железобетонный супервайзер, вы правы.

                            для полноты картины, использует ли supervisorctl SIG_CHILD?
                            • 0
                              вы хотите чтобы вам документацию пересказали?
                              • 0
                                Использует.
                        • 0
                          Коллеги!
                          А что я делаю не так:

                          $ cat /etc/sv/spawn-fcgi/run
                          
                          #!/bin/sh
                          exec 2>&1
                          PHP_FCGI_CHILDREN=5 \
                          PHP_FCGI_MAX_REQUESTS=1000 \
                          exec /usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -u www-data -g www-data -n -- /usr/bin/php5-cgi
                          


                          Запускаем:

                          # sv up spawn-fcgi
                          


                          Проверяем:
                          # sv status spawn-fcgi
                          run: spawn-fcgi: (pid 435) 1s
                          # sv status spawn-fcgi
                          run: spawn-fcgi: (pid 446) 1s
                          # sv status spawn-fcgi
                          run: spawn-fcgi: (pid 450) 1s
                          # sv status spawn-fcgi
                          run: spawn-fcgi: (pid 454) 1s
                          


                          Где я ошибся?
                          • 0
                            попробуйте оставновить сервис, перейти в каталог сервиса и выполнить ./run
                            Всё увидите.

                            Навскидку всё в порядке, опция -n прописана.
                            • 0
                              Спасибо.
                              Я сам дурак, забыл chmod +x на run сделать.

                              P.S. спасибо за статью.
                              • 0
                                на досуге попробуйте поставить ruby и rubygems, сделать
                                gem install thin runit-man
                                runit-man -p 14000 -r

                                и потом посмотреть на страницу localhost:14000
                                • 0
                                  Еще вчера.
                                  Спасибо ;)
                          • 0
                            А что делать с сервисами, не умеющими не отвязываться от консоли?
                            zabbix-server например.
                            • 0
                              Вообще-то для запуска сервисов runit отвязка от консоли не нужна и даже вредна.

                              Я наоборот, убираю всякие опции демонизации. Мне удобнее, чтобы ошибки сыпались в stdout/stderr, их оттуда подхватит svlogd.
                              • 0
                                Так о том и речь — он НЕ умеет НЕ отвязываться.
                                Он сразу после запуска форкается и уходит в бэкграунд.
                                Runit его тут же теряет есессна.
                                • 0
                                  подстава :) в таком случае сам процесс в runit не подсунуть, к сожалению.

                                  либо оставить как есть, либо писать оберточный процесс (daemon controller), либо писать feature request авторам zabbix.

                                  всё-таки у них должна быть фича запускать сервер в фореграунде, хотя бы для отладки.
                          • 0
                            orion www # /etc/init.d/unicorn start
                            fail: unicorn: runsv not running

                            Если же перед этим сделать
                            runsv /var/service/unicorn
                            То всё ок, только это же не выход
                            • +1
                              у вас, видимо, не запущен супервизор runsvdir.
                              • 0
                                Возможно я что-то неверно делаю, но запуск runsvdir запускает мне всех моих демонов
                                • +1
                                  так и должно быть.

                                  /etc/sv — все сервисы
                                  /etc/service — симлинки на активные.
                                  • 0
                                    Видимо я что-то не улавливаю.
                                    Ещё раз: что мне надо сделать, чтобы при загрузке демоны не поднимались, но /etc/init.d/демон start работал без проблем?
                                    • 0
                                      Боюсь, это выбивается из обычного сценария.

                                      В вашем случае init.d-скрипт должен активировать сервис в случае его неактивности и т.д… То есть писать руками.
                                      • 0
                                        Печаль, понравилась идея использовать runit, как дополнение к гентушному openrc, но видимо не судьба(
                                  • +1
                                    Всегда можно для активного сервиса сделать
                                    sv stop service-name.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.