インフラやクラウドの専門というわけではないのですがそろそろIaC周りもちゃんと勉強しておきたい・・・という感じなのでAWSのCloudFormationについて入門しつつ復習として記事にまとめておきます。
※とりあえず最初ということでごく基本的なところを中心に記事を書いていきます。
注意事項と前記事までの振り返り
本記事の処理を動かすとEC2関係などで色々と追加になったり起動したりします。その辺はリソースの停止や削除などをしないとお金がかかったりしてくる可能性があるためご注意ください。
また、本記事は4記事目となります。前回までの内容を踏襲していく形となっているのでご注意ください。
1記事目:
2記事目:
3記事目:
また、前回までの内容を踏まえYAMLのテンプレートは以下のような内容(EC2のごく基本的な設定)をベースに色々編集する形で進めていきます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t2.nano
本記事で触れること
前記事までで少しだけ触れたCloudFormationの組み込み関数についてもっと内容を把握するために全体的に各関数などに触れていきます。以下の関数を対象とします。
Fn::GetAtt
Fn::FindInMap
Fn::Join
Fn::Split
Fn::Select
Fn::Sub
Fn::GetAZs
※組み込み関数の概要などに関しては前記事までで触れたので割愛します。
※あまり使う機会が無さそうな関数は割愛します。
※既に前の記事で触れたRef関数などは割愛します。
※後の記事で恐らく触れるかと思われる複数のスタックを使う場合やテンプレートのモジュール化をするケースなどに絡んだ関数はまだ触れません(Fn::ImportValue
やFn::Transform
など)。
※条件分岐の関数は別の記事で扱います。
また、関数による値の取得結果の確認などのためにOutputsの機能を軽くだけ触れて使っていったり、Mappingsの機能に関しても関数に合わせて少し触れていきます。
Outputs(出力)について
後の記事で詳しく触れるかもしれないため本記事では軽く触れるのみとなりますがOutputs関係についても説明しておきます(関数の値取得結果の確認などに使用します)。
OutputsというキーでYAML上で定義することで、CloudFormationのテンプレートの反映結果の値を一部出力することができます。
この出力値は確認として使えるだけでなく、他のテンプレートから参照したりすることもできます。
今回は値の確認のみで使用します。
最低限の書き方としては以下のように任意の論理名(今回はMyFirstEC2InstanceId
としました)の指定とValue
キーで出力したい値を指定します。
Outputs:
MyFirstEC2InstanceId:
Value: !Ref MyFirstEC2Instance
Ref関数でEC2インスタンスの論理名を指定すると値としてはインスタンスIDとなります。
YAML全体としては以下のようになっています。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyFirstEC2InstanceId:
Value: !Ref MyFirstEC2Instance
試しに既存スタックに対して変更セットを作成・反映してみて挙動を確認してみます。
正常に処理が通りました。
Outputsの内容は出力タブに表示されるのでイベントタブから出力タブに切り替えます。更新処理完了後に出力タブでもリロードしないと結果が反映されないかもしれません。
キーにはYAML上で指定した論理名のMyFirstEC2InstanceId
、値には対象のインスタンスのIDが設定されていることが確認できます。
一応EC2のインスタンスのページにアクセスしてインスタンス IDを確認してみます。
CloudFormationの出力結果のインスタンス IDと一致していることが確認できました。
本記事ではこの出力内容を確認用として使用していきます。
Fn::GetAtt
関数
Fn::GetAtt
関数は任意のリソースの特定の属性の値を取得します。短縮系では!GetAtt
となります。
書き方としてはFn::GetAtt: [<対象リソースの論理名>, <属性名>]
もしくは!GetAtt <対象リソースの論理名>.<属性名>
となります。
属性名に関しては以下のドキュメントで対象のサービスを選択すると表示されるページのリンクを辿っていくと確認できます。
たとえばEC2インスタンスのAZを参照したい・・・という場合にはAmazon EC2 → AWS::EC2::Instanceと各リンクをクリックしていって表示されるページ内にあるReturn values節のFn::GetAtt部分の中からAZ関係を探して参照すればOKとなります。
もっと色々な属性が取れるかな?と思っていたら割と内容が限られている気配があります。欲しい属性が取れるようになっているかどうかは事前に確認しておいた方が良いかもしれません。
AZはAvailabilityZone
という属性名とドキュメントに書かれているため、YAML上ではValue: !GetAtt MyFirstEC2Instance.AvailabilityZone
という記述になります。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyFirstEC2Az:
Value: !GetAtt MyFirstEC2Instance.AvailabilityZone
試しに変更セットを作成して反映してみます。
更新処理が終わるまでまってから出力タブをリロードして確認すると、ap-northeast-1d
というAZの値が取れていることが確認できます。
Fn::Join
関数
Fn::Join
関数では指定された配列の文字列の値を別途指定された区切り文字で連結します。
値はリストで指定し、リストの最初のインデックスには区切り文字、2番目のインデックスには連結したい文字列をさらにリストで指定します。
例えば[AWS, EC2, Instance]
という文字列のリストを::
の区切り文字で連結したい場合にはFn::Join: ["::", [AWS, EC2, Instance]]
といった書き方になります。
短縮系の場合には!Join ["::", [AWS, EC2, Instance]]
といった書き方となります。
試しにYAMLのOutputs部分でこの関数を使って試してみます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyResourceType:
Value: !Join ["::", [AWS, EC2, Instance]]
更新などが終わったら出力タブでリロードして内容を確認します。
AWS::EC2::Instance
というリストの各文字列を::
記号で連結した値が取得できていることが確認できました。
Fn::Select
関数
Fn::Select
関数ではリストの中から特定のインデックスの値を取得します。
値はリストで指定し、リストの最初のインデックスには取得したい値のインデックス、2番目のインデックスには対象のリストを指定します。インデックスは0からスタートします。
例えば[AWS, EC2, Instance]
というリストの中から2番目の値(EC2
)を取得したい場合にはFn::Select: [1, [AWS, EC2, Instance]]
という書き方になります。
短縮系の場合には!Select [1, [AWS, EC2, Instance]]
といった書き方になります。
試しに以下のYAMLで変更セットの作成や反映を行ってみます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyString:
Value: !Select [1, [AWS, EC2, Instance]]
更新が終わって出力タブでリロードするとEC2
という値がリストの中から取れていることが確認できます。
Fn::Split
関数
Fn::Split
関数はFn::Join
の逆のような挙動をします。つまり特定の記号などで連結された文字列をリストに分割します。
値はリストで指定します。最初のインデックスには区切り文字、2番目のインデックスには分割したい文字列を指定します。
たとえばAWS::EC2::Instance
という文字列を::
という区切り文字でリストに分割したい場合にはFn::Split: ["::", "AWS::EC2::Instance"]
といったように指定します。
短縮系の場合には!Split ["::", "AWS::EC2::Instance"]
という書き方になります。
Outputsでの指定にはリストは設定できないため、以下の例ではSplit
関数で分割してからSelect
関数で特定の値を取得して設定しています。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyString:
Value: !Select [2, !Split ["::", "AWS::EC2::Instance"]]
スタックの変更セットを作成・反映し、更新が終わってから出力タブの画面でリロードすると文字列分割後のリストの中でインデックスが2のInstance
という値が取れていることを確認できます。
Fn::Sub
関数
Fn::Sub
関数では文字列内に対して別の値を挿入したりすることができます。Pythonのformatメソッドとかに近い形になります。
短縮系は!Sub
となります。
引数には2つのインデックスを持つリストで指定します。最初のインデックスには置換対象の文字列、2つ目のインデックスには置換に指定するパラメーター名とパラメーターの辞書の値を指定します。
YAML上では以下のような感じになります。複数の置換パラメーターを指定したい場合には2つ目のインデックスに必要数分追加していきます。
!Sub
- <置換対象の文字列>
- <置換で使うパラメーター名1>: <置換で使うパラメーター1>
<置換で使うパラメーター名2>: <置換で使うパラメーター2>
...
置換対象の文字列には${パラメーター名}
といった記述で置換したい箇所を含めておきます。
Pythonで言うと文字列中の%s
とか{formatで置換するパラメーター名}
とかみたいな部分になります。
置換で使うパラメーターの値には他の関数などを利用することも出来ます。
例えば以下の例ではGetAtt
関数を使ってAvailability Zoneの値を取得し、それを使ってSub
関数で文字列の値の一部を置換しています。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyString:
Value: !Sub
- "My instance's AZ is ${AvailabilityZone}."
- AvailabilityZone: !GetAtt MyFirstEC2Instance.AvailabilityZone
スタックの変更セットを作成・反映してみて挙動を確認してみます。
完了後にリロードなどをして出力内容を見てみるとMy instance's AZ is ${AvailabilityZone}.
とYAML上で指定していた文字列がMy instance's AZ is ap-northeast-1d.
と置換された文字列になっていることが確認できます。
Fn::GetAZs
関数
Fn::GetAZs
関数は指定されたリージョンで利用可能なAvailability Zoneのリストを返却します。
Fn::GetAZs: <対象のリージョン名>
という書き方をします。短縮系では!GetAZs <対象のリージョン名>
という記述になります。
以下の例では東京リージョン(ap-northeast-1
)のリストをGetAZs
関数で取得しています。Outputsへの指定ではリストは指定できないのでJoin
関数でコンマ区切りで連結しています。
余談ですがap-northeast-1
の指定をしているとなぜかcfn-lintに引っかかりました。us-east-1
とかの他のリージョンでは普通にエラーなく通っています・・・。良く分からなかったのと実際に変更セットを作成した限りでは普通にエラー無く通ったので気にしないで進めます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: t3.nano
Outputs:
MyString:
Value: !Join [",", !GetAZs "ap-northeast-1"]
変更セットの作成や反映などをして出力結果を確認するとap-northeast-1a,ap-northeast-1c,ap-northeast-1d
といったように東京リージョンの各AZ名が取れていることが確認できます。
Fn::FindInMap
関数
最後にFn::FindInMap
関数について触れていきます。ただしFn::FindInMap
関数に触れる前にMappings
の機能について先に触れておきます(Fn::FindInMap
と基本的にセットで使用するため)。
Mappingsの機能について
CloudFormationにはMappings
のセクションに多次元の辞書(マッピング)を使う形で各キーに応じた定数のようなものを定義することができます。
Mappings
セクション内にマッピング名、対象名、キー名といった具合に3つの階層が必要になります。キー名は同じパラメーターであれば同じキー名とかで大丈夫です。
これによって例えばパラメータで対象の環境を選択し、環境に応じた開発環境・ステージング・本番環境の各インスタンスタイプのマッピングを定義しておいて、選択された環境のインスタンスタイプの値をマッピングから取得する・・・みたいなことができるようになります。
YAMLだと以下のようなフォーマットになります。
Mappings:
<マッピング名>:
<対象名1>:
<キー名1>: <値1>
<対象名2>:
<キー名2>: <値2>
...
例えば先ほどのインスタンスタイプの例に合わせてマッピング名をInstanceTypeMapping
、対象名を開発環境・ステージング・本番としてDev
, Stg
, Prd
、キー名をInstanceType
として値に各インスタンスタイプを持つマッピングだと以下のようになります。
Mappings:
InstanceTypeMapping:
Dev:
InstanceType: t3.nano
Stg:
InstanceType: t3.nano
Prd:
InstanceType: t3.small
このマッピングの個別の値をFn::FindInMap
関数を使って取得することができます。
Fn::FindInMap
関数の使い方
Fn::FindInMap
関数の値には3つのインデックスを持つリストを指定します。1つ目のインデックスにはマッピング名(前節の例で言うとInstanceTypeMapping
部分が該当)、2つ目のインデックスには対象名(最初のキー名。前節の例で言うとDev
やStd
などの部分が該当)、3つ目のインデックスにはキー名(2つ目のキー目。前節で言うとInstanceType
部分が該当)を指定します。返却値としてはマッピングの値(前節で言うとt3.nano
などの部分が該当)が返却されます。
YAMLだとFn:FindInMap: [<マッピング名>, <対象名>, <キー名>]
といった書き方になります。YAMLのリストの書き方的に以下のように改行とインデント・ハイフンを使う形でも受け付けてくれます。
Fn:FindInMap:
- <マッピング名>
- <対象名>
- <キー名>
短縮系だと!FindInMap [<マッピング名>, <対象名>, <キー名>]
といったように指定します。
また、この各インデックスの部分には他のRef
などの関数を挟むこともできます。つまりパラメータで環境を選択させて、選択された値をRef
関数で参照してFn::FindInMap
関数のリスト内で使う・・・みたいな書き方ができます。
例としてEnvironment
という名前のパラメータを定義していてそちらをFn::FindInMap
関数で産所してマッピングの値を取得したい・・・といった場合には以下のような記述になります。
...
!FindInMap:
- InstanceTypeMapping
- !Ref Environment
- InstanceType
実際にパラメータも定義したYAMLファイルを書いて変更セットを作成して試してみます。
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
InstanceTypeMapping:
Dev:
InstanceType: t3.nano
Std:
InstanceType: t3.nano
Prd:
InstanceType: t3.small
Parameters:
Environment:
Type: String
AllowedValues:
- Dev
- Std
- Prd
Resources:
MyFirstEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0590f3a1742b17914
InstanceType: !FindInMap
- InstanceTypeMapping
- !Ref Environment
- InstanceType
YAMLをアップロードして設定を進めます。今回のYAMLテンプレートでは環境のパラメータを設定しているため選択が必要になります。
とりあえず試しとしてインスタンスタイプを変更してみたいためPrdを選択して進めます(現在はインスタンスタイプがnanoになっているのでPrdのsmallに変更してみます)。
イベントが完了するまで待ちます。
完了後にEC2のページにアクセスするとインスタンスタイプがPrdのマッピングに合わせたt3.small
になっていることが確認できます。
今度は同じテンプレートでパラメータでDevを選択してみます。
再度変更セットを反映してから完了するまで待った後にEC2インスタンスのページにアクセスするとインスタンスタイプがDevのマッピングに合わせたt3.nano
になっていることが確認できます。
このようにFn:FindInMap
関数を使ってマッピングの定義から値を取得できることが確認できました。
参考文献・参考サイト・参考講座まとめ