Создание демонов для Mac OS X средствами launchd

January 20, 2012 launchd nginx OS X Upstart демоны

Демон – приложение, которое запускается самостоятельно и работает в фоне, а также автоматически перезапускается после падения и обладает другими приятными плюшками.

Зачем тебе, простому девелоперу, понадобится писать демона? Ну, например, чтобы не запускать-перезапускать сервера приложений, чтоб они не висели в терминале, и т.п.

Ну, например, у меня установлен nginx, и я хочу включить в него приложение, написанное на JRuby. Самый простой способ запустить приложение на JRuby – это Rackup, ну или вообще WEBrick какой-нибудь. Подключить его к nginx несложно. Разберемся, как сделать так, чтоб этот сервер не приходилось запускать вручную.

В OS X демоны управляются утилитой launchd. После убунтовского Upstart она меня очень порадовала, поэтому, собственно, и пишу эту статью.

Launchd различает системные и пользовательские демоны. У них разный набор привилегий, например, пользовательские демоны не могут открывать сокеты на “системный” порт, например, на 80-й. Зато пользовательские демоны запросто устанавливаются и работают без sudo, что, впрочем, на твоей личной машине почти ничего не значит. Поэтому я буду рассматривать пользовательские демоны.

Для управления launchd есть утилита launchctl. Она знакома пользователям Homebrew, потому что он просит вручную регистрировать все устанавливаемые сервисы, например, СУБД. launchctl забирает информацию о сервисах из конфигов, их мы и будем писать.

Конфигурационные файлы launchctl

  1. Придумываем имя для сервиса. Традиционно они называются в обратно-доменно-именном-порядке, то есть я бы мог назвать свой me.shevtsov.leonid.jrubyapp.

  2. Создаем файл ~/Library/LaunchAgents/me.shevtsov.leonid.jrubyapp.plist со следующим содержимым:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
      <!-- Название - должно совпадать с названием файла -->
      <key>Label</key>
      <string>me.shevtsov.leonid.jrubyapp</string>
      <!-- Командная строка -->
      <key>ProgramArguments</key>
      <string>zsh -c "rackup"</string>
      <!-- Рабочий каталог сервиса -->
      <key>WorkingDirectory</key>
      <string>/Users/leonid/projects/jrubyapp</string>
      <!-- Пути к логам -->
      <key>StandardOutPath</key>
      <string>/Users/leonid/projects/jrubyapp/log/rackup.log</string>
      <key>StandardErrorPath</key>
      <string>/Users/leonid/projects/jrubyapp/log/rackup.error.log</string>
      <!-- Перезапускать сервис при ошибке -->
      <key>KeepAlive</key>
      <true/>
      <!-- Запускать сервис при старте системы -->
      <key>RunAtLoad</key>
      <true/>
    </dict>
    </plist>
  3. Вгружаем сервис в систему

    launchctl load ~/Library/LaunchAgents/me.shevtsov.leonid.jrubyapp.plist

    Проверяем, что сервис запущен:

    launchctl list | grep jrubyapp
  4. Готово!

Что можно делать с демоном

Что мне понравилось в launchd – помимо запуска демона при старте системы, можно повесить его на ряд других событий. Например, на изменение каталога (WatchPaths), или на монтирование тома (StartOnMount), по кроноподобному расписанию (StartCalendarInterval). Причем, заметь, сервис сам знает, по какому расписанию себя вызывать – ему не приходится записывать эту конфигурацию во внешний файл (то есть, кронтаб).

Мне показалось, что launchd гораздо удобнее и гибче чем Upstart.

Формат конфигурационных файлов описан в мане по launchctl.plist.

Перезапуск демона

launchctl stop com.mysite.daemon

По документации, после вызова (нерекомендуемой) команды stop любой сервис, кроме запускаемых по требованию, будет перезапущен.

Buy Me a Coffee at ko-fi.com