長らくブランクが空きましたが当エントリはcfn-initはUbuntuのどの起動システムにも対応しているのか?(其の一) の続きです。今回はservicesキーの調査であることがより明確であるエントリ名に変更いたしました。

前回のおさらいと今回の目的

CloudFormationには便利なツールcfn-initが用意されています。これを利用することでservicesキーに指定したサービスの停止などの制御を行うことができます。ドキュメント によるとこれは起動システムsysvinitを前提としたものとのこと。しかし、Ubuntu14でもUbuntu16でもcronデーモンを停止することができました。起動システムとして、Ubuntu14はUpstart、Ubuntu16はsystemdが採用されているにも関わらず、です。前回はその仕組みについては棚上げにしていましたので今回はそこを調査したいと思います。

調査方法

今回は Ubuntu14.04 および Ubuntu16.04 の各々において以下の流れで調査を行いたいと思います。

  1. EC2インスタンスを起動
  2. 自作のデーモンを作成&設定
  3. このインスタンスのAMIを作成
  4. 3.で作成したAMIを指定してCloudFormationでインスタンス作成。この際にサービスの停止を定義する。
  5. 検証

Ubuntu14.04

1. EC2インスタンスを起動

ami-db7311bd を起動します。

2. 自作のデーモンを作成&設定

起動サービスで実行されるデーモンスクリプトを作成します。

#!/usr/bin/env bash

filename=$1
while (true); do
  sleep 1
  date >> /opt/daemon_test/${filename}.log
done

実行結果は以下となります。

$ sudo ./hello.sh foo
$ cat /opt/daemon_test/foo.log
Tue Jan  9 10:25:59 UTC 2018
Tue Jan  9 10:26:00 UTC 2018
Tue Jan  9 10:26:01 UTC 2018
Tue Jan  9 10:26:02 UTC 2018

Upstart準拠の起動設定を行います。/etc/init/hello-upstart.conf として以下のようなファイルを設置します。

description "hello-upstart"

start on runlevel [2345]
stop on runlevel [016]

chdir /opt/daemon_test
exec ./hello.sh upstart
respawn

次にデーモンの設定を反映します。

$ sudo initctl reload-configuration
$ sudo initctl list | grep hello
hello-upstart stop/waiting

インスタンスを再起動してデーモンが起動していることを確認します。

$ sudo initctl list | grep hello
hello-upstart start/running, process 958

$ tail -f /opt/daemon_test/upstart.log
Wed Jan 10 06:29:12 UTC 2018
Wed Jan 10 06:29:13 UTC 2018
Wed Jan 10 06:29:14 UTC 2018
Wed Jan 10 06:29:15 UTC 2018
Wed Jan 10 06:29:16 UTC 2018

3. このインスタンスを基にAMIを作成

Webコンソールから行いました。

4. 3.で作成したAMIを基にインスタンスをCloudFormationで作成。この際にcronデーモンと自作デーモンを停止するように定義。

実行したところ、cronデーモンは停止しましたが、自作デーモンは停止しませんでした。原因を調査してみます。

5. 検証

/var/log/cfn-init.logを見ると/etc/init.d/hello-upstartが無いという理由で落ちているようです。

 :
2018-01-26 08:30:03,817 [DEBUG] Setting service hello-upstart to disabled
2018-01-26 08:30:03,824 [ERROR] update-rc.d failed with error 1. Output: update-rc.d: /etc/init.d/hello-upstart: file does not exist

2018-01-26 08:30:03,824 [ERROR] Error encountered during build of config: Could not disable service hello-upstart (return code 1)
 :
ToolError: Could not disable service hello-upstart (return code 1)

参考にcronデーモンを見てみます。
/etc/init/cron.confが存在しますし、以下の実行結果からもUpstartで起動管理されているように見えます。

$ initctl list | grep cron
cron start/running, process 1073

しかし、sysvinitスタイルの定義の置き場/etc/init.dにも設定は存在していました。というかシンボリックリンクが設置されているようですね。

$ ls -la /etc/init.d/cron
lrwxrwxrwx 1 root root 21 Feb  9  2013 /etc/init.d/cron -> /lib/init/upstart-job

/lib/init/upstart-jobのコメントにはSymlink target for initscripts that have been converted to Upstart.と記してあります。どうやらこれがキモのようですね。自作デーモンにおいてもこれを真似して設置してみました。

$ cd /etc/init.d
$ sudo ln -s /lib/init/upstart-job hello-upstart
$ ls -la hello-upstart
lrwxrwxrwx 1 root root 21 Jan 28 10:05 hello-upstart -> /lib/init/upstart-job

これを基にAMIを作成しました。
そして、このAMIを基にインスタンスをCloudFormationで作成。この際にcronデーモンと自作デーモンを停止するように定義しました。
結果、cronデーモンも自作デーモンも停止しました。

Ubuntu16.04

1. EC2インスタンスを起動

ami-a07012c6 を起動します。

2. 自作のデーモンを作成&設定

起動サービスで実行されるデーモンスクリプトを作成します。Ubuntu14.04 の場合と同じですね。

#!/usr/bin/env bash

filename=$1
while (true); do
  sleep 1
  date >> /opt/daemon_test/${filename}.log
done

systemd準拠の起動設定を行います。/etc/systemd/system/hello.service として以下のようなファイルを設置します。

[Unit]
Description = hello systemd

[Service]
ExecStart = /opt/daemon_test/hello.sh systemd
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

次にデーモンの設定反映します。

# 状態確認
$ systemctl list-unit-files --type=service | grep hello
hello.service                              disabled

# 有効化
$ sudo systemctl enable hello
Created symlink from /etc/systemd/system/multi-user.target.wants/hello.service to /etc/systemd/system/hello.service.
$ sudo systemctl status hello
● hello.service - hello systemd
   Loaded: loaded (/etc/systemd/system/hello.service; enabled; vendor preset: enabled)
   Active: inactive (dead)
$ systemctl list-unit-files --type=service | grep hello
hello.service                              enabled

# 起動
$ sudo systemctl start hello
$ sudo systemctl status hello
● hello.service - hello systemd
   Loaded: loaded (/etc/systemd/system/hello.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2018-01-16 09:22:38 UTC; 3s ago
 Main PID: 6463 (bash)
    Tasks: 2
   Memory: 348.0K
      CPU: 8ms
   CGroup: /system.slice/hello.service
           ├─6463 bash /opt/daemon_test/hello.sh
           └─6475 sleep 1

Jan 16 09:22:38 ubuntu-xenial systemd[1]: Started hello systemd.

インスタンスを再起動してデーモンが起動していることを確認します。

$ sudo systemctl status hello
● hello.service - hello systemd
   Loaded: loaded (/etc/systemd/system/hello.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2018-01-16 09:22:38 UTC; 3s ago
 Main PID: 6463 (bash)
    Tasks: 2
   Memory: 348.0K
      CPU: 8ms
   CGroup: /system.slice/hello.service
           ├─6463 bash /opt/daemon_test/hello.sh
           └─6475 sleep 1

Jan 16 09:22:38 ubuntu-xenial systemd[1]: Started hello systemd.

$ tail -f /opt/daemon_test/systemd.log
Wed Jan 10 06:31:12 UTC 2018
Wed Jan 10 06:31:13 UTC 2018
Wed Jan 10 06:31:14 UTC 2018
Wed Jan 10 06:31:15 UTC 2018
Wed Jan 10 06:31:16 UTC 2018

3. このインスタンスを基にAMIを作成

Webコンソールから行いました。

4. 3.で作成したAMIを基にインスタンスをCloudFormationで作成。この際にcronデーモンと自作デーモンを停止するように定義。

実行したところ、cronデーモンは停止しましたが、自作デーモンは停止しませんでした。原因を調査してみます。

5. 検証

/var/log/cfn-init.logを見るとcannot find a LSB script for helloという理由で落ちているようです。

2018-01-28 10:41:34,511 [DEBUG] Setting service hello to disabled
2018-01-28 10:41:34,520 [ERROR] update-rc.d failed with error 1. Output: update-rc.d: error: cannot find a LSB script for hello

2018-01-28 10:41:34,520 [ERROR] Error encountered during build of config: Could not disable service hello (return code 1)
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/cfnbootstrap/construction.py", line 542, in run_config
    CloudFormationCarpenter(config, self._auth_config).build(worklog)
  File "/usr/local/lib/python2.7/dist-packages/cfnbootstrap/construction.py", line 270, in build
    CloudFormationCarpenter._serviceTools[manager]().apply(services, changes)
  File "/usr/local/lib/python2.7/dist-packages/cfnbootstrap/service_tools.py", line 155, in apply

参考にcronデーモンを見てみます。
以下の実行結果からもsystemdで起動管理されているように見えます。

$ sudo systemctl status cron
● cron.service - Regular background program processing daemon
   Loaded: loaded (/lib/systemd/system/cron.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
     Docs: man:cron(8)

しかし、sysvinitスタイルの定義の置き場/etc/init.dにも設定は存在していました。ただ、Ubuntu14.04とは違ってシンボリックリンクではなさそうです。

$ ls -la /etc/init.d | grep cron
-rwxr-xr-x  1 root root 3049 Apr  5  2016 cron

内容を見るといわゆる「普通の」sysvinit用の設定ファイルのように見えます。
自作デーモンにおいてもこれを真似して設置してみました。

スケルトンを基にコピーして編集。実行権限を付与します。

$ sudo cp -iv /etc/init.d/skeleton /etc/init.d/hello-sysvinit
'/etc/init.d/skeleton' -> '/etc/init.d/hello-sysvinit'
$ sudo chmod +x hello-sysvinit

スケルトンとの差分はこんな感じ。

### BEGIN INIT INFO
-# Provides:          skeleton
+# Provides:          hello-sysvinit
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5

DESC=“Description of the service”
-DAEMON=/usr/sbin/daemonexecutablename
+NAME=hello-sysvinit
+DAEMON=/opt/daemon_test/hello.sh
+DAEMON_ARGS=“sysvinit”

sysvinitの起動設定と同様の作業をすることで

$ sudo update-rc.d hello-sysvinit defaults

以下のようにsystemdの起動設定が作られました。

$ ls -la /run/systemd/generator.late/hello-sysvinit.service
-rw-r--r-- 1 root root 592 Jan 29 15:45 /run/systemd/generator.late/hello-sysvinit.service

これを基にAMIを作成しました。
そして、このAMIを基にインスタンスをCloudFormationで作成。この際にcronデーモンと自作デーモンを停止するように定義しました。
結果、cronデーモンは無事に停止しましたが自作デーモンは停止しませんでした。状態は以下の通り。Activefailedとなっています。

$ systemctl status hello-sysvinit
● hello-sysvinit.service - LSB: Example initscript
  Loaded: loaded (/etc/init.d/hello-sysvinit; bad; vendor preset: enabled)
  Active: failed (Result: timeout) since Tue 2018-01-30 08:45:22 UTC; 4min 6s ago
    Docs: man:systemd-sysv-generator(8)
  CGroup: /system.slice/hello-sysvinit.service
          ├─ 1266 bash /opt/daemon_test/hello.sh sysvinit
          └─11182 sleep 1

Jan 30 08:40:22 ip-172-31-20-228 systemd[1]: Starting LSB: Example initscript...
Jan 30 08:45:22 ip-172-31-20-228 systemd[1]: hello-sysvinit.service: Start operation timed out. Terminating.
Jan 30 08:45:22 ip-172-31-20-228 systemd[1]: Failed to start LSB: Example initscript.
Jan 30 08:45:22 ip-172-31-20-228 systemd[1]: hello-sysvinit.service: Unit entered failed state.
Jan 30 08:45:22 ip-172-31-20-228 systemd[1]: hello-sysvinit.service: Failed with result ‘timeout’.

/var/log/cfn-init.logを見ると当該サービスを停止する必要がないとみなされたようです。

2018-01-30 08:45:54,198 [DEBUG] Setting service hello-sysvinit to disabled
2018-01-30 08:45:54,295 [INFO] disabled service hello-sysvinit
2018-01-30 08:45:54,311 [DEBUG] No need to modify running state of service hello-sysvinit

これはデーモン稼働時のステータスがactivating (start)になっていることに起因しているのではないかと考えられます。

$ systemctl status hello-sysvinit
● hello-sysvinit.service - LSB: Example initscript
   Loaded: loaded (/etc/init.d/hello-sysvinit; bad; vendor preset: enabled)
   Active: activating (start) since Tue 2018-01-30 05:09:55 UTC; 1min 6s ago
     Docs: man:systemd-sysv-generator(8)
  Control: 1210 (hello-sysvinit)
    Tasks: 3
   Memory: 560.0K
      CPU: 118ms
   CGroup: /system.slice/hello-sysvinit.service
           ├─1210 /bin/sh /etc/init.d/hello-sysvinit start
           ├─1252 bash /opt/daemon_test/hello.sh sysvinit
           └─1762 sleep 1

Jan 30 05:09:55 ip-172-31-29-245 systemd[1]: Starting LSB: Example initscript...

ちなみにcronデーモンだと以下のように active (running) になっています。/etc/init.d/hello-sysvinitの書き方に一工夫必要なように感じられます。

$ systemctl status cron
● cron.service - Regular background program processing daemon
   Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2018-01-30 05:09:55 UTC; 57s ago
     Docs: man:cron(8)
 Main PID: 1099 (cron)
    Tasks: 1
   Memory: 356.0K
      CPU: 1ms
   CGroup: /system.slice/cron.service
           └─1099 /usr/sbin/cron -f

Jan 30 05:09:55 ip-172-31-29-245 cron[1099]: (CRON) INFO (pidfile fd = 3)
Jan 30 05:09:55 ip-172-31-29-245 cron[1099]: (CRON) INFO (Running @reboot jobs)
Jan 30 05:09:55 ip-172-31-29-245 systemd[1]: Started Regular background program processing daemon.

systemdと正しくコンバーチブルなsysvinit設定ができていなかったようですね…
今回はこれ以上は深追いしないでおきます。

まとめ

今回の結果から確実に言えるのは、プレーンなUpstartおよびsystemdの設定のみから起動されるデーモンはcfn-initservicesキーでは制御できないということです。cronデーモンはsysvinitとのコンバーチブルな設定が配置されていたためcfn-initservicesキーで制御できていたのですね。もし制御したいサービスがsysvinitコンバーチブルな設定が配置されていない場合は自前で準備してやる必要がありそうです。少々面倒ですね。

くわえて、今回の実験においてcfn-initのインストールに非常に時間がかかるという事実がありました。インストール完了までは各種デーモンが稼働してしまっているのが確認できました。リソースのスペックによるとは思うのですが、この点は要注意です。

これらを総合して考えると、cfn-initservicesキーを使ってのサービスの停止が有用な場面は限られてくると思われます。「sysvinit設定が配置されていて、稼働時間が完全にゼロにならなくても実害がないサービス」のみで使えそうですね。cfn-initの使用を前提とした場合は、OSの起動システムでの起動設定は行わずcfn-initにて所望のデーモンを起動するのが良さそうです。 何ごとも適材適所ですね。