既に多くの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