CloudFormationでネストしたスタックを作る

Sat Jan 2, 2021 - aws
Sat Jan 9, 2021

前提知識

まず、スタックやネストといった言葉の意味がよくわかっていない人は、たぶんCloudFormationの概要がつかめてないと思いますので、最初はこちらを参照ください。

AWS CloudFormationってなんだ

ネストしたスタックの概要

CloudFormationで個別に、Aスタック、Bスタック、Cスタックと作成して、これらをまとめて一つのアーキテクチャとして作成する場合は、AスタックからBスタックを参照するためにImportOutputs関数を使うのですが、ある程度の規模のスタックになるのこの参照関係が実に面倒になってきます。

特にスタックが相互に参照している場合です。例えば、AスタックはBスタックの値を参照していてかつBスタックもAスタックの値も参照している。このような場合、waitなどを使って、スタックの作成の順番をこちらで指定する必要があります。

さらに、相互参照のスタックを削除するのも結構面倒です。削除する場合はAスタックの参照を一度、消して、それからBスタックをアップデートして、Aスタックを削除、そして、Bスタックを削除するなんていう面倒なことになります。

ここまでくると流石にまとめてスタックを作りたくなりますよね。そこで使うのがネストです。

親スタックの配下に子スタックを作成し、親スタックを作成すればまとめて子スタックが作成され、削除する場合も親スタックを削除すれば子スタックもまとめて削除できるようにします。

ディレクトリ構造

まずは、最初にディレクトリの構造を説明します。正直、ディレクトリの構造は人それぞれなので、あくまでも参考程度にしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.
├── Makefile
└── nested-stack
    ├── architect-bucket
    │   └── s3.yml
    ├── master.yml
    ├── package
    │   └── packaged.yml
    ├── s3-1.yml
    └── s3-2.yml

Makefile

CloudFormationではaws cliのコマンドを何回も実行することが多いです。コマンド実行の度に、このような長ったらしいコマンドを実行するのは実に面倒です。

1
aws cloudformation create-stack --stack-name myteststack --template-body file://sampletemplate.json --parameters ParameterKey=KeyPairName,ParameterValue=TestKey ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2

なので、予めMakefileに、これらのコマンドをいい感じにまとめておきます。そうすれば、make createみたいな感じで実行するだけよくなります。

nested-stack

これはCloudFormationで展開するサービスを入れているフォルダです。このフォルダ内に作りたいスタックを入れてます。

  • master.yml (親テンプレート)

  • s3-1.yml (子テンプレート)

  • s3-2.yml (子テンプレート)

architect-bucket

ネストしたスタックを作る手順はpackagedeployとなるのですが、packageコマンドを実行するとローカルにあるテンプレートをS3にアップロードするので、これはそのS3バケット作成用です。

mbコマンドでS3を作成してもいいですが、まぁ、これは好みですね。

package

packageコマンドのきちんとした説明は、公式を参照してください。ここでは、ざっくり説明します。

律儀にネストしたスタックを作成しようする以下のような過程が必要になります。

  • ①子テンプレートをS3にアップロード

  • ②アップロードされた子テンプレートのURLを確認

  • ③親テンプレートに先ほど確認した子テンプレートのURLをProperties.TemplateURLに追加

この面倒な手順を簡単にするためにpackageコマンドのオプションを使います。

コマンドが長くなるのでMakefileに書きます。make packageでコマンドを実行すると子テンプレートがs3 bucketにアップされます。ここでは、architect-bucketですね。

1
2
3
4
5
6
7
# Makefile
# package 
.PHONY: package
package:
	@aws cloudformation package --template-file ./nested-stack/master.yml \
	--s3-bucket architect-bucket \
	--output-template-file ./nested-stack/package/packaged.yml

そして、親テンプレートであるmaster.ymlProperties.TemplateURLが子テンプレートのURLに書き換わったテンプレートpackaged.ymlが作成されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 # master.yml
AWSTemplateFormatVersion: 2010-09-09
Description: master
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./s3-1.yml ← ローカルの相対パスがS3のURLに変換される
      Parameters:
        Name: !Ref Name
  S3Bucket2:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./s3-2.yml ← ローカルの相対パスがS3のURLに変換される
      Parameters:
        S3Name1: !GetAtt S3Bucket1.Outputs.OutputS3Name1

実際に試してないので、こんな感じになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 # packaged.yml
AWSTemplateFormatVersion: 2010-09-09
Description: master
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::CloudFormation::Stack
    Properties:
      https://s3.ap-northeast-1.amazonaws.com/nested-stack/なんちゃらなんちゃら.template
      Parameters:
        Name: !Ref Name
  S3Bucket2:
    Type: AWS::CloudFormation::Stack
    Properties:
      https://s3.ap-northeast-1.amazonaws.com/nested-stack/なんちゃらなんちゃら.template
      Parameters:
        S3Name1: !GetAtt S3Bucket1.Outputs.OutputS3Name1

これでデプロイする準備はできました。先ほど作成されたpackaged.ymlを使ってmake deployを実行してみます。

deployコマンドが親スタック作成とネストした子スタックの作成までまるっとやってくれるので大変便利です。

1
2
3
4
5
6
7
# Makefile
# deploy 
.PHONY: deploy
deploy:
	@aws cloudformation deploy \
	--template-file nested-stack/package/packaged.yml \
	--stack-name nested-stack \

親スタックの値をネストした子スタック渡す方法

親スタックの値をネストした子スタック渡す方法ですが、これも好みだとは思うのですが、僕は親スタックに直接、値をセットするのは後々違うプロジェクトでmaster.yml自体を編集することになるので、なんかイヤなので、Makefileに値をセットしてdeployコマンドのオプション--parameter-overridesで値を渡すようにしています。

1
2
3
4
5
6
7
8
9
# Makefile
Name := bokunonikki
# deploy 
.PHONY: deploy
deploy:
	@aws cloudformation deploy \
	--template-file nested-stack/package/packaged.yml \
	--stack-name nested-stack \
	--parameter-overrides Name=${Name} 

Nameの値をまず最初に親スタックに渡します。master.ymlはこのようにします。

このようにすることで、ネストされた子スタックにs3-1.yml値を渡すことができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 # master.yml
AWSTemplateFormatVersion: 2010-09-09
Description: master
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./s3-1.yml
      Parameters:
        Name: !Ref Name

s3-1.ymlは以下のようにすれば親スタックの値を受け取ることができます。この場合は、bokunonikki-1というS3バケットが作成されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
AWSTemplateFormatVersion: 2010-09-09
Description: nested-stack-s3-1
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Sub
        - ${ Name }-1
        - { Name: !Ref Name }

ネストした子スタックどうしで値を渡す

ネストした子スタックどうしで値を渡すにはOutputs関数を使います。

ここでは、試しにAスタックの値をBスタックに渡す場合を考えてみます。

まず、Aスタックの値をOutputs関数を使ってBスタックでも参照できるようにします。この場合は、OutputS3Name1という名前で参照できるようになってます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
AWSTemplateFormatVersion: 2010-09-09
Description: nested-stack-s3-1
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Sub
        - ${ Name }-1
        - { Name: !Ref Name }
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
Outputs:
  OutputS3Name1:
    Value: !Ref S3Bucket1

次に受け取る側のBスタックです。値を受け取る側はまず、親であるテンプレートに!GetAtt関数を使って参照できるようにします。この場合は、ParametersS3Name1という値に、S3Bucket1のOutputsのOutputS3Name1をセットするという意味です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
AWSTemplateFormatVersion: 2010-09-09
Description: master
Parameters:
  Name:
    Type: String
Resources:
  S3Bucket1:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./s3-1.yml
      Parameters:
        Name: !Ref Name
  S3Bucket2:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./s3-2.yml
      Parameters:
        S3Name1: !GetAtt S3Bucket1.Outputs.OutputS3Name1 ← これ

Bスタック自体は、特別何をする必要もありません。普通にParametersをセットすればいいです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
AWSTemplateFormatVersion: 2010-09-09
Description: nested-stack-s3-2
Parameters:
  S3Name1:
    Type: String
Resources:
  S3Bucket2:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Sub
        - ${ S3Name1 }-2
        - { S3Name1: !Ref S3Name1 }
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

以上で終わりです。これらのコマンドは実際に実行したわけではないので、コピペ実行はできないのでお気をつけあれ。

ただ、これらの大まかな知識があればあとは公式サイトを見ればだいだいできるようになるはずです。

参考になった本

Makeの本は、これ一冊あればことたりると思います。何かしたくなったら調べるくらいのレベルでも結構役に立ちます。

この本は実際にawsのサービスを実際に手を動かして作っているので、awsの概要を掴むには最適です。

See Also