インスタンス作成時にUser Dataで指定した設定が遅れる理由

# 1. はじめに
クラウドでComputeインスタンス作成時にUserd Data（cloud-init）を指定すると、OS設定をカスタマイズできる。ただし設定が終わるまでには、タイムラグがあることを理解する必要がある。今回はその理由を探ってみる。

なおcloud-initの内部動作の詳細は次のエントリで説明している。

* [cloud-initをデバッグする Part 1](https://qiita.com/yamada-hakase/items/b42aed68e5623ed74024)

### 1-1. 検証環境

今回はOracle Cloud InfrastructureでOracle Linux 7を使用しているが、Linuxであれば他のクラウドでもほとんど同じだ。

* Oracle Cloud Infrastructure Compute
* Oracle Linux 7.6
* cloud-init-18.4-1.el7.x86_64

# 2. User Dataが実行される仕組み
User Dataの内容を実行しているのはcloud-initで、OSのサービスとして実装されている。その内容を調べてみる。

### 2-1. cloud-initを構成するサービス群 
サービスを確認するとcloudで始まる4つのサービスがある。この中でも中心となるのは```cloud-config.service```である。

```
$ systemctl list-unit-files --type service| grep ^cloud
cloud-config.service                          enabled　★これ
cloud-final.service                           enabled
cloud-init-local.service                      enabled
cloud-init.service                            enabled
```

### 2-2. cloud-config.serviceの中身を確認する

サービスの内容を定義したユニットファイルを確認する。```systemctl cat```で表示できるが、実体のファイルは```/usr/lib/systemd/system/```配下に存在する。

```
$ systemctl cat cloud-config.service
```

ここで重要なのは```After```である。```After```は起動順序を定義するディレクティブで、指定したユニットがアクティブになってから起動することを表す。また```network-online.target```は特別なターゲットで、ネットワークサービスがオンラインになったことを表す。

* [RHEL7マニュアル「systemd のユニットファイルの作成および変更」](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-managing_services_with_systemd-unit_files)

```shell:/usr/lib/systemd/system/cloud-config.service
# /usr/lib/systemd/system/cloud-config.service

[Unit]
Description=Apply the settings specified in cloud-config
After=network-online.target cloud-config.target　★
Wants=network-online.target cloud-config.target
ConditionPathExists=!/etc/cloud/cloud-init.disabled
ConditionKernelCommandLine=!cloud-init=disabled

[Service]
Type=oneshot
ExecStart=/usr/bin/cloud-init modules --mode=config
RemainAfterExit=yes
TimeoutSec=0

# Output needs to appear in instance console output
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target
```

つまり以下のように定義した場合、ネットワークが完全にオンラインになったあとに```cloud-config.service```を実行することを表す。

```
After=network-online.target cloud-config.target
```

### 2-3. sshd.serviceの中身を確認する

さらに```sshd.service```の中身を確認する。すると```After```ディレクティブには```network.target sshd-keygen.service```が設定されている。

```
$ systemctl cat sshd
```

```shell:/usr/lib/systemd/system/sshd.service
# /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service　★
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target
```

### 2-4. cloud-initとsshdではどちらが早い

両方の```After```ディレクティブの内容は似ているが、わずかに違いがある。詳細は[wiki/Software/systemd/NetworkTarget](https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/)を参照のこと。

* **network.target**：ネットワークスタックが起動したこと（sshd.serviceで指定）
* **network.online**：ネットワークスタックが起動し、IPアドレスが割り当てられたこと（cloud-config.serviceで指定）

つまり起動順序としては、sshdのほうがわずかに早いか、ほぼ同時に起動されることになる。

# 3. User Dataの実行が遅れる理由

ここまで来て、理由がわかったと思う。User Dataの内容はcloud-initのサービス群で設定される。ネットワークが起動したあとに実行されるので、

**「パッケージのアップデート」のような時間のかかる処理を含んでいると、sshログインしてもUser Dataの内容が反映されていない**

ということが起こりえる。しかし実行中なので、いずれ設定される。

またわずかにsshdのほうが起動順序は早いが、これは誤差の範囲だろう。User Dataに重い処理が含まれているかどうかがポイントになる。

### 3-1. Kickstartとの違いを理解する
今回の件はKickstartに慣れていると戸惑う人も多いだろう。Linuxインストール時に設定をカスタマイズする手段としては[Kickstart](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/installation_guide/chap-kickstart-installations)が標準的だ。あらかじめ作成した設定したファイル（Kickstartファイル）をOSのインストーラーに食わせることで、自動的に設定を済ませる機能である。サイレントインストールの仕組みと言い換えてもいいだろう。

つまりKickstartはインストールプロセスで使用するため、OSが起動した段階で、設定したすべての内容が有効になっている。それに対してcloud-initは、ネットワークが起動したあとに実行されるので、sshログインできても、まだ設定中の可能性がある。

なおKickstartファイルは、RHEL系のディストリビューションであれば、```/root/anakonda-ks.cfg```にある。ただしクラウドベンダ提供のイメージでは削除されていることもある。

### 3-2. cloud-initが終わったことを知る方法
cloud-initは```/var/log/cloud-init.log```にログを出力する。大量に出力されるので、内容を理解するのは困難だが、次のように``` finish: modules-final```の行が表示されれば終了している。

```
# tail -f /var/log/cloud-init.log
★ここから下は画面の出力★
2019-05-16 13:04:25,343 - util.py[DEBUG]: Read 14 bytes from /proc/uptime
2019-05-16 13:04:25,343 - util.py[DEBUG]: cloud-init mode 'modules' took 0.208 seconds (0.21)
2019-05-16 13:04:25,344 - handlers.py[DEBUG]: finish: modules-final: SUCCESS: running modules for final
```

# 4. まとめ

* クラウドのCompute（IaaS）でUser Dataを指定したとき、起動直後には設定内容がすぐに反映されないことがある
* 遅れる原因は、User Dataの基盤となるcloud-initはLinuxのサービスとして実装されているので、ネットワークが起動したあとに設定が実行されるから
* User Dataで時間のかかる処理を記述したときは、ある程度待ってから設定を確認する
* Kickstartとは、まったく違う仕組みであることを理解する