この記事は、株式会社日立システムズ Advent Calendar 2022の22日目の記事です。
前回の第2回では、実際に簡単なAWS環境を基にCloudFormationテンプレートを作成し、内容を説明してきました。
しかし、作成したテンプレートにはCloudFormationのメリットを享受するために改善の余地がありました。
そこで第3回の今回は第2回で作成したテンプレートを改善するとともにネストスタックの考え方についてまとめていきたいと思います。
1. 【再掲】作成するAWS環境
2. CloudFormationテンプレートの改善
前回では、以下のリソースを1つのテンプレートで作成しました。
- VPC
- InternetGateway
- Subnet
- RouteTable
- EC2
- SecurityGroup
- IAMRole
- KeyPair
しかし、CloudFormationではある程度のリソースのまとまり(機能のまとまり等)ごとにテンプレートを分割し作成することが推奨されます。なぜなら分割することで、AWS環境ごとに各テンプレートを組み合わせて作成しやすくなるからです。つまり、テンプレートの再利用がしやすくなるということです。
そこで今回は以下のリソースごとにテンプレートを作成していきます。
テンプレート名(スタック名) | 記載するリソース |
---|---|
rootstack | networkstack、ec2stack |
networkstack | VPC、InternetGateway、Subnet、RouteTable |
ec2stack | EC2、SecurityGroup、IAMRole、KeyPair |
VPC、InternetGateway等のネットワークリソースをまとめた「networkstack」というテンプレートと、EC2とEC2に付随するリソースをまとめた「ec2stack」というテンプレートを作成します。
また、テンプレ―トを分割するにあたり新たに「rootstack」というテンプレートを作成します。
rootstackは、networkstackやec2stackの親にあたるテンプレートで、Resourcesとして
各スタックを記述します。このテンプレートがあることでAWS環境ごとに各テンプレートを組み合わせて作成しやすくなります。
各テンプレート(スタック)間の関係性は以下の通りです。
なお、今回のような構成を「ネストスタック構成」と言います。
スタックを作成する際は
①子にあたる各テンプレートをS3バケットにアップロードする。
②CloudFormationの「スタックの作成」-「テンプレートの指定」で親にあたるテンプレートファイルを指定する。
③スタック名や各パラメータを入力し、スタックを作成する。
という手順になります。
そして、作成した各テンプレ―トは以下の通りになります。
AWSTemplateFormatVersion: "2010-09-09"
Description: "Root Stack"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: CommonParameter
Parameters:
- TemplateBucket
- PjName
- Env
- Label:
default: NetworkStackParameter
Parameters:
- VpcCidr
- PublicSubnetCidr
- Label:
default: Ec2StackParameter
Parameters:
- VolumeSize01
- ImageId01
- InstanceType01
- SourceIp
# --------------------------------------------------------------------------------------------------------------
# Input Parameters
# --------------------------------------------------------------------------------------------------------------
Parameters:
TemplateBucket:
Type: String
Default: qiita-dev-s3
Description: S3Bucket Name for Template
PjName:
Type: String
Default: Qiita
Description: Project Name
Env:
Type: String
Default: dev
Description: Environment Name
VpcCidr:
Type: String
Default: 192.168.0.0/16
Description: Cidr of Vpc
PublicSubnetCidr:
Type: String
Default: 192.168.0.0/24
Description: Cidr of PublicSubnet
VolumeSize01:
Type: String
Default: 8
Description: VolumeSize of PublicSubnetEc2
ImageId01:
Type: AWS::EC2::Image::Id
Default: ami-0de5311b2a443fb89
Description: ImageId of PublicSubnetEc2
InstanceType01:
Type: String
Default: t3.micro
Description: InstanceType of PublicSubnetEc2
SourceIp:
Type: String
Default: 10.0.0.0/8
Description: SourceIp of Internet
Resources:
# network stack
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${TemplateBucket}.s3-${AWS::Region}.amazonaws.com/Templates/01_networkstack.yaml
Parameters:
PjName: !Ref PjName
Env: !Ref Env
VpcCidr: !Ref VpcCidr
PublicSubnetCidr: !Ref PublicSubnetCidr
# ec2 stack
Ec2Stack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${TemplateBucket}.s3-${AWS::Region}.amazonaws.com/Templates/02_ec2stack.yaml
Parameters:
PjName: !Ref PjName
Env: !Ref Env
VolumeSize01: !Ref VolumeSize01
ImageId01: !Ref ImageId01
InstanceType01: !Ref InstanceType01
Vpc: !GetAtt NetworkStack.Outputs.VpcId
PublicSubnet: !GetAtt NetworkStack.Outputs.PublicSubnetId
SourceIp: !Ref SourceIp
AWSTemplateFormatVersion: "2010-09-09"
Description: "Network Stack"
# --------------------------------------------------------------------------------------------------------------
# Input Parameters
# --------------------------------------------------------------------------------------------------------------
Parameters:
PjName:
Type: String
Description: Project Name
Env:
Type: String
Description: Environment Name
VpcCidr:
Type: String
Description: Cidr of Vpc
PublicSubnetCidr:
Type: String
Description: Cidr of PublicSubnet
Resources:
# --------------------------------------------------------------------------------------------------------------
# Title: VPC
# Description: VPC of Qiita-dev
# --------------------------------------------------------------------------------------------------------------
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-VPC
# --------------------------------------------------------------------------------------------------------------
# Title: InternetGateway
# Description: InternetGateway of Qiita-dev-VPC
# --------------------------------------------------------------------------------------------------------------
# InternetGateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-IGW
# Attach InternetGateway
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref Vpc
InternetGatewayId: !Ref InternetGateway
# --------------------------------------------------------------------------------------------------------------
# Title: Subnet
# Description: PublicSubnet of Qiita-dev-VPC
# --------------------------------------------------------------------------------------------------------------
# PublicSubnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: !Ref AWS::Region
CidrBlock: !Ref PublicSubnetCidr
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-PublicSN
VpcId: !Ref Vpc
# --------------------------------------------------------------------------------------------------------------
# Title: RouteTable
# Description: RouteTable,Route,SubnetRouteTableAssociation of Qiita-dev-PublicSN
# --------------------------------------------------------------------------------------------------------------
# RouteTable of PublicSubnet
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-PublicSN-RT
VpcId: !Ref Vpc
# Route of PublicSubnetRouteTable
PublicNatgwSubnet01Route:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# Subnet Route Assocciation of PublicSubnetRouteTable
PublicSubnetSRTAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicSubnetRouteTable
SubnetId: !Ref PublicSubnet
# --------------------------------------------------------------------------------------------------------------
# Output Parameters
# --------------------------------------------------------------------------------------------------------------
Outputs:
VpcId:
Value: !Ref Vpc
PublicSubnetId:
Value: !Ref PublicSubnet
AWSTemplateFormatVersion: "2010-09-09"
Description: "EC2 Stack"
# --------------------------------------------------------------------------------------------------------------
# Input Parameters
# --------------------------------------------------------------------------------------------------------------
Parameters:
PjName:
Type: String
Description: Project Name
Env:
Type: String
Description: Environment Name
VolumeSize01:
Type: String
Description: VolumeSize of PublicSubnetEc2
ImageId01:
Type: AWS::EC2::Image::Id
Description: ImageId of PublicSubnetEc2
InstanceType01:
Type: String
Description: InstanceType of PublicSubnetEc2
Vpc:
Type: AWS::EC2::VPC::Id
Description: VpcId
PublicSubnet:
Type: AWS::EC2::Subnet::Id
Description: SubnetId of PublicSubnet
SourceIp:
Type: String
Description: SourceIp of Internet
Resources:
# --------------------------------------------------------------------------------------------------------------
# Title: EC2
# Description: EC2 of Qiita-dev-PublicSN
# --------------------------------------------------------------------------------------------------------------
# EC2 of PublicSubnet
PublicSubnetEc2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: !Ref VolumeSize01
EbsOptimized: true
ImageId: !Ref ImageId01
InstanceType: !Ref InstanceType01
KeyName: !Ref PublicSubnetEc2Key
SecurityGroupIds:
- !Ref Ec2SecurityGroup
SubnetId: !Ref PublicSubnet
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-EC2
IamInstanceProfile: !Ref Ec2InstanceProfile
# --------------------------------------------------------------------------------------------------------------
# Title: SecurityGroup
# Description: SecurityGroup of Qiita-dev-EC2
# --------------------------------------------------------------------------------------------------------------
# SecurityGroup of PublicSubnetEc2
Ec2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of PublicSubnet EC2 Instance
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref SourceIp
Tags:
- Key: Name
Value: !Sub ${PjName}-${Env}-EC2-SG
VpcId: !Ref Vpc
# --------------------------------------------------------------------------------------------------------------
# Title: IAM Role
# Description: IAM Role,InstanceProfile of Qiita-dev-EC2
# --------------------------------------------------------------------------------------------------------------
# IAM Role of PublicSubnetEc2
Ec2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${PjName}-${Env}-EC2-Role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# InstanceProfile of PublicSubnetEc2
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2Role
# --------------------------------------------------------------------------------------------------------------
# Title: Key Pair
# Description: Key Pair of Qiita-dev-EC2
# --------------------------------------------------------------------------------------------------------------
# KeyPair of PublicSubnetEc2
PublicSubnetEc2Key:
Type: AWS::EC2::KeyPair
Properties:
KeyName: !Sub ${PjName}-${Env}-EC2-Key
networkstackやec2stackに関する記述に大きな変更はないため、
主にrootstackに関して説明します。
rootstackのParametersではnetworkstackやec2stackで使用するパラメータを記述しています。
Parameters:
TemplateBucket:
Type: String
Default: qiita-dev-s3
Description: S3Bucket Name for Template
PjName:
Type: String
Default: Qiita
Description: Project Name
Env:
Type: String
Default: dev
Description: Environment Name
VpcCidr:
Type: String
Default: 192.168.0.0/16
Description: Cidr of Vpc
PublicSubnetCidr:
Type: String
Default: 192.168.0.0/24
Description: Cidr of PublicSubnet
VolumeSize01:
Type: String
Default: 8
Description: VolumeSize of PublicSubnetEc2
ImageId01:
Type: AWS::EC2::Image::Id
Default: ami-0de5311b2a443fb89
Description: ImageId of PublicSubnetEc2
InstanceType01:
Type: String
Default: t3.micro
Description: InstanceType of PublicSubnetEc2
SourceIp:
Type: String
Default: 10.0.0.0/8
Description: SourceIp of Internet
Resourcesではnetworkstackやec2stackに関する記述をしています。
Resources:
# network stack
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${TemplateBucket}.s3-${AWS::Region}.amazonaws.com/Templates/01_networkstack.yaml
Parameters:
PjName: !Ref PjName
Env: !Ref Env
VpcCidr: !Ref VpcCidr
PublicSubnetCidr: !Ref PublicSubnetCidr
# ec2 stack
Ec2Stack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${TemplateBucket}.s3-${AWS::Region}.amazonaws.com/Templates/02_ec2stack.yaml
Parameters:
PjName: !Ref PjName
Env: !Ref Env
VolumeSize01: !Ref VolumeSize01
ImageId01: !Ref ImageId01
InstanceType01: !Ref InstanceType01
Vpc: !GetAtt NetworkStack.Outputs.VpcId
PublicSubnet: !GetAtt NetworkStack.Outputs.PublicSubnetId
SourceIp: !Ref SourceIp
TypeはAWS::CloudFormation::Stackで、PropertiesのParametersには各スタックで利用する値を指定します。ここで指定した値は各スタックでParametersとして利用できます。
また、TemplateURLではテンプレートをS3に格納した先のURLを記述します。
ここでec2stackに着目します。
ec2stackではEC2の作成にあたり、networkstackで作成したPublicSubnetのSubnetIdを参照する必要があります。
またSecurityGroupの作成にあたり、networkstackで作成したVPCのVpcIdを参照する必要があります。
そこでnetworkstackではOutputsセクションでec2stackに渡したいVpcIdやPublicSubnetのSubnetIdを定義します。
Outputs:
VpcId:
Value: !Ref Vpc
PublicSubnetId:
Value: !Ref PublicSubnet
そして、rootstack内のec2stackのParametersでは、リソースの属性値を返すFn::GetAtt関数を使用し「NetworkStack.Outputs.(LogicalID)」のように記述します。
このようにすることでec2stack側でVpcIdやPublicSubnetのSubnetIdを参照することができるようになります。
まとめ
今回はCloudFormationのメリットを活かすために、ある程度のリソースのまとまり(機能のまとまり等)ごとにテンプレートを作成しました。
また、ネストスタックの考え方や別スタックに値を出力させる方法等をまとめました。
今回紹介した内容はCloudFormationテンプレートを作成する上で基本的な考え方になりますので、是非実践してみてください。
※本記事は2022/12/13時点での内容になります。
最新の情報に関しては公式ドキュメントを参照ください。