cloud-initをデバッグする Part 1

# 1. はじめに

以前書いた記事「[cloud-initを使ったLinux OSの初期設定](https://qiita.com/yamada-hakase/items/40fa2cbb5ed669aaa85b)」が、特定クラウドに依存しないこともあって**「LGTM（いいね）」**が好調だ♪　そこでデバッグ方法も書いてみることにした。

**「cloud-initをデバッグする」**というタイトルを付けたが、cloud-init自身はデバッグの対象ではない。正確には**「cloud-initのUser-Dataに指定したスクリプトのデバッグ方法」**である。

### 1-1. 対象環境

* cloud-initを利用できる環境（cloud-init 18.5で確認）
* 特定のクラウドサービスには依存しない。プライベートクラウドも同様
* Linux OSイメージ


### 1-2. 予定している内容

* User-Dataを書くときのチェック項目
* デバッグのためのcloud-initの理解
* インスタンスを作成しないでcloud-initを実行する方法
* その他高度な分析方法

# 2. cloud-initふりかえり

まずは基本をふりかえろう。以下のことは覚えておきたい。

* cloud-initのUser-Dataには、シェルスクリプトやcloud-configなど複数の書式を指定できる
* cloud-config書式を利用すると、シンプルで汎用性の高い記述ができる
* cloud-configで設定できることは利用可能なモジュールに限定される。そのようなときはruncmdモジュールを使うと、cloud-config内にOSコマンドを記述できる
* cloud-initはroot権限で実行される
* cloud-initは、Kickstartとは異なりインスタンス作成後に実行される。そのため時間のかかる処理を記述すると、すべての設定が反映されるまでタイムラグがある

# 3. デバッグの基本

「デバッグの基本」というタイトルを付けたが、**User-Data**を作成するうえでチェックすべき項目を説明する。

### 3-1. チェック項目

書式がシェルスクリプトなのか、cloud-configなのかでチェック項目は異なる。とくに長いステップものや使い回すものには事前に確認したい。以下の表の中で<font color="blue">青文字</font>の項目を説明する。

| 実施項目 | シェルスクリプト | cloud-config |
|:--|:-:|:-:|:-:|
| 実際の動作確認  | ○ | ○ |
| <font color="blue">sedで文字列置換するときは、対象がシンボリックリンクでないこと  | ○ | ○ |
| syslogやcloud-initのログの確認  | ○ | ○ |
| <font color="blue">シェルスクリプトの文法チェック※  |  ○ | × |
| <font color="blue">YAML形式になっていること  | ×  | ○ |
| 使用しているモジュールが設定ファイルにあること  | ×  | ○ |

※シェルスクリプトは便宜上bashを想定している。`#!`で呼び出せれば何でもよい。bash以外は言語ごとの文法チェッカーを使用すること。

### 3-2. sedの文字列置換に要注意

ファイル内の文字列を書き換えるのに`sed -i`をよく使用する。<font color="red">しかし、シンボリックリンクに対して実行すると、リンクが切れて別ファイルになるので注意が必要だ。</font>次のいずれかの条件に当てはまるときは、必ずチェックしてほしい。

* シェルスクリプトでsedを実行している
* cloud-configのruncmdでsedを実行している


#### 3-2-1. 失敗例：シンボリックリンクに対する実行
たとえばCentOS 7において、SELinuxの設定ファイルは`/etc/selinux/config`だ。ところが`/etc/sysconfig/selinux`にもシンボリックリンクが存在する（システム系の設定ファイルは、互換性維持のためシンボリックリンクを持つものが多い）。

```shell-session
$ ls -l /etc/selinux/config
-rw-r--r--. 1 root root 543 Feb 19  2019 /etc/selinux/config
$ ls -l /etc/sysconfig/selinux
lrwxrwxrwx. 1 root root 17 Feb 19  2019 /etc/sysconfig/selinux -> ../selinux/config
```

<font color="red">次のようにシンボリックリンクに対して実行すると、リンクが切れて別ファイルになり、SELinuxは無効にならない。</font>

```
sed -i -e 's/^\SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
```

#### 3-2-2. シンボリックリンクに対するsed -iの挙動を確かめる 

実際にどのような動作になるのか試してみよう。cloud-initはroot権限で実行されるので、今回は意図的にrootユーザーを使用している。

1. 事前準備として、テスト用のファイル`testfile1`に対してシンボリックリンク`testfile2`を作成する。

```shell-session
# echo "foo" > testfile1
# ln -s testfile1 testfile2
# ls -l
total 72
-rw-rw-r--. 1 opc  opc      5 Dec 13 22:33 testfile1
lrwxrwxrwx. 1 opc  opc      9 Dec 13 22:34 testfile2 -> testfile1
```

2.この段階ではシンボリックリンクなので中身は同じ。

```shell-session
# grep foo testfile*
testfile1:foo
testfile2:foo
```

3.シンボリックリンクに対してsedで置換を実施する。

```shell-session
# sed -i -e 's/^foo/foo2/' testfile2
```

4.シンボリックリンク`testfile2`は通常ファイルになっている。

```shell-session
# ls -l
-rw-rw-r--. 1 opc  opc      5 Dec 13 22:42 testfile1
-rw-rw-r--. 1 opc  opc      6 Dec 13 22:44 testfile2 ★シンボリックリンクではない
```

5.ファイルの中身を確認すると、sedの置換対象の`testfile2`だけが変わっている。

```shell-session
# grep test testfile*
testfile1:foo
testfile2:foo2
```

<font color="blue">**このような特性があるので、sedで置換するときは必ずシンボリックリンクではなく実体を指定する。**</font>

もしくは次のように`--follow-symlinks`オプションを指定してもリンク切れを回避できる。実体に対して、このオプションを指定しても問題は起きない。そのため必ず指定するようにしてもいいかもしれない。

```shell-session
# sed -i --follow-symlinks -e 's/^test/test2/' testfile2

# ls -l
total 72
-rw-rw-r--. 1 opc  opc      5 Dec 13 22:33 testfile1
lrwxrwxrwx. 1 opc  opc      9 Dec 13 22:34 testfile2 -> testfile1
```

それとは別に`-i"サフィックス"`を指定すると、バックアップファイルを自動的に作成できる。次のように指定すると`foo.bak`が作成される。

```shell-session
# sed -i".bak" -e 's/^test/test2/' foo
```

### 3-3. シェルスクリプトの文法チェック

シェルスクリプトはインタプリタ言語なので、そのコードパスを通るまでエラーがあるかわからない。そこで活用したいのが文法チェッカーだ。よく知られているのは`bash -n`と、Webサイトの[ShellCheck](https://www.shellcheck.net/)がある。

以下のサンプルコードで試してみよう。

```shell-session:エラーのあるサンプルコード
#!/bin/bash

VAL=1

if [ $VAL -eq 1 ] ; then
    echo "True"
else
    echo "False"
if　★ここが間違っている
```

**bash -nによる文法チェック**
上記のコードを`bash -n`で実行すると最終行にエラーがあることがわかる。

```shell-session
$ bash -n test.sh
test.sh: 行 13: 構文エラー: 予期しないファイル終了 (EOF) です
```

**ShellCheckによる文法チェック**
`bash -n`よりもインテリジェントなのが**[ShellCheck](https://www.shellcheck.net/)**だ。`bash -n`は抜け漏れもあるのだが、こちらはもっと厳密なチェックや推奨まで表示できる。次は実際の実行例だ。
![shellcheck01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266049/c33832a1-4128-f63b-8e5a-69d9f726d5a4.png)


### 3-4. YAMLの書式チェック

cloud-config形式のときは**「YAML形式になっていること」「先頭が#cloud-configで始まっていること」**を確認する。YAMLは「キー:␣値」という形式で記述するため、コロン（:）+ 空白 が含まれているときはダブルクォーテーションで囲む必要がある。

短いなら目視チェックでもよいが「[YAML Lint](http://www.yamllint.com/)」などのWebサイトを活用しよう。

```yaml:間違っている例
runcmd:
 - echo a : b
```

```yaml:正しい例
runcmd:
 - "echo a : b"
```

# 4. cloud-initの仕組みを理解する：設定ファイル編

cloud-initの仕組みを理解するために、はじめに設定ファイルを説明する。

### 4.1. cloud-initの設定ファイル 

cloud-initの設定ファイルは以下の２カ所にある。

<font color="blue">1. /etc/cloud/cloud.cfg
2. /etc/cloud/cloud.cfg.d/*.cfg</font>


ここで重要なのは、上記の順序で評価し、`/etc/cloud/cloud.cfg.d/`配下のファイルは辞書順（数字、アルファベット）で評価されることだ。

また設定ファイルの数や内容は、使用しているクラウドやオペレーティング・システムによって異なる。AWSとAzure、Oracle Cloud Infrastructureで調べた結果は以下のとおり。

**Amazon EC2**

```shell-session:AmazonLinux2
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/10_aws_yumvars.cfg
```

**Oracle Cloud Infrastructure Compute**

```shell-session:OracleLinux7
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/99_oci_cloud.cfg
```

```shell-session:OracleLinux8
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/99_oci.cfg
```

```shell-session:Ubuntu
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/90_dpkg.cfg
/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
/etc/cloud/cloud.cfg.d/99-oracle-compute-infra-datasource.cfg
/etc/cloud/cloud.cfg.d/99-oracle-compute-user-redirect.cfg
```

**Azure VM**

```shell-session:RHEL7
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
```

```shell-session:Ubuntu
$ find /etc/cloud/ -name "*cfg" | sort
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/10-azure-kvp.cfg
/etc/cloud/cloud.cfg.d/90-azure.cfg
/etc/cloud/cloud.cfg.d/90_dpkg.cfg
```

**おまけ**

これまで説明したとおり、クラウドやオペレーティング・システムによって設定ファイルは異なる。しかし基本はcloud-initパッケージに含まれる２つのファイルである。

```shell-session
$ rpm -ql cloud-init | grep .cfg$
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d/05_logging.cfg
```

上記２つ以外のファイルはどこから来ているのか調べると、クラウドによって異なるようだ。

```shell-session:EC2ではsystem-releaseパッケージ
$ rpm -qf /etc/cloud/cloud.cfg.d/10_aws_yumvars.cfg
system-release-2-11.amzn2.x86_64
```

```shell-session:OCIではバージョンによって異なる
Oracle Linux 7はパッケージに含まれていない
$ rpm -qf /etc/cloud/cloud.cfg.d/99_oci_cloud.cfg
file /etc/cloud/cloud.cfg.d/99_oci_cloud.cfg is not owned by any package

Oracle Linux 8ではパッケージに含まれている
$ rpm -qf /etc/cloud/cloud.cfg.d/99_oci.cfg
oci-linux-config-2.0-1.0.13.el8.noarch
```

### 4.2. cloud-initの設定ファイルを解読する（基礎編）

これまでcloud-initに、どのような設定ファイルがあるか、どのような順序で評価されるかを説明した。次に実際の設定ファイルを見ながら具体的に説明する。

以下の/etc/cloud/cloud.cfgを軽く眺めてもらいたい。

```yaml:/etc/cloud/cloud.cfg
users:
 - default

disable_root: 1
ssh_pwauth:   0

mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
resize_rootfs_tmp: /dev
ssh_deletekeys:   0
ssh_genkeytypes:  ~
syslog_fix_perms: ~

datasource_list: ['OpenStack']
datasource:
  OpenStack:
    metadata_urls: ['http://169.254.169.254']
    timeout: 10
    max_wait: 20

cloud_init_modules:　★ひとかたまりの実行単位で、これが１つのステージ。
                      この下に書かれているのが、このステージで実行されるモジュール群。
 - migrator
 - bootcmd
 - write-files
# The growpart module is disabled by default. To enable automatic boot volume resizing, uncomment
# the below entry for '- growpart' and reboot. All the dependent packages for the growpart
# module to work such as cloud-utils-growpart and gdisk are already included in the image.
#- growpart
 - resizefs
 - rsyslog
 - users-groups
 - ssh

cloud_config_modules:　★cloud_configステージ
 - mounts
 - locale
 - set-passwords
 - yum-add-repo
 - package-update-upgrade-install
 - timezone
 - puppet
 - chef
 - salt-minion
 - mcollective
 - disable-ec2-metadata
 - runcmd

cloud_final_modules:　★cloud_finalステージ
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - ssh-authkey-fingerprints
 - keys-to-console
 - phone-home
 - final-message

system_info:
  default_user:
    name: opc
    lock_passwd: true
    gecos: Oracle Public Cloud User
    groups: [wheel, adm, systemd-journal]
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    shell: /bin/bash
  distro: rhel
  paths:
    cloud_dir: /var/lib/cloud
    templates_dir: /etc/cloud/templates
  ssh_svcname: sshd

# vim:syntax=yaml
```

コメントを書いたので、大まかに理解できたと思うが、設定ファイルからわかることは以下のとおり。

**1点目：**YAML形式で記述されている。

**2点目：**cloud-initの各モジュールは、いくつかの単位（ステージ）にまとめられ、以下の順序で実行される。

1. cloud_init_modules
2. cloud_config_modules
3. cloud_final_modules

**3点目：**ステージごとに記述されているモジュールが実行される。つまり、設定ファイルに記述していないモジュールをUser-Dataに書いても実行されない。

[前回](https://qiita.com/yamada-hakase/items/40fa2cbb5ed669aaa85b)power_stateが動かなかった根本原因はここにある。cloud-initパッケージにpower_stateモジュールは含まれているが、設定ファイルに書かれていないので実行されなかったのだ。

```shell-session
★cloud-initにpower_stateは含まれている
$ rpm -ql cloud-init | grep power_state
/usr/lib/python2.7/site-packages/cloudinit/config/cc_power_state_change.py
/usr/lib/python2.7/site-packages/cloudinit/config/cc_power_state_change.pyc
/usr/lib/python2.7/site-packages/cloudinit/config/cc_power_state_change.pyo
```

**<font color="red">注意：</font>**
<font color="red">cloud-initの設定ファイルやcloud-initのバージョンは、使用しているクラウドやオペレーティングシステムによって異なる。そのためpower_stateが使える場合もあれば、上記以外のモジュールを使えることもある。</font>

### 4.3. cloud-initの設定ファイルを解読する（応用編）

本当に有効な設定を理解するには、これまでの知識に加えて、優先順位のメカニズムを理解する必要がある。設定ファイルは以下の順序で評価され、同じキーがあるときには最後の値が評価される。

1. /etc/cloud/cloud.cfg
2. /etc/cloud/cloud.cfg.d/*.cfg（辞書順）

別の言い方をすると以下のとおり。

* /etc/cloud/cloud.cfgだけでは、本当に有効な設定は判断できない
* 同じキーが複数のファイルにあるときは、最後のファイルの「キー:値」の組み合わせが有効になる


以下の実行例は、Oracle Cloud InfrastructureでOracle Linux 7を使用しているときのものだ。**キー「cloud_config_modules」**は/etc/cloud/cloud.cfg.d/99_oci_cloud.cfgにもあるので、実際に有効になっているのは、こちらのファイルに書かれた値である。

```shell-session
$ find /etc/cloud/ -name "*cfg" | xargs grep ^cloud_config_modules
/etc/cloud/cloud.cfg:cloud_config_modules:
/etc/cloud/cloud.cfg.d/99_oci_cloud.cfg:cloud_config_modules:
```

diffを取ってみると、いくつか書き換えていることがわかる。この例ではcloud.cfgを99_oci_cloud.cfgにコピーしてカスタマイズしているので、cloud.cfgの設定はすべて上書きされている。

<font color="blue">Oracle Cloud Infrastructureでは、/etc/cloud/cloud.cfg.d/99_oci_cloud.cfgが主要な設定ファイルとして使われている。</font>


```shell-session

$ diff -uNr /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.d/99_oci_cloud.cfg
--- /etc/cloud/cloud.cfg        2019-08-07 12:50:39.000000000 +0900
+++ /etc/cloud/cloud.cfg.d/99_oci_cloud.cfg     2019-11-07 12:20:39.942013483 +0900
@@ -1,3 +1,5 @@
+# Cloud.cfg file with merged content from cloud-init 18.5.1.
+# Aspects not used in OCI have been commented out.
 users:
  - default

@@ -9,18 +11,28 @@
 ssh_deletekeys:   0
 ssh_genkeytypes:  ~
 syslog_fix_perms: ~
-disable_vmware_customization: false
+#disable_vmware_customization: false
+
+datasource_list: ['Oracle', 'OpenStack']
+datasource:
+  OpenStack:
+    metadata_urls: ['http://169.254.169.254']
+    timeout: 10
+    max_wait: 20

 cloud_init_modules:
- - disk_setup
+#-disk-setup
  - migrator
  - bootcmd
  - write-files
- - growpart
+# The growpart module is disabled by default. To enable automatic boot volume resizing, uncomment
+# the below entry for '- growpart' and reboot. All the dependent packages for the growpart
+# module to work such as cloud-utils-growpart and gdisk are already included in the image.
+#- growpart
★以下省略
```

**またcloud-initのお作法として「/etc/cloud/cloud.cfgを直接書き換えてはいけない」ことがある。**

Oracle Cloud Infrastructureでは変更点が多いのでファイルを全部コピーしていた。しかし自分でカスタマイズするときは、変更する部分だけを書いて、/etc/cloud/cloud.cfg.d/配下に配置すればよい。

```yaml:/etc/cloud/cloud.cfg.d/xxxxx.cfg（優先順位が高い名前をつける）
cloud_final_modules:
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - ssh-authkey-fingerprints
 - keys-to-console
 - phone-home
 - final-message
 - power-state-change
```

**Amazon EC2の場合（Amazon Linux 2）**

Amazon EC2では、設定ファイル/etc/cloud/cloud.cfg.d/10_aws_yumvars.cfgを追加している。しかし、キー「write_metadata」は/etc/cloud/cloud.cfgに無いので、キーの上書きを考慮する必要は無い。

```shell-session
$ find /etc/cloud/ -name "*cfg" | xargs grep  ^[a-zA-Z]
/etc/cloud/cloud.cfg.d/10_aws_yumvars.cfg:write_metadata:★追加したキー
/etc/cloud/cloud.cfg.d/05_logging.cfg:log_cfgs:
/etc/cloud/cloud.cfg.d/05_logging.cfg:output: {all: '| tee -a /var/log/cloud-init-output.log'}
/etc/cloud/cloud.cfg:users:
/etc/cloud/cloud.cfg:disable_root: true
/etc/cloud/cloud.cfg:ssh_pwauth:   false
/etc/cloud/cloud.cfg:mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
/etc/cloud/cloud.cfg:resize_rootfs: noblock
/etc/cloud/cloud.cfg:resize_rootfs_tmp: /dev
/etc/cloud/cloud.cfg:ssh_deletekeys:   false
/etc/cloud/cloud.cfg:ssh_genkeytypes:  ~
/etc/cloud/cloud.cfg:syslog_fix_perms: ~
/etc/cloud/cloud.cfg:datasource_list: [ Ec2, None ]
/etc/cloud/cloud.cfg:repo_upgrade: security
/etc/cloud/cloud.cfg:repo_upgrade_exclude:
/etc/cloud/cloud.cfg:network:
/etc/cloud/cloud.cfg:cloud_init_modules:
/etc/cloud/cloud.cfg:cloud_config_modules:
/etc/cloud/cloud.cfg:cloud_final_modules:
/etc/cloud/cloud.cfg:system_info:
/etc/cloud/cloud.cfg:mounts:
```

# 5. cloud-initの仕組みを理解する：その他のファイル編

設定ファイル以外の主要なファイルやサービスを説明する。オフィシャルの[cloud-init FAQ](https://cloudinit.readthedocs.io/en/latest/topics/faq.html#faq)には、情報がコンパクトにまとまっている。興味のある方は読んでみてはどうだろう。

* cloud-initのログファイル
* cloud-initに指定したUser-Data
* cloud-initを構成するサービス

### 5-1. cloud-initのログファイル 

cloud-initには以下のログファイルがある。

<dl>
  <dt>/var/log/cloud-init.log</dt>
    <dd>詳細なデバッグ情報を含んだログ。とても長い</dd>
  <dt>/var/log/cloud-init-output.log</dt>
    <dd>cloud-initを実行したときの簡易ログ。環境によって無いこともある</dd>
  <dt>/var/run/cloud-init/配下のファイル</dt>
    <dd>cloud-init自身が実行するかどうかを判断したときの情報を含んだログ</dd>
</dl>

<font color="blue">上記の中でデバッグに使いやすいのはcloud-init-output.logだ。必要な情報だけがまとまっている。ただし使用環境によっては無いこともあるので、そのときはcloud-init.logを参照する。</font>

#### 5-1-1. cloud-init終了の確認方法

cloud-initは、SSHでログインできても実行途中のことがある。ログファイルを`tail -F`することで、終了したことを確認できる。次のように`modules-final`や`finish`と表示されれば終了している。

```shell-session:/var/log/cloud-init.logの終了確認方法
Jan 28 02:20:08 cloud-init[3468]: handlers.py[DEBUG]: finish: modules-final: SUCCESS: running modules for final
```

```shell-session:/var/log/cloud-init-output.logの終了確認方法
Cloud-init v. 18.5-2.amzn2 running 'modules:final' at Tue, 28 Jan 2020 02:20:08 +0000. Up 22.21 seconds.
Cloud-init v. 18.5-2.amzn2 finished at Tue, 28 Jan 2020 02:20:08 +0000. Datasource DataSourceEc2.  Up 22.75 seconds
```

* 参考：[インスタンス作成時にUser Dataで指定した設定が遅れる理由](https://qiita.com/yamada-hakase/items/657fead978491e8db92f)

### 5-2. cloud-initに指定したUser-Data

インスタンス作成時に指定したUser-Dataは以下のファイルに格納されている。

**/var/lib/cloud/instances/*/user-data.txt**

途中にアスタリスクを使っているのは、アスタリスクの部分はインスタンスIDで、インスタンスごとに異なるためだ。以下の例はOCIで実施しているが、AWSでもほとんど同じ結果だ。

1. /var/lib/cloud/instances/を確認するとインスタンスIDがある。

```shell-session
$ ls -l /var/lib/cloud/instances/
total 4
drwxr-xr-x. 5 root root 4096 Jan 28 21:41 ocid1.instance.oc1.ap-tokyo-1.anxhiljripjhrziczokbsclxvsmfff6tviwuwotdjqbky46iXXXXXXXXX
```
2.サブディレクトリには、さまざまなファイルがある。

```shell-session
$ ls -l /var/lib/cloud/instances/*/
total 36
-rw-r--r--. 1 root root   50 Jan 28 21:41 boot-finished
-rw-------. 1 root root  126 Jan 28 21:40 cloud-config.txt
-rw-r--r--. 1 root root   35 Jan 28 21:40 datasource
drwxr-xr-x. 2 root root    6 Jan 28 21:40 handlers
-r--------. 1 root root 8129 Jan 28 21:40 obj.pkl
drwxr-xr-x. 2 root root   20 Jan 28 21:40 scripts
drwxr-xr-x. 2 root root 4096 Jan 28 21:41 sem
-rw-------. 1 root root   92 Jan 28 21:40 user-data.txt
-rw-------. 1 root root  433 Jan 28 21:40 user-data.txt.i
-rw-------. 1 root root    0 Jan 28 21:40 vendor-data.txt
-rw-------. 1 root root  344 Jan 28 21:40 vendor-data.txt.i
```

3.これらのなかでuser-data.txtに、指定した**User-Data**が格納されている。

```shell-session
$ sudo cat /var/lib/cloud/instances/*/user-data.txt
#cloud-config
timezone: Asia/Tokyo
runcmd:
  - systemctl restart rsyslog
```

### 5-3. cloud-initを構成するサービス

cloud-initは複数のサービスで構成されている。それぞれの役割や、実行順序の依存関係は覚えておきたい。

```shell-session
# systemctl list-units --type=service | grep ^cloud-
cloud-config.service               loaded active exited  Apply the settings specified in cloud-config
cloud-final.service                loaded active exited  Execute cloud user/final scripts
cloud-init-local.service           loaded active exited  Initial cloud-init job (pre-networking)
cloud-init.service                 loaded active exited  Initial cloud-init job (metadata service cr
```

`systemd-analyze plot`コマンドを使うと、systemdのブートシーケンスを解析できる。

```shell-session
# systemd-analyze plot >bootup.svg
```

出力したSVG（Scalable Vector Graphics）ファイルをブラウザで表示し、一部を切り取ったものが以下の図だ。拡大しないとわからないが、黄色枠で囲んでいるのがcloud-initを構成するサービスだ。
![cloud-init-service.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266049/a9edd514-181c-abe5-e449-90f8d96a88fc.png)

上記の解析結果をまとめると、以下の順序で実行している。

1. cloud-init-local.service
2. <font color="red">ネットワークを起動</font>
3. cloud-init.service
5. cloud-config.service
6. <font color="red">すべてのサービスが起動</font>
6. cloud-final.service

これまでの設定ファイルの解析と併せると、サービスの役割は以下のとおり。

| サービス | 内容 |
|:--|:--|
| cloud-init-local.service  | ネットワーク起動前のcloud-init初期化  |
| cloud-init.service  | ディスクパーティションの設定やOSユーザーの設定など基礎となる設定 |
| cloud-config.service  | ロケールやタイムゾーン、パッケージのインストール／アップデート、ChefやPuppet、runcmdなどの設定  |
| cloud-final.service  | 再起動やメッセージの表示など、すべての設定が終わったあとに実行する作業 |

# おわりに

cloud-initの仕組みについて、だいぶ理解が深まったのではないだろうか。次回は、インスタンスを作成しないでcloud-initを実行する方法や、高度な解析方法を説明する予定だ（どこまで書けるか微妙だけど）。

つづく