TL;DR
- EC2インスタンスのWindowsServerに、ユーザーデータを設定する話。
- 「ユーザーデータの存在は知ってるけど仕組みは知らないよ」という人向け。
- 本当に、WindowsServerのバージョンで仕組みを変えるのはやめてほしい。
※ 今回はあくまでWindowsServerの話で、Linuxの話は一切出てきません。
ユーザーデータとは
ご存じの方も多数おられるかも知れませんが、AWS・EC2のユーザーデータはサーバの起動時に指定した処理を実行する仕組みです。その辺りはAWSの公式ドキュメントをご覧いただければお分かりかと。
Windows インスタンスでの起動時のコマンドの実行
EC2インスタンスの作成時、「ステップ 3: インスタンスの詳細の設定」の「高度な設定」から処理を記述できます。
なお、AWS公式のAMIからEC2インスタンスを作成する場合、デフォルトでは初回起動時の1回のみしか実行されません。
しかしそれは考えてみればとても自然な話で、ユーザーデータは「構築直後の面倒なセッティング作業を、構築時にまとめてコマンドで設定しちまおう」という思想のもと実装された機能(だと私は思ってる)ので、毎回起動するたびに処理が実行されたら逆に困るわけですね。この辺り、ユーザーデータがAWS環境のIaC推進に一役買っている部分と言えそうです。
ユーザーデータの利用ケース
とは言え、このユーザーデータによる処理を、初回起動時以外も実行したい場合が出てくるかも知れません。例えば以下のようなケースです。
- ユーザーデータの処理を毎回起動するたびに実行したい
- 現在のサーバのAMI(バックアップ)を取得して、AMI復元後に処理を実行したい
こういった用途を考えたとき、ユーザーデータがどのような仕組みで実行されているのかを認識しておく必要があるわけですが、残念ながらユーザーデータの仕組みは**WindowsServerのバージョンによって大きく異なります。**具体的には「Windows Server 2012 R2以前」と「Windows Server 2016」で異なります。
※「今時2012R2なんて使うなよ」というありがたいご意見は、いったん聞こえないフリをさせていただきます
ユーザーデータの仕組み
ユーザーデータ設定はEC2インスタンスの構築時以外にも、管理コンソールから設定できます(設定できるのはサーバ停止時のみ)。
ただし前述の通り、デフォルトではユーザーデータは初回起動時しか実行されません。次回起動時に実行するにはOS側でのユーザーデータの有効化が必要となります。Windows Server 2012 R2以前の場合はEC2config、Windows Server 2016の場合はEC2Launchというサービスを利用しており、それぞれ設定方法が異なります。
Windows Server 2012 R2 以前の場合
プログラムの一覧から「EC2 Config Service Settings」を起動すると、EC2インスタンスの設定ウィンドウが表示されます。この中にあるUserDataの項目にチェックを入れます。そうすることにより、次回起動時にユーザーデータが有効化されます。
試しにサーバを停止して、「Cドライブ直下にhoge.txtを出力する」というクソのような処理をユーザーデータに記述してみましょう。
<script>
echo hoge > C:\hoge.txt
</script>
設定後、EC2インスタンスを起動してファイルを確認すると、問題なくファイルが作成されていることが分かります。
しかし「EC2 Config Service Settings」を起動すると、UserDataのチェックが外れてしまっています。
上記の流れで設定を行った場合、ユーザーデータが実行されるのは次回起動時のみの設定となります。勘のいい人であれば、「EC2 Config Service Settings」のUserDataのチェックを入れた後にサーバを停止してAMIを取得すれば、次回起動時のみユーザーデータを実行してくれることが分かるかと思います。
では、毎回起動するたびにユーザーデータを実行するためにはどうするか。
それは公式ドキュメントにもあるとおり、ユーザーデータの処理の末尾に**<persist>true</persist>**と入力するだけです。では、再度UserDataのチェックを入れてサーバを停止し、<persist>true</persist>を付加したゴミのような処理をユーザーデータに記述します。
<script>
echo fuga > C:\fuga_%date:/=%%time::=%.txt
</script>
<persist>true</persist>
こうした設定を行えば、起動するたびに処理が実行され、
「EC2 Config Service Settings」のUserDataのチェックも外れないようになります。
こうして考えると、AWS公式AMIから新規でEC2インスタンスを作成する際も、ユーザーデータの処理末尾に<persist>true</persist>を付加してさえいれば、毎回起動時に処理を実行してくれるということはお分かりかと思います。
WindowsServer2012R2まではEC2Configの設定さえ行えばそれで良かったので楽チンでした。しかしWindowsServer2016では、設定方法がガラッと変わってしまいます。
Windows Server 2016 の場合
WindowsServer2016からはEC2Launchというサービスに変更されております。プログラムの一覧から「EC2LaunchSettings」を起動すると、WindowsServer2012R2以前に使用していたEC2Configと似たような設定画面が表示されます。
こちらの設定画面にも「Handle User Data」という項目がありデフォルトではチェックが入っております。ただし、これも公式ドキュメントに書かれておりますが、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」というタスクが登録されておりますが、デフォルトでは無効化されております。上記コマンドを実行した場合、状態が「準備完了」となり、次回起動時にユーザーデータが実行されるようになります。
注意すべきは、「EC2LaunchSettings」の設定画面には「Administrator Password」という項目があり、デフォルトではRandomとなっております。この設定のままだと、次回起動時にAdministratorのパスワードがランダムな文字列に変更されてしまいます2。なので、不用意なパスワード変更を避けるために、設定をDoNothingに変更しておきましょう。
さて、準備は整ったので、サーバを停止して「Cドライブ直下にhoge.txtを出力する」というクソのような処理をユーザーデータに記述してみましょう。今度はPowershellでやってみます。
<powershell>
New-Item C:\hoge.txt -itemType File -Value "hoge"
</powershell>
設定後にEC2インスタンスを起動すると、今回もファイルが作成されました。
タスクスケジューラを確認すると、「Amazon EC2 Launch - Instance Initialization」のタスクの状態は「無効」になっています。
上記の流れで設定を行った場合、ユーザーデータが実行されるのは次回起動時のみの設定となります。なので、初回起動時のみユーザーデータを実行するようなAMIを取得したい場合、上記のPowershellコマンド(InitializeInstanceのスケジュール)を実行した状態でサーバを停止してAMIを取得すればよいことが分かります。
では、毎回起動時にユーザーデータを実行するにはどうするか。実はWindowsServer2016の場合、方法は2つあります。
- ユーザーデータにInitializeInstanceのスケジュール処理を組み込み、毎度ユーザーデータの有効化を行う。
- 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」のタスクの状態は常に「準備完了」という状態になります。
2. の方法では、「Amazon EC2 Launch - Instance Initialization」のタスクの状態が「無効」となっている代わりに、**「Amazon EC2 Launch - Userdate Execution」**というタスクが新たに作成され「準備完了」の状態を保ちます。
どちらの方法を選ぶかは好みの問題だと思います。毎度スケジュールするなんてアホらしいと思うのであれば 2. を選べばいいし、変にタスクを増やしたくなければ 1. を選べばいいし、不安であれば両方織り込んだユーザーデータを実装してもいいと思います。
おわりに
公式ドキュメントの重要性は十分承知しているつもりですが、公式以外の情報(主にQiita)を参考にすることもまあまああるので、そういった方々のお役に立てればと思い筆を取らせていただきました。
記載の誤り等あればぜひご指摘をお願いします。