CFnのテンプレートを1つにしておくと可読性が悪くなったりなどはよく言われているのでNested Stackを使って分割するサンプルを書いてみた。
ネストされたスタック
CloudFormationの最上位のテンプレート内で、別のCloudFormationテンプレートをリソースとして定義することができる、というもの。
これによってテンプレートを分割できる。
試す
VPCの作成とEC2の作成を別テンプレートにして試す。
ディレクトリ構成は以下のようにしてみる。
直下の./master.yml
の中に./ec2/master.yml
と./vpc/master.yml
を呼び出すように定義する。
./ ┗ master.yml ec2/ ┗ master.yml vpc/ ┗ master.yml
各master.yml
の中身は以下のような感じにしている。
./master.yml
AWSTemplateFormatVersion: '2010-09-09' Resources: vpcStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: ./vpc/master.yml EC2Stack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: ./ec2/master.yml
TemplateURL
のところでネストするテンプレートを定義する。
通常この部分はアップロードする先のS3のURLを指定しなければならない。
./vpc/master.yml
AWSTemplateFormatVersion: 2010-09-09 Description: VPC Template Parameters: ProjectName: Type: String Default: 'sample-cfn' Description: Project name for tag Resources: # VPC VPC: Type: 'AWS::EC2::VPC' Properties: CidrBlock: 10.1.0.0/16 InstanceTenancy: default EnableDnsSupport: 'true' EnableDnsHostnames: 'false' Tags: - Key: Name Value: !Sub "${ProjectName}-vpc" # Inernet Gateway InternetGateway: Type: 'AWS::EC2::InternetGateway' Properties: Tags: - Key: Name Value: !Sub "${ProjectName}-igw" # DHCP Options DHCPOptions: Type: 'AWS::EC2::DHCPOptions' Properties: Tags: - Key: Name Value: !Sub "${ProjectName}-dopt" DomainName: ap-northeast-1.compute.internal DomainNameServers: - AmazonProvidedDNS # RouteTable RouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-rt" # NetworkACL NetworkACL: Type: 'AWS::EC2::NetworkAcl' Properties: VpcId: !Ref VPC # NetworkACL Entry NetworkAclEntryEgress: Type: 'AWS::EC2::NetworkAclEntry' Properties: CidrBlock: 0.0.0.0/0 Egress: 'true' Protocol: '-1' RuleAction: allow RuleNumber: '100' NetworkAclId: !Ref NetworkACL NetworkAclEntryIngress: Type: 'AWS::EC2::NetworkAclEntry' Properties: CidrBlock: 0.0.0.0/0 Protocol: '-1' RuleAction: allow RuleNumber: '100' NetworkAclId: !Ref NetworkACL # InternetGateway VPCGatewayAttachment: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # RouteTable Route: Type: 'AWS::EC2::Route' Properties: DestinationCidrBlock: 0.0.0.0/0 RouteTableId: !Ref RouteTable GatewayId: !Ref InternetGateway DependsOn: VPCGatewayAttachment # DHCP Options Association DHCPOptionsAssociation: Type: 'AWS::EC2::VPCDHCPOptionsAssociation' Properties: VpcId: !Ref VPC DhcpOptionsId: !Ref DHCPOptions # Subnet PublicSubnetA: Type: 'AWS::EC2::Subnet' Properties: CidrBlock: 10.1.1.0/24 AvailabilityZone: ap-northeast-1a VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-public-subnet-1a" SubnetRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: SubnetId: !Ref PublicSubnetA RouteTableId: !Ref RouteTable SubnetRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: SubnetId: !Ref PublicSubnetA NetworkAclId: !Ref NetworkACL ### Outputs Outputs: VPCId: Value: !Ref VPC Export: Name: !Sub "${ProjectName}-vpc" RouteTableId: Value: !Ref rt1 Export: Name: !Sub "${ProjectName}-rt" NetworkAclId: Value: !Ref NetworkACL Export: Name: !Sub "${ProjectName}-nacl" PublicSubnetA: Value: !Ref subnet1 Export: Name: !Sub "${ProjectName}-public-subnet-1a"
./ec2/master.yml
AWSTemplateFormatVersion: 2010-09-09 Description: Create EC2 Template Parameters: ProjectName: Type: String Default: 'sample-cfn' Description: Project name for tag Resources: EC2: Type: 'AWS::EC2::Instance' Properties: DisableApiTermination: 'false' InstanceInitiatedShutdownBehavior: stop ImageId: ami-00f9d04b3b3092052 InstanceType: t2.micro KeyName: ec2-key-pair-apn1 Monitoring: 'false' Tags: - Key: Name Value: !Sub "${ProjectName}-ec2" NetworkInterfaces: - DeleteOnTermination: 'true' Description: Primary network interface DeviceIndex: 0 SubnetId: "Fn::ImportValue": !Sub "${ProjectName}-public-subnet-1a" GroupSet: - !Ref SecurityGroup AssociatePublicIpAddress: 'true' SecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: 'Sample SG' VpcId: "Fn::ImportValue": !Sub "${ProjectName}-vpc" SecurityGroupIngress: Type: 'AWS::EC2::SecurityGroupIngress' Properties: GroupId: !Ref SecurityGroup IpProtocol: tcp FromPort: '22' ToPort: '22' CidrIp: 0.0.0.0/0 SecurityGroupEgress: Type: 'AWS::EC2::SecurityGroupEgress' Properties: GroupId: !Ref SecurityGroup IpProtocol: '-1' CidrIp: 0.0.0.0/0
実行
テンプレートファイルをアップロードするS3バケットを作成します。
% aws s3 mb s3://cfn-nested-stack
S3バケット作成後、下記コマンドでアップロードします。
% aws cloudformation package --template-file master.yaml \ --s3-bucket cfn-nested-stack \ --output-template-file master-package.yml
チェンジセットを作成します。
aws cloudformation execute-change-set \ --stack-name cfn-nested-stack \ --change-set-name cfn-nested-change-set
チェンジセットを実行します。
aws cloudformation execute-change-set \ --stack-name cfn-nested-stack \ --change-set-name cfn-nested-change-set
まとめ
ネストされたスタックをaws cliで扱うやり方について書いた。親テンプレートに定義するTemplateURL
は本来、子テンプレートが置いてあるs3のバケットURLを指定する必要があるが、ローカルのディレクトリを指定してaws cliを使ってパッケージングすると、子テンプレートをS3にアップロードした上でTemplateURL
をs3のURLに書き換えてくれるのでその点は便利だと思う。
ただ、テンプレートが肥大化すると結局変数の受け渡しが煩雑になる気はする。