systemdとinitスクリプト

systemdが採用されたことで、サービスの起動プロセスが変わった。systemdに対応した起動プロセスを用意しているサービスは、その挙動がsystemdによってコントロールされる。では、起動プロセスがsystemdに対応していないサービスはどうか? というのを調べてみた。

Debianの場合

systemdに対応していないサービスというだけでは漠然としてしまうので、td-agentの0.12で挙動を確認することにする。

ベースとなる環境はDebian/stretch(9.2)で、systemdのバージョンは232-25+deb9u1。td-agentのインストールはtd-agentのドキュメントに従う。

curl -L https://toolbelt.treasuredata.com/sh/install-debian-jessie-td-agent2.sh | sh

これでtd-agentが起動される。

現状のtd-agent.deb(2.6.0-0)はsystemdに対応していない。が、systemctlの状態を得ることができる。つまりsystemdのコントロール下にある。

vagrant@stretch:~$ systemctl status td-agent
● td-agent.service - LSB: data collector for Treasure Data
   Loaded: loaded (/etc/init.d/td-agent; generated; vendor preset: enabled)
   Active: active (running) since Wed 2017-10-18 06:34:48 GMT; 48s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 9349 ExecStart=/etc/init.d/td-agent start (code=exited, status=0/SUCCESS)
 Main PID: 9371 (ruby)
    Tasks: 9 (limit: 4915)
   CGroup: /system.slice/td-agent.service
           ├─9371 /opt/td-agent/embedded/bin/ruby /usr/sbin/td-agent --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid
           └─9374 /opt/td-agent/embedded/bin/ruby /usr/sbin/td-agent --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

これを見るとExecStartの値/etc/init.d/td-agent startによりサービスが起動されたことがわかる。systemctl showを使えば他の設定値も確認できる。

vagrant@stretch:~$ systemctl show --no-pager td-agent
Type=forking
Restart=no
PIDFile=/var/run/td-agent/td-agent.pid
NotifyAccess=none
RestartUSec=100ms
...

では誰がそれをしてくれたのか、というところでDocsに目を向けるとsystemd-sysv-generator(8)とある。systemd-sysv-generatorはSysV initスクリプトをもとに、systemdのユニットを作成するツールである。

同様のツールは他にもあってたとえばsystemd-fstab-generatorは/etc/fstabからユニットを作成する。これらのsystemd-*-generatorはsystemdの起動時やリロード時に実行されるらしい。(systemd.generetor(7))

td-agent.debでは、パッケージインストール後に実行されるスクリプト(postinst)の中でupdate-rc.dを実行している。update-rc.dは/etc/rcX.d以下に/etc/init.d/td-agentへのシンボリックリンクをインストールした上で、systemd daemon-reloadを実行する。このときにsystemd-sysv-generatorが実行されてユニットが作成されるのだろう。

サービスの起動はというと、同じくpostinstの中でinvoke-rc.dを実行していて、その内部でsystemctl start td-agent.serviceが実行されることによる。もちろん端末からinvoke-rc.d td-agent startとしても同様。

では、/etc/init.d/td-agentを直接実行するとどうなるのか。

同ファイルをながめつつsh -x /etc/init.d/td-agent startなどしてみると、/lib/lsb/init-functionsが読み込まれ、さらに/lib/lsb/init-functions.d/40-systemdが読み込まれることが分かる。これらのファイルにはDebianパッケージのinitスクリプトで共通して使われる関数などが書かれている。

40-systemdでは、initスクリプト実行時に与えられた引数(td-agentとstart)をもとにsystemctl start td-agent.serviceのようなコマンドラインを形式して、これを実行する。するとsystemdが上述したinitスクリプトに合わせて作成されたユニットに従って、再びinitスクリプトを実行する。もちろんこの時にはsystemctlに持っていかれないようにしている。(このあたりの動作はinitスクリプトや40-systemdにloggerコマンドをしこんでやるとjournalctlで確認することができる。)

最終的には/etc/init.d/td-agentに書かれている通り、start-stop-daemonを使ってtd-agentプロセスが起動されることになる。

ちなみに/etc/init.d/td-agentは内部でulimit -n 65536を実行しているが、この値を変更するにはこの部分を書き換えるしかない。他の値についてはsystemctl edit td-agent.serviceにより設定を追加することができる。

たとえば以下の内容にすると最大プロセス数が3,000になる。

[Service]
LimitNPROC=3000

なお、systemctl editで書いた内容を反映させるには、エディタを終了してからsystemctl restartしなくてはならないことに注意。Ctrl-zではダメなのです。(実際に何度かやってしまって、そのたびに軽く混乱した。)

CentOSの場合

CentOS 7.2のsystemdは219-42.el7_4.1というバージョンで、Debianとは少し異なるが、動作はおおむね同じようだった。update-rc.dをchkconfigに、invoke-rc.dをserviceに、それぞれ読み替えればよい。

ただし、initスクリプトの中のプロセス起動のやり方に少し違いがある。

CentOS向けのinitスクリプトではstart-stop-daemonに相当するものとしてdaemonが使われている。

  ulimit -n 65536 1>/dev/null 2>&1 || true
  local RETVAL=0
  daemon --pidfile="${TD_AGENT_PID_FILE}" ${START_STOP_DAEMON_ARGS} "${TD_AGENT_RUBY}" ${TD_AGENT_ARGS} || RETVAL="$?"

daemonの実体は関数で、Debianの/lib/lsb/init-functionsと/lib/lsb/init-functions.d/40-systemdを合わせたようなものにあたる/etc/init.d/functionsの中で定義されている。この関数は最終的に以下のようにしてプロセスを起動する。

    if [ -z "$user" ]; then
       $cgroup $nice /bin/bash -c "$corelimit >/dev/null 2>&1 ; $*"
    else
       $cgroup $nice runuser -s /bin/bash $user -c "$corelimit >/dev/null 2>&1 ; $*"
    fi

$userはプロセス実行時のユーザで、initスクリプト内で指定されている。td-agent.rpmの場合はtd-agentユーザが指定されており、runuserを経由してtd-agentプロセスが起動されることになる。

ところで、上に引用した通り、ulimit -n 65536よりも後でrunuserが使用されている。そのためrunuserがPAMを使用していることに注意が必要かもしれない。CentOSの標準的なインストール(と言っていいかどうか分からないが少なくともvagrant init centos/7として環境)では、runuserのPAM設定にはpam_limits.soが含まれているので、サービスの動作に/etc/security/limits.confや/etc/security/limits.d以下のファイルが影響することがある。(各ファイルでの設定内容による)

あとtd-agentパッケージについての注意とか

td-agent.deb(2.3.6-0)ではpostinstスクリプトで以下のように/etc以下のファイルを生成している。

cp -f --remove-destination ${td_agent_dir}/etc/init.d/td-agent /etc/init.d/td-agent

# ...

if [ -d "/etc/logrotate.d/" ]; then
  cp -f ${td_agent_dir}/etc/td-agent/logrotate.d/td-agent.logrotate /etc/logrotate.d/td-agent
fi

同様にtd-agent.rpm(2.3.6-0.el7)ではpostinstallスクリプトで以下のようにしている。

if [ -d "/etc/logrotate.d/" ]; then
  cp -f ${td_agent_dir}/etc/td-agent/logrotate.d/td-agent.logrotate /etc/logrotate.d/td-agent
fi

# ...

cp -f ${td_agent_dir}/etc/init.d/td-agent /etc/init.d/td-agent

これらはいずれもパッケージの管理下にはない。パッケージ更新時や再インストール時に上書きされるので、運用方法によっては注意が必要かもしれない。と思う。