AWS CloudFormationとは
CloudFormationはYamlもしくはJSONを使用してAWSリソースを自動で構築するAWSサービスでAWS環境をコードでテンプレート化しておくことで、効率化、ミスの軽減、複製の簡易化などが可能になります。
概要
テンプレートと言われる構築環境をコード化させたファイルに記載し、スタックと呼ばれる作業を行うことでGUI上で作成した状況と同じ環境が自動的に作成される。1つのテンプレートに全ての環境を記載することも可能だが、AWSからは各レイヤー別に分けることがベストプラクティスと言われている。
今回は以下3つのLayerに分けて作成する。
- Network Layer ←今回はここ!
- Security Layer
- Application Layer
クロススタック参照
各レイヤー別にテンプレートを作成し1つの環境構築を行うためには、テンプレート間の互換性が必要になる。そのためにはクロススタック参照
を使用して他レイヤーの値を参照できるようにコードに'組込関数'と呼ばれる関数を活用していく作業がポイント。
構成図
Network Layer
まずはベースとなるVPC、Subnet(Private/Public),InternetGateway、RouteTable、VPCEndpointを作成する。
Parameters
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "PJPrefix"
Parameters:
- PJPrefix
- Label:
default: Network Layer Configuration
Parameters:
- AvailabilityZoneName
- VPCCIDR
- PublicSubnetACIDR
- PublicSubnetCCIDR
- PrivateSubnetACIDR
- PrivateSubnetCCIDR
- PublicSubnetName
- PrivateSubnetName
ParameterLabels:
AvailabilityZoneName:
default: AvailabilityZoneName
VPCCIDR:
default: VPC CIDR
PublicSubnetACIDR:
default: PublicSubnetA CIDR
PublicSubnetCCIDR:
default: PublicSubnetC CIDR
PrivateSubnetACIDR:
default: PrivateSubnetA CIDR
PrivateSubnetCCIDR:
default: PrivateSubnetC CIDR
PublicSubnetName:
default: PublicSubnetName
PrivateSubnetName:
default: PrivateSubnetName
#---------------------------------------------------
# input Parameters
#---------------------------------------------------
Parameters:
PJPrefix:
Type: String
Default:"-"
AvailabilityZoneName:
Description: input AvailabilityZone
Type: String
Default: ap-northeast-1
VPCCIDR:
Type: String
Default: "10.0.0.0/16"
PublicSubnetACIDR:
Type: String
Default: "10.0.10.0/24"
PublicSubnetCCIDR:
Type: String
Default: "10.0.20.0/24"
PrivateSubnetACIDR:
Type: String
Default: "10.0.30.0/24"
PrivateSubnetCCIDR:
Type: String
Default: "10.0.40.0/24"
PublicSubnetName:
Description: input PublicSubnet
Type: String
Default: PublicSubnet1
PrivateSubnetName:
Description: input PrivateSubnet
Type: String
Default: PrivateSubnet1
VPC
CidrBlockはParametersに設定したものをRef関数で反映させ、同様にValueにはSub関数を使用しプロジェクト名を反映させる。こうすることで、運用時の修正にはParametersの値を変更するだけで済みミスの防止と汎用性が向上する。
AWS公式:組込関数リファレンス
CfnVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-CfnVPC"
※ハードウェア専用インスタンスは今回使用しないのでテナンシーはdefault設定となる。
Subnet
SubnetはPublic/Privateを各2つずつ作成する。
TagsのValueについて今回はSub関数でParametersの値をテストで引用し後ろにa/cをつけてみる。
# Public-Subnetを2つ作成(1a/1c)
CfnPublicSubnet1a:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PublicSubnetACIDR
MapPublicIpOnLaunch: true
VpcId: !Ref CfnVPC
AvailabilityZone: !Sub "${AvailabilityZoneName}a"
Tags:
- Key: Name
Value: !Sub "${PublicSubnetName}a"
CfnPublicSubnet1c:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PublicSubnetCCIDR
MapPublicIpOnLaunch: true
VpcId: !Ref CfnVPC
AvailabilityZone: !Sub "${AvailabilityZoneName}c"
Tags:
- Key: Name
Value: !Sub "${PublicSubnetName}c"
# Private-Subnetを2つ作成(1a/1c)
CfnPrivateSubnet1a:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetACIDR
MapPublicIpOnLaunch: false
VpcId: !Ref CfnVPC
AvailabilityZone: !Sub "${AvailabilityZoneName}a"
Tags:
- Key: Name
Value: !Sub "${PrivateSubnetName}a"
CfnPrivateSubnet1c:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetCCIDR
MapPublicIpOnLaunch: false
VpcId: !Ref CfnVPC
AvailabilityZone: !Sub "${AvailabilityZoneName}c"
Tags:
- Key: Name
Value: !Sub "${PrivateSubnetName}c"
InternetGateway
VPCとInternetGatewayをAttachmentで関連付けを行う。
CfnInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-CfnIGW"
# 作成したIGWをVPCにAttachする。
CfnAttachCfInternetGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId : !Ref CfnInternetGateway
VpcId: !Ref CfnVPC
Route Table
今回2つのルートテーブルを作成し、関連するIGW、Subnet(Public,Private)に紐付けていく。
- RouteTable For Public ( InternetGateway接続用 )
- RouteTable For Private ( VPCEndpoint接続用 )
CfnRouteTableForPublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref CfnVPC
Tags:
- Key: Name
Value: !Sub "${PublicSubnetName}a"
CfnRouteTableForPrivate:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref CfnVPC
Tags:
- Key: Name
Value: !Sub "${PrivateSubnetName}a"
# Route先にIGWを定義
CfnPublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref CfnRouteTableForPublic
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref CfnInternetGateway
# Public-Subnet1a/1cに関連付け
CfnAssocciateRouteTableForPublicSubnet1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref CfnRouteTableForPublicSubnet
SubnetId: !Ref CfnPublicSubnet1a
CfnAssocciateRouteTableForPublicSubnet1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref CfnRouteTableForPublicSubnet
SubnetId: !Ref CfnPublicSubnet1c
# Private-Subnet1a/1cに関連付け
CfnAssocciateRouteTableForPrivateSubnet1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref CfnRouteTableForPrivate
SubnetId: !Ref CfnPrivateSubnet1a
CfnAssocciateRouteTableForPrivateSubnet1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref CfnRouteTableForPrivate
SubnetId: !Ref CfnPrivateSubnet1c
VPC Endpoint
VPCEndpointを経由してVPC外に設置するS3と接続できるように事前に設定しておく。
VPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref CfnRouteTableForPrivate
ServiceName: com.amazonaws.ap-northeast-1.s3
VpcEndpointType: Gateway
VpcId: !Ref CfnVPC
Output
クロススタック参照を行うため、後ほど構築するSeculity/Applicationの各Layerで値を引用できるようにOutputsでExport値を定義しておく。また同じテンプレートを使用し異なるスタックを使用することを想定しExportのNameにはSub関数で作成するスタックの名前が反映する仕様とした。
Outputs:
StackVPC:
Description: VPC's ID
Value: !Ref CfnVPC
Export:
Name: !Sub "${AWS::StackName}-VPCID"
StackPublicSubnet1a:
Description: Public Subnet1a's ID
Value: !Ref CfnPublicSubnet1a
Export:
Name: !Sub "${AWS::StackName}-PublicSubnet1a"
StackPublicSubnet1c:
Description: Public Subnet1c's ID
Value: !Ref CfnPublicSubnet1c
Export:
Name: !Sub "${AWS::StackName}-PublicSubnet1c"
StackPrivateSubnet1a:
Description: Private Subnet1a's ID
Value: !Ref CfnPrivateSubnet1a
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnet1a"
StackPrivateSubnet1c:
Description: Private Subnet1c's ID
Value: !Ref CfnPrivateSubnet1c
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnet1c"
Security-Layerに続く
続きはこちらから。
AWS CloudFormationのテンプレートをLayer別に作成してクロススタック参照を作ってみた。
その2 [ Security-Layer編 ]
エラー事例
テンプレートを構築していく中で、多くのエラーに遭遇。
備忘録を兼ねて事象と解決方法をメモとして残しておく。
テンプレート作成を行う段階で遭遇したエラーと解決方法[主にcfn-lintで事前に検知]
[事象1] cfn-lint E1001: Top level template section VPCEndpoint is not valid
[解決法] 作成したコードの深さの設定ミス:論理IDなどを設定するTOPの位置の深さに設定ミスがあり。
[事象2] cfn-lint E0000: Duplicate resource found "ParameterLabels"
[解決法] コード内容が重複していると検出されるエラー。1つ削除して解消させる。
[事象3] cfn-lint E2015: Default should be allowed by AllowedPattern
[解決法] インプットパラメーターで設定している値の表記ミス、正しく表記できていない。
[事象4] cfn-lint E1012: Ref CfnEC2 not found as a resource or parameter
[解決法] インプットパラメーターで設定している値の表記ミス、正しく表記できていない。
[事象5] Unresolved resource dependencies [ALBSecurityGroup] in the Outputs block of the template
[解決法] !Ref関数で指定している引用元の論ID名に相違がある。
[事象6] No export named sstack-PrivateSubnet1a found. Rollback requested by user
[解決法] クロススタックで参照している値のtypoミス
[事象7] Bucket name should not contain uppercase characters
[解決法] Paraetersに記載しているS3のバケット名に大文字を使用していたため(大文字禁止)