はじめに
本記事でも何度か紹介しているAWS Black Belt Online Seminarですが、また新しいセミナーが公開されたようです。
3か月くらい前にもCloudFormationのセミナーが公開されていましたが、それだけCloudFormationの需要があるということなんでしょうね。
その6ではTransformを使用した他テンプレートのインクルードを試しましたが、今回はスタックをネスト構成にしてテンプレートを分割してみようと思います。
- 【前】CloudFormationをゼロから勉強する。(その7:変更セットとドリフト検出)
- 【次】CloudFormationをゼロから勉強する。(その9:クロススタックリファレンス)
- 【番外編】CloudFormationをゼロから勉強する。(番外編:AWS Perspective)
- 【番外編】CloudFormationをゼロから勉強する。(番外編:Former2)
ネストとは
イメージとしてはその6のTransformでのテンプレート分割と同じような方法となりますが、Transformのインクルードはベーステンプレートの一部を別ファイルにして呼び出すイメージなので、実行単位であるスタックとしては1スタックであるのに対して、ネストは他のスタックを呼び出すイメージとなるので、実行時の見え方は複数スタックとなります。
共通コンポーネント用スタックを作成して、個々のスタックから呼び出すようにするのがAWSのベストプラクティスとのことです。
・・・とはいえTransformのインクルードでも書き方次第でネストした場合と同じことができそうなのでいまいち区別がし辛いですが、ネストの場合はその6で書いたようなyamlの書き方の制約は無いので、Transformのインクルードはリソースの一部パラメータ(例えばタグ)を分割したりするのに使い、ネストはリソース自体を分割したりするのに使うといった区別が良いかと思います。
ネストの書式
ResourcesセクションにAWS::CloudFormation::Stackタイプのリソースを作成することでネストしたスタックを指定できます。
ネストしたファイル名を指定する必要があるため、PropertiesでTemplateURLの指定が必須となります。
また、S3バケットへのアップロードが必要となるため、Transformでのインクルードと同様、実行する前にpackageコマンドでURLの変換、S3バケットへのアップロードを行う必要があります。
Resources:
NestStack1:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./vpc.yaml
ネストしたスタックへの入出力
ネストのスタックは親子関係となるため、親スタックから子スタックを呼び出す構成になります。
入出力の関係は子スタックをプログラミング言語の関数として考えるとイメージが湧きやすいと思いますが、親スタックで子スタックからの入出力を受ける場合、子スタック(という関数)に引数を渡すのが入力で、子スタック(という関数)からの戻り値が出力となります。
子スタック側での入出力書式
これはネストしていない場合と同じくParametersセクションで上位からの入力を受けて、Outputsで結果を上位へ渡します。
親スタック側での入力書式
親スタックから子スタックへ値を入力するためにはネストするスタックのPropertiesにParametersを記載して子スタックに渡す値を記載する必要があります。
上述の例で言うと、親スタックから子スタック(という関数)へ引数を渡す方法が、PropertiesのParametersとなります。
Resources:
NestStack1: ★子スタック名
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./vpc.yaml
Parameters:
NestVPCRange: !Ref VPCRange
NestSubnetRange1: !Ref SubnetRange1
親スタック側での出力書式
子スタック(という関数)からの戻り値は!GetAtt組み込み関数で以下の書式で記載することで取得できます。
!GetAtt [子スタック名].Outputs.[子スタック側のOutputリソース名]
例えば親スタックで前項目の子スタックへの入力値指定例の設定、子スタックで以下のような出力設定を行ったとします。
Outputs:
NestSubnet1Id: ★Outputリソース名
Value: !Ref EC2Subnet1
その場合、子スタックの出力値(戻り値)を取得するためには以下の指定を行うことで取得できます。
!GetAtt NestStack1.Outputs.NestSubnet1Id
ネストスタック間の入出力まとめ
ネストスタック間の入出力について、AWSマネジメントコンソール画面からの入出力も含めたイメージ図を描いてみました。
青が子スタックとのやり取り、赤が自スタックでのやり取りとなります。
基本のイメージが分かれば、図のような複雑なスタック構成でも悩むことはないかと思いますが、注意点として、各スタック間の値の入出力は単純なネストの記述ではスタックを飛び越してやり取りできません。
そのため、例えば図中のNestStack2の出力値をNestStack3で使用したい場合は、まずNestStack1のOutputでNestStack2の戻り値をRoot Stackに渡し、次にRoot StackからNestStack3のParametersでNestStack3に値を引き渡す必要があります。
もしスタックを飛び越してやり取りしたい場合は、Outputセクションで値をExportすればやり取りすることが可能なようです。
※Exportする方法(Cross Stack Reference)は次回試そうと思います。
今回作成する構成
今回は以下のような構成を作ってみようと思います。
ルートスタック用テンプレートの作成
各子スタックで使用するVPCRange、SubnetRange1、SubnetRange2を読み込み、各スタックの入力に引き渡します。
また、vpc.yamlで作成するVPC IDとsubnet1.yamlで作成するSubnet1のIDはsubnet2.yamlとec2.yamlで使用するため、vpc.yamlの出力値をsubnet2.yamlとec2.yamlの入力値として!GetAttで指定するようにします。
AWSTemplateFormatVersion: 2010-09-09
Resources:
NestStack1:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./vpc.yaml
Parameters:
NestVPCRange: !Ref VPCRange
NestSubnetRange1: !Ref SubnetRange1
NestStack3:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./subnet2.yaml
Parameters:
NestVpcId: !GetAtt NestStack1.Outputs.NestVpcId
NestSubnetRange2: !Ref SubnetRange2
NestStack4:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./ec2.yaml
Parameters:
NestSubnetId: !GetAtt NestStack1.Outputs.NestSubnet1Id
Parameters:
VPCRange:
Type: String
Description: "VPC Subnet Range"
SubnetRange1:
Type: String
Description: "Subnet Range1"
SubnetRange2:
Type: String
Description: "Subnet Range2"
VPCスタック用テンプレートの作成
VPC Rangeと、子スタックとなるsubnet1.yamlのSubnet1 Rangeの情報をParametersセクションで受け取り、subnet1.yamlにSubnet1 Rangeの値を渡します。
また、Subnet1のIDは、ec2.yamlでも使用するため、subnet1.yamlの出力値をvpc.yamlの出力値としてルートスタックとなるcf.yamlに渡すようにします。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
NestVPCRange:
Type: String
NestSubnetRange1:
Type: String
Resources:
EC2VPC1:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: !Ref NestVPCRange
Tags:
- Key: "Name"
Value: "cf_VPC1"
NestStack2:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./subnet1.yaml
Parameters:
NestVpcId: !Ref EC2VPC1
NestSubnetRange1: !Ref NestSubnetRange1
Outputs:
NestVpcId:
Value: !Ref EC2VPC1
NestSubnet1Id:
Value: !GetAtt NestStack2.Outputs.NestSubnet1Id
Subnet1スタック用テンプレートの作成
親スタックとなるvpc.yamlからの値をParametersで受け、Subnet1のIDをOutputsで出力するようにします。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
NestVpcId:
Type: 'AWS::EC2::VPC::Id'
NestSubnetRange1:
Type: String
Resources:
EC2Subnet1:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref NestVpcId
CidrBlock: !Ref NestSubnetRange1
Tags:
- Key: "Name"
Value: "cf_Subnet1"
Outputs:
NestSubnet1Id:
Value: !Ref EC2Subnet1
Subnet2スタック用テンプレートの作成
subnet2.yamlは他スタックで使用する値は無いので、親スタックとなるcf.yamlからの値をParametersで受けるだけになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
NestVpcId:
Type: 'AWS::EC2::VPC::Id'
NestSubnetRange2:
Type: String
Resources:
EC2Subnet2:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref NestVpcId
CidrBlock: !Ref NestSubnetRange2
Tags:
- Key: "Name"
Value: "cf_Subnet2"
EC2スタック用テンプレートの作成
subnet1.yamlで出力したSubnet1のIDをParametersで読み込み、EC2インスタンスを作成します。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
NestSubnetId:
Type: 'AWS::EC2::Subnet::Id'
Resources:
EC2Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: ami-0cc75a8978fbbc969
InstanceType: t2.micro
KeyName: staging_key
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
SubnetId: !Ref NestSubnetId
DeviceIndex: 0
テンプレートの実行
ネスト化により、テンプレートが複数に分かれるため、その6と同様、packageとdeployコマンドでテンプレートの変換と実行を行います。
URLなどのテンプレートの変換は、ルートスタックで行えば、子スタックも自動的に変換とS3への転送が行われるため、ルートスタック用テンプレートのみ実行します。
aws cloudformation package --template-file cf.yaml --s3-bucket [S3バケット名] --output-template-file output.yaml
packageコマンド実行後、deployコマンドでS3バケットに転送したテンプレートを実行します。
aws cloudformation deploy --template-file output.yaml --stack-name stack-test --parameter-overrides VPCRange=172.24.0.0/16 SubnetRange1=172.24.0.0/24 SubnetRange2=172.24.1.0/24
CloudFormationのスタック画面で以下の様になれば成功です。
おわりに
スタックのネストは親子関係となるため、構造が分かりやすく、書き方とイメージを覚えてしまえば分かりやすいと感じました。
次も同じようなテンプレート分割方法となるクロススタック参照(Cross Stack Reference)を勉強してみようと思います。


