【Ubuntu / CentOS】 SysV init と systemd で自作コマンドのサービス化

既に多くのLinuxディストリビューションで initシステム は SysV init や Upstart から systemd に移行が済んでおり, Ubuntu 15.04, CentOS 7, Debian jessie から default となっています。 もはや systemd を積極的に使わない理由はないのかもしれないですが, 最近いくつかの脆弱性 (CVE-2017-1000082, CVE-2017-9445 etc) の発見もあり SysV init のサービス起動スクリプトも中々捨てがたい気もします。現に shell script という気軽さからなのか /etc/init.d は未だ残っています。

今回は SysV init サービス起動スクリプトと systemd のサービスユニットファイルの最小限的な書き方を残しておきます。

環境は Ubuntu 16.04.02 です。例として, 以下のシンプルな HTTP Server (Go 1.8.3) のサービス化を行います。

package main

import (
  "fmt"
  "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello, World")
}

func main() {
  http.HandleFunc("/", handler)
  http.ListenAndServe(":3000", nil)
}

SysV init

SysV init は最初のプロセス /sbin/init により初期化を行い, シーケンシャルにランレベルに応じたサービス /etc/init.d/${name} を起動する。
サービス起動スクリプトには最低限 start, stop, status の3つの処理を書いておく。 (`$ sudo vim /etc/init.d/go-simple-server`)
Debian系ではプロセスのデーモン化に start-stop-daemon(8) が使える。 pidfile も生成してくれるので便利。

#!/bin/sh

EXEC=/usr/local/bin/server
PIDFILE=/var/run/go-simple-server.pid
USER=ubuntu
###############
# SysV Init Information
# chkconfig: - 58 74
# description: go-simple-server is the dsmllib API server daemon.
### BEGIN INIT INFO
# Provides: go-simple-server
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Should-Start: $syslog $named
# Should-Stop: $syslog $named
# Short-Description: start and stop go-simple-server
# Description: dsmllib API server daemon
### END INIT INFO

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
            echo "$PIDFILE exists, process is already running or crashed"
        else
            echo "Starting go-simple-server..."
            start-stop-daemon --start -c ${USER} --quiet --background --exec $EXEC --make-pidfile --pidfile $PIDFILE
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
            echo "$PIDFILE does not exist, process is not running"
        else
            PID=$(cat $PIDFILE)
            echo "Stopping ..."
            start-stop-daemon --stop --quiet --pidfile $PIDFILE
            while [ -x /proc/${PID} ]
            do
                echo "Waiting for go-simple-server to shutdown ..."
                sleep 1
            done
            rm $PIDFILE
            echo "go-simple-server stopped"
        fi
        ;;
    status)
        if [ ! -f $PIDFILE ]
        then
            echo 'go-simple-server is not running'
        else
            PID=$(cat $PIDFILE)
            if [ ! -x /proc/${PID} ]
            then
                echo 'go-simple-server is not running'
            else
                echo "go-simple-server is running ($PID)"
            fi
        fi
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    *)
        echo "Please use start, stop, restart or status as first argument"
        ;;
esac

実行権限を付与する。

$ sudo chmod +x /etc/init.d/go-simple-server

Ubuntu 16.04 には chkconfig がないので同様の機能を提供する sysv-rc-conf をインストールする。

$ sudo apt install sysv-rc-conf

ランレベルの確認。

$ sudo sysv-rc-conf --list go-simple-server
go-simple-se 2:on  3:on 4:on 5:on

start で起動して status を確認。

$ sudo /etc/init.d/go-simple-server start
Starting go-simple-server...

$ cat /var/run/go-simple-server.pid
4526

$ sudo /etc/init.d/go-simple-server status
go-simple-server is running (4526)

stop で停止して status を確認。

$ sudo /etc/init.d/go-simple-server stop
Stopping ...
Waiting for go-simple-server to shutdown ...
go-simple-server stopped

$ sudo /etc/init.d/go-simple-server status
go-simple-server is not running

また, CentOS の例を以下に示す。CentOS では /etc/init.d/functions の daemon(), killproc() を使う。 daemon() は start-stop-daemon(8) のように pidfile の生成はサポートしていないので, 別途 getpid(2) や $$, $! などで生成する必要がある。 一方, killproc() は pidfile を削除してくれる。

#!/bin/sh

EXEC=/usr/local/bin/server
PIDFILE=/var/run/go-simple-server.pid
USER=ubuntu
###############
# SysV Init Information
# chkconfig: - 58 74
# description: go-simple-server is the dsmllib API server daemon.
### BEGIN INIT INFO
# Provides: go-simple-server
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Should-Start: $syslog $named
# Should-Stop: $syslog $named
# Short-Description: start and stop go-simple-server
# Description: dsmllib API server daemon
### END INIT INFO

# Source function library.
. /etc/init.d/functions

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
            echo "$PIDFILE exists, process is already running or crashed"
        else
            echo "Starting go-simple-server..."
            daemon --pidfile=${PIDFILE} --user=${USER} ${EXEC}
            echo $(pgrep server) > $PIDFILE
            echo
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
            echo "$PIDFILE does not exist, process is not running"
        else
            PID=$(cat $PIDFILE)
            echo "Stopping ..."
            killproc -p $PIDFILE
            while [ -x /proc/$PID ]
            do
                echo "Waiting for go-simple-server to shutdown ..."
                sleep 1
            done
            echo "go-simple-server stopped"
        fi
        ;;
    status)
        if [ ! -f $PIDFILE ]
        then
            echo 'go-simple-server is not running'
        else
            PID=$(cat $PIDFILE)
            if [ ! -x /proc/${PID} ]
            then
                echo 'go-simple-server is not running'
            else
                echo "go-simple-server is running ($PID)"
            fi
        fi
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    *)
        echo "Please use start, stop, restart or status as first argument"
        ;;
esac

Code は GitHub Gist にも置いた。

systemd

systemd は多機能でその各機能は Unit という単位で提供される。以下の Unit Type がある。

  • mount: ファイルシステムのマウント/アンマウントに関する設定
  • socket: ソケットの監視設定
  • service: プロセスのデーモン化に関する設定
  • path: パスの監視設定
  • device: デバイス情報の管理
  • target: Unitを集約したUnit

プロセスのデーモン化はサービスユニット (*.service) で行う。 SysV init のサービス起動スクリプトが手続き的なのに対し systemd の記述は宣言的である。
(`$ sudo vim /lib/systemd/system/server.service`)

[Unit]
Description=simple server daemon

[Service]
ExecStart=/usr/local/bin/server
Restart=always
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=simple

[Install]
WantedBy=multi-user.target
Alias=server.service

サービスを有効化。

$ sudo systemctl enable server
Created symlink from /etc/systemd/system/server.service to /lib/systemd/system/server.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/server.service to /lib/systemd/system/server.service.

サービス一覧の表示。

$ sudo systemctl list-unit-files --type=service | grep server
server.service                       enabled

start で起動して status を確認。

$ sudo systemctl start server

$ sudo systemctl status server
● server.service - simple server daemon
   Loaded: loaded (/lib/systemd/system/server.service; enabled; vendor preset: enabled)
   Active: activating (start) since Mon 2017-07-31 22:42:29 JST; 45s ago
 Main PID: 9038 (server)
   CGroup: /system.slice/server.service
           └─9038 /usr/local/bin/server

Jul 31 22:42:29 ubuntu systemd[1]: Starting simple server daemon...

停止と status の確認。

$ sudo systemctl stop server

$ sudo systemctl status server
● server.service - simple server daemon
   Loaded: loaded (/lib/systemd/system/server.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Mon 2017-07-31 22:47:12 JST; 1s ago
  Process: 9126 ExecStart=/usr/local/bin/server & (code=killed, signal=TERM)
 Main PID: 9126 (code=killed, signal=TERM)

Jul 31 22:47:01 ubuntu systemd[1]: Starting simple server daemon...
Jul 31 22:47:12 ubuntu systemd[1]: Stopped simple server daemon.

サービスの無効化。

$ sudo systemctl disable server
Removed symlink /etc/systemd/system/multi-user.target.wants/server.service.
Removed symlink /etc/systemd/system/server.service.

[1] なぜsystemdなのか?
[2] 私がsystemdを嫌う理由
[3] Linux女子部 systemd徹底入門
[4] The init.d Script