この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
CloudFormationでEC2のユーザーデータをごりっごりに書きたいんじゃ…!
そんな気分の時はございませんか?
ユーザーデータとは、EC2起動時に実行されるシェルスクリプトです。
Linux インスタンスでの起動時のコマンドの実行 - Amazon Elastic Compute Cloud
起動時にホスト名、時刻同期、文字コード、タイムゾーンの設定をスクリプト化することでEC2の構築作業を省力化できます。 弊社ブログで具体的な方法を紹介しています。
[AWS]CloudFormationを使いこなして早く帰るTips5選 | DevelopersIO
そんな中、ユーザーデータでシェル変数を取り扱う時にちょっと詰まったのでその備忘録です。
CloudFormationでユーザーデータを書く
ユーザーデータをEC2に直接定義するとマネジメントコンソールで確認できないため、今回はLaunchTemplate(起動テンプレート)を利用します。
CloudFormationでユーザーデータを定義したLaunchTemplateを書くとこんな感じです。 ホームフォルダによくわかんないファイルを作っていますが、とりあえず流してください。
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
SampleLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: sample-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: |
#!/bin/bash
echo INSTANCE_ID > /home/ec2-user/instance-id.txt
CloudFormationでユーザーデータを記述する場合、Base64エンコードされてなければいけません。なので組み込み関数の Fn::Base64
を使用しています。
AWS::EC2::Instance - AWS CloudFormation
Fn::Base64 - AWS CloudFormation
組み込み関数Fn::Subを使用する
ユーザーデータの中でCloudFormationのパラメーターを使いたいことがよくあります。
ホスト名をパラメーターで指定できるようにしてテンプレートを使いまわしたいとか、そういうケースです。
そういう場合は、組み込み関数Fn::Subを使用します。
ファイル名をパラメータ指定できるように変更するとこんな感じです。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
InstanceIdFileName:
Type: String
Default: instance-id.txt
Resources:
SampleLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: sample-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
echo INSTANCE_ID > /home/ec2-user/${FileName}
- {
FileName: !Ref InstanceIdFileName
}
ユーザーデータの中でシェル変数を使用する
さて、ここからが本題です。
Fn::Subの変数は ${MyVarName}
といった表記をしているんですが、完全にシェルの変数参照と表記が被っています。
なのでcurlコマンド使ってEC2のメタデータからインスタンスIDとってきて変数に格納、後の行で変数参照しようと思って以下の様な書き方をすると、 Unresolved resource dependencies [INSTANCE_ID]
ってvalidateで怒られます。
---
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
InstanceIdFileName:
Type: String
Default: instance-id.txt
Resources:
SampleLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: sample-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
echo ${INSTANCE_ID} > /home/ec2-user/${FileName}
- {
FileName: !Ref InstanceIdFileName
}
どうしたものかと悩んでいたんですが、よく見るとエスケープの方法がドキュメントに記載されていました。
ドル記号と中括弧 (${}) をそのまま書き込むには、最初の中括弧の後に感嘆符 (!) を追加します (${!Literal} など)。AWS CloudFormation では、このテキストは ${Literal} のようになります。
と、いうわけでドキュメントに記載されている通りエスケープしてやれば、シェル変数もガンガン使えます。
---
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
InstanceIdFileName:
Type: String
Default: instance-id.txt
Resources:
SampleLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: sample-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
echo ${!INSTANCE_ID} > /home/ec2-user/${FileName}
- {
FileName: !Ref InstanceIdFileName
}
実際にCloudFormationスタックを作成して、マネジメントコンソールでユーザーデータの内容を確認してみると、ファイル名だけCloudFormationのパラメーターに置換されているのに対して、エスケープした ${INSTANCE_ID}
はそのまま残せています。
このLaunchTemplateを元にしてEC2を立ててSSH接続してみると、想定したとおりホームフォルダにインスタンスIDが記載されたファイルができていることがわかります。
おわりに
これでユーザーデータに変数山盛りにしたCloudFormationテンプレートがガンガン書けますね!!
他の人が読めるテンプレートになるよう、ほどほどにしておきましょう。
おまけ
検証に使用したEC2付きのCloudFormationテンプレートを記載しておきます。 イメージID,キーペア名,セキュリティグループID,サブネットIDは適切に設定してください。
---
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
InstanceIdFileName:
Type: String
Default: instance-id.txt
Ec2InstanceType:
Type: String
Default: t2.micro
Ec2ImageId:
Type: AWS::EC2::Image::Id
Ec2KeyPairName:
Type: AWS::EC2::KeyPair::KeyName
Ec2SecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
Ec2SubnetId:
Type: AWS::EC2::Subnet::Id
Resources:
SampleLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: sample-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
echo ${!INSTANCE_ID} > /home/ec2-user/${FileName}
- {
FileName: !Ref InstanceIdFileName
}
SampleInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref Ec2ImageId
InstanceType: !Ref Ec2InstanceType
KeyName: !Ref Ec2KeyPairName
SecurityGroupIds:
- !Ref Ec2SecurityGroupId
SubnetId: !Ref Ec2SubnetId
Tags:
-
Key: Name
Value: sample-ec2
LaunchTemplate:
LaunchTemplateId: !Ref SampleLaunchTemplate
Version: !GetAtt SampleLaunchTemplate.LatestVersionNumber