CloudFormation で名前だけが異なる同じ設定の S3 バケットを複数作りたい時に、一つのスタックの中で、バケット名のリストをループして一気に S3 バケットを作れないかと調べたところ、Fn::ForEach 関数を使えば実現できました。
ただし、この方法では、S3 バケット名にハイフンなどの記号を含んでいると、CloudFormation テンプレートの OutputKey に英数字以外の文字を指定できない制約に引っかかるため、テンプレートの書き方を少しだけ工夫する必要がありましたので、手順をメモしておきます。
Fn::ForEach で一気に S3 バケットを作成する CloudFormation テンプレートの全体
S3 バケットは、以下の 3 つの名前で作成します。
- test01-0001-0018
- test01-0001-0020
- test01-0001-0028
デフォルトの暗号化は SSE-KMS とし、特定の IAM ロールからのみ特定のアクションを許可するバケットポリシーも設定します。
ライフサイクルポリシーで 30 日後にオブジェクトを削除するようにします。また、コスト配分タグも設定しておきます。
以上の値は全てパラメーターにしておき、使い回しができるようにしておきました。
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::LanguageExtensions
Description: CloudFormation Template for Create S3 Bucket
Parameters:
BucketNames:
Type: CommaDelimitedList
Default: test01-0001-0018,test01-0001-0020,test01-0001-0028
AllowedIAMRoleARN:
Type: String
Description: Set IAM Role ARN for allowing to access this bucket
Default: "IAM ロールの ARN をここにセット"
KMSKeyARN:
Type: String
Description: Set KMS Key ARN for encryption bucket
Default: "SSE-KMS で暗号化するデフォルトの KMS キーの ARN をここにセット"
CostTagValue:
Type: String
Description: Set CostTag Value
Default: "コスト配分タグの値をここにセット"
Resources:
"Fn::ForEach::LoopBucketNames":
- LoopBucketName
- !Ref BucketNames
- "CreateBucket&{LoopBucketName}":
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID:
Ref: KMSKeyARN
BucketName:
Ref: LoopBucketName
LifecycleConfiguration:
Rules:
- ExpirationInDays: 30
Status: Enabled
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
Tags:
- Key: "コスト配分タグのキーをここにセット"
Value:
Ref: CostTagValue
"Fn::ForEach::LoopBucketPolicyNames":
- LoopBucketPolicyName
- !Ref BucketNames
- "CreatedBucketPolicyFor&{LoopBucketPolicyName}":
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: LoopBucketPolicyName
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- s3:ListBucket
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:GetObjectTagging
- s3:PutObjectTagging
Effect: Allow
Resource:
- !Sub "arn:aws:s3:::${LoopBucketPolicyName}"
- !Sub "arn:aws:s3:::${LoopBucketPolicyName}/*"
Principal:
AWS:
Ref: AllowedIAMRoleARN
テンプレートの解説
Fn::ForEach の有効化
CloudFormation テンプレートの中でループを使うためには、テンプレート内に以下の値を追記する必要があります。
Transform: AWS::LanguageExtensions
なお、これによりスタックの作成の際に以下のような確認画面が追加されました。
- AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。
- AWS CloudFormation によって IAM リソースがカスタム名で作成される場合があることを承認します。
- AWS CloudFormation によって、次の機能が要求される場合があることを承認します: CAPABILITY_AUTO_EXPAND
S3 バケット名のリストをパラメーターに設定
ここでは "BucketNames" という名前のパラメーターに、カンマ区切り文字列で作成したい S3 バケット名を定義しています。
Parameters:
BucketNames:
Type: CommaDelimitedList
Default: test01-0001-0018,test01-0001-0020,test01-0001-0028
リストからループでリストの値をセットして S3 バケットを作成
ここでは "LoopBucketName" という識別子の変数を設定しました。この変数には、上記で設定した "BucketNames" というリストからループして順に値がセットされていきます。
これを利用して、S3 バケット名に変数 "LoopBucketName" を指定することでリストにある名前で S3 バケットが作成されていく仕組みです。
Resources:
"Fn::ForEach::LoopBucketNames":
- LoopBucketName
- !Ref BucketNames
- "CreateBucket&{LoopBucketName}":
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID:
Ref: KMSKeyARN
BucketName:
Ref: LoopBucketName
ここでポイントとなるのは、リソースの OutputKey に英数字以外を指定することができないため、今回のようにリストの項目にハイフンを含んでいる場合に変数 "LoopBucketName" をそのまま ${LoopBucketName} と展開してしまうと、以下のようなエラーメッセージが表示されてスタックの作成に失敗します。
OutputKey 'CreateBuckettest01-0001-0018' should be alphanumeric.
このような場合、AWS 公式ドキュメントによると、&{} を使って変数を展開するよう書かれています。
Passing non-alphanumeric characters within the Collection for Fn::ForEach
This example uses the &{} syntax, which allows the non-alphanumeric characters (. and /) in the IP addresses to be passed within the Collection.
そこで、このテンプレートでは以下のように OutputKey の箇所を &{} で変数 "LoopBucketName" を展開するようにしています。
CreateBucket&{LoopBucketName}
あとは同じ仕組みでバケットポリシーも Fn::Each 関数でループして作成するようにしています。
Fn::ForEach 関数の使い方は AWS 公式サイトを参照してください。
スタックから作成された S3 バケットの確認
テンプレートが完成したら、CloudFormation からスタックを作成します。
リソースが作成できました。「論理 ID」を見るとバケット名からハイフンが取り除かれた文字列がセットされていることが分かります。
S3 バケットの方もテンプレートで設定した内容で作成できていました。
変更セットの作成(おまけ)
せっかくなのでスタックの変更セットを作成し、S3 バケットの設定を変えてみます。
ライフサイクルポリシーで 30 日でオブジェクトを削除する設定にしていますが、これを 180 日に変更してみます。
スタックで新規作成の時点のライフサイクルルールを確認します。
CloudFormation の変更セットを作成します。手順は割愛しますが、元のテンプレートの YAML の "LifecycleConfiguration" の箇所を修正し、アップロードしています。
LifecycleConfiguration:
Rules:
- ExpirationInDays: 180
変更セットが作成できたら、実行する前に差分を確認してみます。
差分が期待どおり日数のみ変更となっていることを確認したら、変更セットを実行します。
スタックから作成したリソースに変更がかかりました。S3 バケットの方を見てみます。
ライフサイクルルールの日数が変更になっていました。
CloudFormation を使うことで、今回のように同じ設定の S3 バケットを複数作るなどの定型の作業を楽に行えるようになりますので、私はもっと勉強して IaC やってるぜと言えるようにしたいと思います。
参考 URL
ガバクラ勢の Kubota さんも Fn::Each 関数を使って S3 バケットを 1,000 個(!?)作ってらっしゃいました。リストをパラメーターストアから呼び出す方法を使われています。