こんにちは。デジタル・トランスフォーメーション推進開発部の保田(ほだ)と申します。
前回書いた記事からかなり間が空いてしまいました。 blog.css-net.co.jp
デジタル・トランスフォーメーションということで、似た名前の CloudFormation の話をします。
要約
Fn::Sub
の値にリストを持ってくるといい感じにマッピングしてくれる
前提知識
前提知識は以下の通りです。
- CloudFormation の初歩
本題
真面目な話は公式ドキュメントにすべて書いてあります。
■参考 マッピングで Fn::Sub
以降はザックリ噛み砕いて語っていきます。
CFn を語りたい
蛇足ですが、まず組み込み関数の基本的な説明をします。 知っとるわい、という方は適当に読み飛ばしていただいて大丈夫です。
まず Fn::Sub
は CloudFormation テンプレートの組み込み関数の一つで、代入( Substitution)が出来ます。
例えば例を挙げると以下のような CFn のテンプレートのような使い方があります。
Parameters: MyBucketName: Type: String Description: "Enter a MyBucket Name" Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: Fn::Sub: ${MyBucketName} # ここ
Parameters セクションに書いたパラメータ MyBucketName の値を ${MyBucketName}
に代入しているわけです。
YAML では省略記法として !Sub
が使えますので、以降後者の書き方に統一します。
Parameters: MyBucketName: Type: String Description: "Enter a MyBucket Name" Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${MyBucketName} # ここ
ネストが一つ浅くなるメリットもありますので、使えるところでは省略記法の方が良いでしょう。
ちなみにこの例だと、参照 Ref
(省略記法で !Ref
)で済むので本当はこっちの方が良いです。
Parameters: MyBucketName: Type: String Description: "Enter a MyBucket Name" Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref MyBucketName # ここ
■参考 Ref
細かいところで言うと !Sub
はパラメータへの代入なので${}
で囲った ${MyBucketName}
を、 !Ref
は論理 ID の参照なので ${}
を付けずに MyBucketName
と書くところもポイントですね。
まぁ多分みなさん無意識に使い分けることが出来ていると思いますが…
次のような例だと Sub
の出番です。
((前半は省略)) Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${MyBucketName}-${AWS::AccountId}
AWS::AccountId
は 擬似パラメータ参照 です。
ザックリ言うと CFn スタックの作成・更新・削除を行った人(あるいは AWS リソース)の認証情報に応じて自動的にアカウント ID を参照してくれます。
AWS アカウント ID はセキュリティ面や可搬性の観点からもハードコーディングしないことが推奨されますので、この書き方はよく使われます。 また、重複 NG で世界で唯一でないといけない S3 バケット名を簡単に一意にする方法としても末尾に AWS アカウントを付けるのはよくありますよね。
一応 Ref
でも書けますが、ダサいです。
((前半は省略)) Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: Fn::Join: - "-" - - !Ref MyBucketName - !Ref AWS::AccountId
Fn::Join
はその名の通り結合です。これ複雑ですよね。いつも間違えます。
■参考 Fn::Join
本当に本題
語りたい欲が出てしまい、かなり脇道に逸れましたがようやく本題です。
状況設定
まず具体的なユースケースを以下に列挙します。
- S3 バケットのテンプレートを作る
- 本番環境(1つ)、開発環境(複数)をひとつの CFn テンプレートで定義する
- 本番環境のバケット名は MyBucket とする
- 開発環境のバケット名は MyBucket-012345678901 とする
- ※ 012345678901 は開発者の AWS アカウント ID
答え
というわけで、結論から言うと答えはこうなります。
AWSTemplateFormatVersion: 2010-09-09 Parameters: Env: Type: String Default: dev AllowedValues: - dev - prod Description: "Enter an environment name" Conditions: IsProd: !Equals [!Ref Env, prod] Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub - "MyBucket${Suffix}" - Suffix : !If [IsProd, "", !Sub "-${AWS::AccountId}"]
パブリックアクセスのブロック設定や暗号化設定(※)も大事ですがここでは見かけ上省きます。
(※) ↓ を追加しよう!
PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256
解説
では解説です。
まず Parameters セクションです。
半分おまけ程度ですが、パラメータ Env
として利用できる値を dev
と prod
に限定しています。
マネジメントコンソール上からスタックを作成するときはドロップダウンリストとして表示されます。便利!
Parameters: Env: Type: String Default: dev AllowedValues: - dev - prod Description: "Enter an environment name"
次に Conditions セクションです。
Conditions: IsProd: !Equals [!Ref Env, prod]
■参考 条件
ここでは組み込み関数 Fn::Equals
を使っていますが、これは後ろに来る2つの値が同じであれば true
を、そうでない場合は false
を返します。
要するに !Ref Env
と prod
が等しいかどうかを見ているわけでして、無論 Env
は直前のパラメータにして指定した dev
もしくは prod
が来るやつです。
まとめるとこうです。
- パラメータ
Env
をdev
とする →IsProd
はfalse
- パラメータ
Env
をprod
とする →IsProd
はtrue
IsProd
という名前もしっくりきますね。
最後に Resources セクションです。
Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub - "MyBucket${Suffix}" - Suffix : !If [IsProd, "", !Sub "-${AWS::AccountId}"]
はい、ここがポイントです。 !Sub
の後ろがリストになっています。
リストの一つ目の要素を見ると MyBucket${Suffix}
となっており、唐突にパラメータ ${Suffix}
が使われています。
じゃあこれはどこで定義したのよ、というとそれは二つ目の要素、すなわちすぐ下の行にあります。
Suffix: !If [IsProd, "", !Sub "-${AWS::AccountId}"]
組み込み関数 Fn::If
がいますが、使い方は何のことはない、三項演算子そのものです。
!If [条件(true か false になる変数), true の場合に採用する値, false の場合に採用する値]
■参考 Fn::If
今回の場合ですと、「 IsProd
が true なら ""
(空文字)を、 false なら -012345678901
」となります。
そして、この結果がパラメータ Suffix
の値として MyBucket${Suffix}
に代入されるわけですね。
ドキュメントではこの書き方をマッピングと呼んでいるみたいです。わかるようなわからんような…。
何はともあれ、状況設定に挙げた項目 ↓ がすべて満たされたのでした。
- S3 バケットのテンプレートを作る
- 本番環境(1つ)、開発環境(複数)をひとつの CFn テンプレートで定義する
- 本番環境のバケット名は MyBucket とする
- 開発環境のバケット名は MyBucket-012345678901 とする
- ※ 012345678901 は開発者の AWS アカウント ID
まとめ
CFn も意外と(?)柔軟にイイ感じに書ける。