EC2インスタンス(WindowsServer)への起動時処理設定（ユーザーデータ）

# TL;DR

- EC2インスタンスのWindowsServerに、ユーザーデータを設定する話。
- 「ユーザーデータの存在は知ってるけど仕組みは知らないよ」という人向け。
- 本当に、WindowsServerのバージョンで仕組みを変えるのはやめてほしい。

`※　今回はあくまでWindowsServerの話で、Linuxの話は一切出てきません。`

# ユーザーデータとは

ご存じの方も多数おられるかも知れませんが、AWS・EC2のユーザーデータは**サーバの起動時に指定した処理を実行する仕組み**です。その辺りはAWSの公式ドキュメントをご覧いただければお分かりかと。
[Windows インスタンスでの起動時のコマンドの実行](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/WindowsGuide/ec2-windows-user-data.html)

EC2インスタンスの作成時、「ステップ 3: インスタンスの詳細の設定」の「高度な設定」から処理を記述できます。
<img width="569" alt="01.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/2a063f9f-af4f-9122-3b58-25bed4117adc.jpeg">

なお、AWS公式のAMIからEC2インスタンスを作成する場合、**デフォルトでは初回起動時の１回のみしか実行されません。**
しかしそれは考えてみればとても自然な話で、ユーザーデータは「構築直後の面倒なセッティング作業を、構築時にまとめてコマンドで設定しちまおう」という思想のもと実装された機能（だと私は思ってる）ので、毎回起動するたびに処理が実行されたら逆に困るわけですね。この辺り、ユーザーデータがAWS環境のIaC推進に一役買っている部分と言えそうです。

# ユーザーデータの利用ケース

とは言え、このユーザーデータによる処理を、初回起動時以外も実行したい場合が出てくるかも知れません。例えば以下のようなケースです。

- ユーザーデータの処理を**毎回起動するたびに実行**したい
- 現在のサーバのAMI（バックアップ）を取得して、**AMI復元後に処理を実行**したい

こういった用途を考えたとき、ユーザーデータがどのような仕組みで実行されているのかを認識しておく必要があるわけですが、残念ながらユーザーデータの仕組みは**WindowsServerのバージョンによって大きく異なります。**具体的には「Windows Server 2012 R2以前」と「Windows Server 2016」で異なります。
※「今時2012R2なんて使うなよ」というありがたいご意見は、いったん聞こえないフリをさせていただきます

# ユーザーデータの仕組み

ユーザーデータ設定はEC2インスタンスの構築時以外にも、管理コンソールから設定できます（設定できるのはサーバ停止時のみ）。
<img width="508" alt="02.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/490c26f3-c705-aadc-f7f1-923dda6492eb.jpeg">

ただし前述の通り、デフォルトではユーザーデータは初回起動時しか実行されません。次回起動時に実行するには**OS側でのユーザーデータの有効化**が必要となります。Windows Server 2012 R2以前の場合は**EC2config**、Windows Server 2016の場合は**EC2Launch**というサービスを利用しており、それぞれ設定方法が異なります。

### Windows Server 2012 R2 以前の場合

プログラムの一覧から「EC2 Config Service Settings」を起動すると、EC2インスタンスの設定ウィンドウが表示されます。この中にあるUserDataの項目にチェックを入れます。そうすることにより、次回起動時にユーザーデータが有効化されます。
<img width="365" alt="03.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/97f823d6-2270-27ab-ec48-77259a75c8fd.jpeg">

試しにサーバを停止して、「Cドライブ直下にhoge.txtを出力する」というクソのような処理をユーザーデータに記述してみましょう。

```
<script>
echo hoge > C:\hoge.txt
</script>
```

<img width="693" alt="04.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/05af32c9-cdd5-3a8c-7091-2ba4b50e988b.jpeg">

設定後、EC2インスタンスを起動してファイルを確認すると、問題なくファイルが作成されていることが分かります。
<img width="600" alt="05.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/09149c57-4c25-d2dc-82b2-1ad5176895d1.jpeg">

しかし「EC2 Config Service Settings」を起動すると、UserDataのチェックが外れてしまっています。
<img width="365" alt="06.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/7d5c8626-15a0-0dfc-b2e8-dfbe87bb61a0.jpeg">

上記の流れで設定を行った場合、**ユーザーデータが実行されるのは次回起動時のみ**の設定となります。勘のいい人であれば、「EC2 Config Service Settings」のUserDataのチェックを入れた後にサーバを停止してAMIを取得すれば、次回起動時のみユーザーデータを実行してくれることが分かるかと思います。

では、毎回起動するたびにユーザーデータを実行するためにはどうするか。
それは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/WindowsGuide/ec2-windows-user-data.html)にもあるとおり、ユーザーデータの処理の末尾に**\<persist>true\</persist>**と入力するだけです。では、再度UserDataのチェックを入れてサーバを停止し、\<persist>true\</persist>を付加したゴミのような処理をユーザーデータに記述します。

```
<script>
echo fuga > C:\fuga_%date:/=%%time::=%.txt
</script>
<persist>true</persist>
```
<img width="692" alt="07.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/8612343b-9641-ecea-6e9a-c784d200573c.jpeg">

こうした設定を行えば、起動するたびに処理が実行され、
<img width="600" alt="08.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/2c861d99-7499-8b55-8ff9-e684da3d4afa.jpeg">
「EC2 Config Service Settings」のUserDataのチェックも外れないようになります。
<img width="365" alt="09.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/dc44477d-9d14-ad30-9c74-d2ce7abf5dc4.jpeg">

こうして考えると、AWS公式AMIから新規でEC2インスタンスを作成する際も、ユーザーデータの処理末尾に\<persist>true\</persist>を付加してさえいれば、毎回起動時に処理を実行してくれるということはお分かりかと思います。

WindowsServer2012R2まではEC2Configの設定さえ行えばそれで良かったので楽チンでした。しかしWindowsServer2016では、設定方法がガラッと変わってしまいます。

### Windows Server 2016 の場合

WindowsServer2016からはEC2Launchというサービスに変更されております。プログラムの一覧から「EC2LaunchSettings」を起動すると、WindowsServer2012R2以前に使用していたEC2Configと似たような設定画面が表示されます。
<img width="328" alt="10.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/33f06ed9-a401-c290-6ee2-cee99c9dac2d.jpeg">

こちらの設定画面にも「Handle User Data」という項目がありデフォルトではチェックが入っております。ただし、これも[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/WindowsGuide/ec2-windows-user-data.html)に書かれておりますが、Powershellで下記コマンドを実行しなければ次回起動時にユーザーデータは実行されません。

```
C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
```

実行すると、下記のような表形式でがコンソールに出力が返されます。

```
TaskPath TaskName                          State
-------- --------                          -----
\        Amazon Ec2 Launch - Instance I... Ready
```

その結果はタスクスケジューラに反映されます[^1]。EC2Launchがインストールされている場合、「Amazon EC2 Launch - Instance Initialization」というタスクが登録されておりますが、デフォルトでは無効化されております。上記コマンドを実行した場合、状態が「準備完了」となり、次回起動時にユーザーデータが実行されるようになります。
<img width="817" alt="12.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/dabf30a0-11ac-a458-5a09-bf463c1b10ce.jpeg">

注意すべきは、「EC2LaunchSettings」の設定画面には「Administrator Password」という項目があり、デフォルトでは**Random**となっております。この設定のままだと、**次回起動時にAdministratorのパスワードがランダムな文字列に変更**されてしまいます[^2]。なので、不用意なパスワード変更を避けるために、設定を**DoNothing**に変更しておきましょう。

さて、準備は整ったので、サーバを停止して「Cドライブ直下にhoge.txtを出力する」というクソのような処理をユーザーデータに記述してみましょう。今度はPowershellでやってみます。

```
<powershell>
New-Item C:\hoge.txt -itemType File -Value "hoge"
</powershell>
```
<img width="451" alt="13.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/3ee372b2-bf7c-8d27-68ab-e3a3119c6a45.jpeg">

設定後にEC2インスタンスを起動すると、今回もファイルが作成されました。
<img width="510" alt="14.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/c818bfc9-18a8-261a-bbad-a1fe5a8eac90.jpeg">

タスクスケジューラを確認すると、「Amazon EC2 Launch - Instance Initialization」のタスクの状態は「無効」になっています。
<img width="817" alt="15.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/140f7b6e-e95a-5552-9213-ae48a3a3fdf2.jpeg">

上記の流れで設定を行った場合、**ユーザーデータが実行されるのは次回起動時のみ**の設定となります。なので、初回起動時のみユーザーデータを実行するようなAMIを取得したい場合、上記のPowershellコマンド（InitializeInstanceのスケジュール）を実行した状態でサーバを停止してAMIを取得すればよいことが分かります。

では、毎回起動時にユーザーデータを実行するにはどうするか。実はWindowsServer2016の場合、方法は２つあります。

1.  ユーザーデータにInitializeInstanceのスケジュール処理を組み込み、毎度ユーザーデータの有効化を行う。
2.  WindowsServer2012R2以前と同様、\<persist>true\</persist>をユーザーデータに設定する。

例えば、「Cドライブ直下にfuga_(システム日付).txtを出力する」というカスのような処理を考えます。 **1.** の場合のユーザーデータは下記のような設定となります。

```
<powershell>
C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
$date = Get-Date -Format "yyyyMMddhhmm";
New-Item C:\fuga_$date.txt -itemType File -Value "fuga"
</powershell>
```

**2.** の場合はこうなります。

```
<powershell>
$date = Get-Date -Format "yyyyMMddhhmm";
New-Item C:\fuga_$date.txt -itemType File -Value "fuga"
</powershell>
<persist>true</persist>
```

いずれの方法でも、毎回起動時にユーザーデータに記述した処理を実行することができます。**1.** と **2.** の方式の差はタスクスケジューラに現れます。**1.** の方法では、前述の「Amazon EC2 Launch - Instance Initialization」のタスクの状態は常に「準備完了」という状態になります。
<img width="817" alt="12.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/78ab9031-b7b2-d69f-5c50-f4bf05d6a81c.jpeg">

**2.** の方法では、「Amazon EC2 Launch - Instance Initialization」のタスクの状態が「無効」となっている代わりに、**「Amazon EC2 Launch - Userdate Execution」**というタスクが新たに作成され「準備完了」の状態を保ちます。
<img width="817" alt="17.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/97680/c6104c24-9910-d750-7b2e-fdd8bc33e12f.jpeg">

どちらの方法を選ぶかは好みの問題だと思います。毎度スケジュールするなんてアホらしいと思うのであれば **2.** を選べばいいし、変にタスクを増やしたくなければ **1.** を選べばいいし、不安であれば両方織り込んだユーザーデータを実装してもいいと思います。

# おわりに

公式ドキュメントの重要性は十分承知しているつもりですが、公式以外の情報（主にQiita）を参考にすることもまあまああるので、そういった方々のお役に立てればと思い筆を取らせていただきました。
記載の誤り等あればぜひご指摘をお願いします。


[^1]: ちなみにWindowsServer2012以前（EC2Config）の場合は「EC2ConfigMonitorTask」というタスクがスケジューラに登録されていて、この中でユーザーデータ実行等を管理しているものと思われます。
[^2]: EC2インスタンスの初回起動時と同様、AWS管理画面からキーペアを使えばパスワードを再取得できます。