AWS CloudFormation で UserData を用いて Linux インスタンスを構築する際の TIPS
テンプレートの例、挙動、TIPS などを雑多にまとめた。
前提
- Lin インスタンスは RHEL
テンプレート
Resources: Instance1: Type: AWS::EC2::Instance Properties: …… UserData: !Base64 Fn::Sub: | #!/bin/bash -xe # ★ここに ShellScript を書く # 制約 # - root ユーザーで実行される(sudo 不要) # - インタラクティブは受け付けない # X) yum install unzip # O) yes | yum install unzip # 変数の書き方 # - CloudFormation系は ${} で囲む # !Ref Vpc1 → ${Vpc1} # ${AWS::Region} # - ShellScript の変数は ${!VarName} のように ! でエスケープ # target_vpc="`cat vpc-id.txt`" # aws ec2 describe-instances --filters "Name=vpc-id,Values=${!target_vpc}" ... # デバッグ # - 動作ログは以下みたいだが, メタ情報すぎて役に立たない # /var/log/cloud-init.log # - UserData に書いたスクリは以下に保存されてる # /var/lib/cloud/instances/(instance-id)/user-data.txt
ドキュメント
- Linux インスタンスでの起動時のコマンドの実行 - Amazon Elastic Compute Cloud
- UserData の挙動全般書いてある
- 熟読するのが結局一番の近道
- Fn::Sub - AWS CloudFormation
- UserData は Fn::Sub でくくるので押さえたい
挙動
UserData に書いたスクリはいつ実行されるか?
Ans: CREATE COMPLETE から少しタイムラグがある
- 1: 管理コンソール上で Lin インスタンスが CREATE COMPLETE した
- 2: Lin インスタンスに SSH ログインできるようになった
- 3: UserData に書いたスクリが実行された
1, 2, 3 は同時ではなく この順番で実行にタイムラグがある。感覚では 1 の後に 2 が可能になるまで数十秒、2 の後に 3 が実行されるまでに 10 秒。
なので素早く操作してると「あれ?UserData実行されてないんじゃん?」 → 「あ、実行されてた」なんてことになってちょっと焦る。
UserData に書いたスクリが実行失敗したらステータスはどうなる?
Ans: CREATE COMPLETE のまま。
管理コンソール上からは UserData スクリの実行成否は見えない。
たぶんだけど、そもそもレイヤーが違う感じ。以下のようになってる?
- Lin インスタンスが生成されたら CREATE COMPLETE
- その後で、
- UserData を走らせる
- この結果は管理コンソールには出さないよ
UserData 部分を編集して再アップ(スタック更新)したら再実行どうなる?
Ans: されません。
リファレンス AWS::EC2::Instance - AWS CloudFormation によると、
Update requires: Some interruptions
とある。Some interruptions(中断を伴わない更新)とは リソースの使用を中断することなく、またリソースの物理 ID を変更することなく、リソースを更新する もの。一方で、UserData は「新しくインスタンスをつくるときに一度だけ実行される」もの。
よって再実行はされない。
※リソース更新として何が更新されるかはよくわからん。 /var/lib/cloud/instances/(instance-id)/user-data.txt あたりが置き換わったりしてる?
TIPS - 書き方
あらかじめつくっといたスクリを実行させたいのですが?
Ans: どっかにアップしたものをゲットしてくる。
たとえば S3 にアップしておいて、curl で取ってくるとか。
curl "https://(BucketName).s3-(RegionName).amazonaws.com/(UploadedScriptName).sh" -o "(YourFavoriteName).sh" chmod 775 (YourFavoriteName).sh ./(YourFavoriteName).sh
ただし S3 のアクセス権が面倒か。適切にアクセス権つけて IAM つける必要がある。
私は今回はちょっとしたお試しだったので以下のようにした。
- スクリプトは全部パブリックにする
- バケットは……
- 使うときはパブリックにする
- 使わないときはパブリックを全部切る
ガバガバなので好ましくはない。たとえば URL がばれたら誰でも見れちゃうし、悪意ある人が DDoS で get を仕掛けたら(たとえば何百万回とダウンロードするとか)課金地獄になってしまうかもしれない。
AWS CLI をセットアップしたいのですが?
Ans: 愚直に。
AWS_REGION="ap-northeast-1" curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" python get-pip.py pip install awscli aws configure set region ${AWS_REGION}
自身が所属する VPC の ID など AWS リソース情報がほしいのですが?
Ans: AWS CLI の describes 系コマンドで頑張る。
たとえば以下は「指定タグがついた VPC」の ID を表示する。
$ aws ec2 describe-vpcs --filters "Name=tag-key,Values=${TagKeyForMyParentVpc}" --query "Vpcs[].VpcId[]" [ "vpc-XXXXXXXXXXXXXXXXX" ]
上記をもう少し詳しく言うと、TagKeyForMyParentVpc タグのついた VPC 情報 response を引っ張ってきて、その response.vpcs.0.VpcId を見ている。クエリは --query "Vpcs[].VpcId
でも良いかも?
ちなみに上記は jq でパースすれば楽に取り出せる。
$ aws ec2 describe-vpcs --filters "Name=tag-key,Values=${TagKeyForMyParentVpc}" --query "Vpcs[].VpcId[]" | jq -r .[0] vpc-XXXXXXXXXXXXXXXXX
参考: describe-vpcs — AWS CLI 1.16.281 Command Reference
JSON をパースしたいのですが?
Ans: jq を使う。RHEL ではパッケージがないので自力でバイナリを get してきて配置する。
yes | yum install wget wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 chmod 775 ./jq cp jq /usr/bin
参考: How to install jq on RHEL6.5 - Server Fault
TIPS - UserData を素早く試行するには?
まず ShellScript 部分はインスタンス上で一通り動作確認まで持っていく。問題はその後、UserData に書き写してから動作成功に持っていくところだろう。
やり方1: 全部入りテンプレ
愚直に行くと「インスタンス」と「インスタンスに必要な VPC やら IAM やら」を全部つくるテンプレに UserData を書いてスタック作成することになる。仮に 全部入りテンプレ と呼ぼう。
全部入りテンプレは言うまでもなく時間がかかる。最小構成でも VPC x1、Subnet x1~2、RouteTable x1~2、InternetGateway x1、NAT x1、SecurityGroup x1、Instance x1 くらいはつくると思う。この規模だと スタック作成に 5 分、削除にも 5 分くらいかかる感触。つまり一度の試行で 10 分くらいかかる。
なのでもう少し楽をする。
やり方3: 注入テンプレ
注入テンプレ と名付けた方法を使う。といっても、単純で、
- 1: 「インスタンスに必要な環境をつくるテンプレ A 」で環境をつくる
- 2: 「A の環境にインスタンスをつくるテンプレ B」でインスタンスをつくる
- 試行するときは B だけをスタック作成&削除する
こうする。これなら全部入り(A+B)とは違い、インスタンス(B)だけが作成&削除の対象になる。作成に 1 分、削除に 1 分くらいで済む。
※IAM もつくっちゃうと 2 分くらいなるけど……
ちなみにインスタンス作成時には「サブネットの ID」が必要なので、以下のようにすると良いか。
- テンプレ A の Output で、サブネットの ID を表示する
- テンプレ B には「テンプレ A のサブネット ID」を受け付けるパラメーターを設置する
おわりに
サーバー設定の自動化として UserData を使う方法についてまとめた。AWS リソースと比べるとトライアルアンドエラーしづらいが、このレイヤーも自動化したいなら頑張るしかない。少しずつ頑張っていくとしよう。
他にも AWS::CloudFormation::Init などの手段があるみたいなので追々試したい。