前回のまでのおさらい
前回のSeculity-Layer編では通信のポートを制御するセキュリティグループを作成した。最後となる今回はEC2など主要な構成をテンプレートで構築していく。
概要
テンプレートと言われる構築環境をコード化させたファイルに記載し、スタックと呼ばれる作業を行うことでGUI上で作成した状況と同じ環境が自動的に作成される。1つのテンプレートに全ての環境を記載することも可能だが、AWSからは各レイヤー別に分けることがベストプラクティスと言われている。
今回は以下3つのLayerに分けて作成する。
- Network Layer
- Security Layer
- Application Layer ←今回はここ!
クロススタック参照
各レイヤー別にテンプレートを作成し1つの環境構築を行うためには、テンプレート間の互換性が必要になる。そのためにはクロススタック参照
を使用して他レイヤーの値を参照できるようにコードに'組込関数'と呼ばれる関数を活用していく作業がポイント。
構成図
Application-Layer
このレイヤーでEC2、RDS、ELBのテンプレートを作成していく。
VPC関連を作成した時と同様に、Parametersで引用する数値を予め設定しておく
2022年5月に新機能として、CloudFormationでkeypairを作成できるようになった。
今回は旧式のやり方で進めているが、自身でも準備が出来たら投稿予定!
参考記事 : CloudFormationでキーペアの作成/削除が可能になった
Parameters
AWSTemplateFormatVersion: 2010-09-09
Description: Creation of EC2 Related(for EC2,RDS,ALB)
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Live03 Prefix
Parameters:
- PJPrefix
- Label:
default: EC2 Configuration
Parameters:
- KeyName
- EC2InstanceName
- EC2InstanceAMI
- EC2InstanceType
- EC2InstanceVolumeType
- EC2InstanceVolumeSize
- Label:
default: RDS Configuration
Parameters:
- DBInstanceName #スタックの名前
- MySQLMajorVersion #MysqlのメジャーVer
- MySQLMinorVersion #MysqlのマイナーVer
- DBInstanceClass #インスタンスクラス
- DBInstanceStorageSize #ストレージサイズ
- DBInstanceStorageType #ストレージタイプ
- DBName #データベースの名前
- DBMasterUserName #マスターユーザー名
- DBPassword #パスワード
- Label:
default: InternetALB Configuration
Parameters:
- InternetALBName
ParameterLabels:
# for EC2
InternetALBName:
default: InternetALBName
KeyName:
default: KeyPairName
EC2InstanceName:
default: EC2 Name
EC2InstanceAMI:
default: EC2 AMI
EC2InstanceType:
default: EC2 InstanceType
EC2InstanceVolumeType:
default: EC2 VolumeType
EC2InstanceVolumeSize:
default: EC2 VolumeSize
# for RDS
DBInstanceName:
default: DBInstanceName
MySQLMajorVersion:
default: MySQLMajorVersion
MySQLMinorVersion:
default: MySQLMinorVersion
DBInstanceClass:
default: DBInstanceClass
DBInstanceStorageSize:
default: DBInstanceStorageSize
DBInstanceStorageType:
default: DBInstanceStorageType
DBName:
default: DBName
DBMasterUserName:
default: DBUserName
DBPassword:
default: DBPassword
#---------------------------------------------------
# input Parameters
#---------------------------------------------------
Parameters:
PJPrefix:
Type: String
Default: live03
# EC2
KeyName:
Description: input EC2 Keyname
Type: AWS::EC2::KeyPair::KeyName
Default: live03-keypair
EC2InstanceName:
Type: String
Default: web
EC2InstanceAMI:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
EC2InstanceType:
Type: String
Default: t2.micro
EC2InstanceVolumeType:
Type: String
Default: gp2
EC2InstanceVolumeSize:
Type: String
Default: "30"
# RDS
DBInstanceName:
Type: String
Default: RDS
MySQLMajorVersion:
Type: String
Default: "5.7"
AllowedValues: [ "5.5", "5.6", "5.7" ]
MySQLMinorVersion:
Type: String
Default: "22"
DBInstanceClass:
Type: String
Default: db.t3.micro
DBInstanceStorageSize:
Type: String
Default: "20"
DBInstanceStorageType:
Type: String
Default: gp2
DBName:
Type: String
Default: db
DBMasterUserName:
Type: String
Default: dbuser # 自身の設定した値を挿入
NoEcho: true
MinLength: 1
MaxLength: 16
AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
ConstraintDescription: must begin with a letter and contain only alphanumeric characters.
DBPassword:
Default: dbpassword # 自身の設定した値を挿入
NoEcho: true
Type: String
MinLength: 8
MaxLength: 41
AllowedPattern: "[a-zA-Z0-9]*"
ConstraintDescription: must contain only alphanumeric characters.
# ALB
InternetALBName:
Type: String
Default: Cfn
# S3
S3BucketName:
Type: String
Default: cfn202705
Resources:
# ------------------------------------------------------------#
# IAM Role for EC2
# ------------------------------------------------------------#
EC2IAMRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub "${PJPrefix}-${EC2InstanceName}-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
- "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
EC2InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- Ref: EC2IAMRole
InstanceProfileName: !Sub "${PJPrefix}-${EC2InstanceName}-profile"
EC2
ALBを使用しELB経由で負荷分散させる仕様にするためEC2は2台(1a /1c)作成する。
各IDはParametersから組込関数を用いて引用。
Resources:
#---------------------------------------------------
# EC2の作成(public-subnet内)
#---------------------------------------------------
CfnEC21a:
Type: AWS::EC2::Instance
Properties:
SubnetId: !ImportValue nstack-PublicSubnet1a
ImageId: !Ref EC2InstanceAMI
InstanceType: !Ref EC2InstanceType
KeyName: !Ref KeyName
InstanceInitiatedShutdownBehavior: stop
Tenancy: default
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: !Ref EC2InstanceVolumeType
VolumeSize: !Ref EC2InstanceVolumeSize
SecurityGroupIds:
- !ImportValue sstack-Sg-WEB
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-${EC2InstanceName}-1a"
CfnEC21c:
Type: AWS::EC2::Instance
Properties:
SubnetId: !ImportValue nstack-PublicSubnet1c
ImageId: !Ref EC2InstanceAMI
InstanceType: !Ref EC2InstanceType
KeyName: !Ref KeyName
InstanceInitiatedShutdownBehavior: stop
Tenancy: default
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: !Ref EC2InstanceVolumeType
VolumeSize: !Ref EC2InstanceVolumeSize
SecurityGroupIds:
- !ImportValue sstack-Sg-WEB
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-${EC2InstanceName}-1c"
RDS
AZ-1aとAZ-1cに設置しMulti-AZ構成とする。
CfnRDS:
Type: "AWS::RDS::DBInstance"
Properties:
Engine: MySQL
EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}"
Port: 3306
DBInstanceClass: !Ref DBInstanceClass
AllocatedStorage: !Ref DBInstanceStorageSize
StorageType: !Ref DBInstanceStorageType
DBName: !Ref DBName
MasterUsername: !Ref DBMasterUserName
MasterUserPassword: !Ref DBPassword
DBSubnetGroupName: !Ref DBSubnetGroup
PubliclyAccessible: false
PreferredBackupWindow: "18:00-18:30"
PreferredMaintenanceWindow: "sat:19:00-sat:19:30"
AutoMinorVersionUpgrade: false
VPCSecurityGroups:
- !ImportValue (スタック名)-Sg-RDS
CopyTagsToSnapshot: true
BackupRetentionPeriod: 7
Tags:
- Key: "Name"
Value: !Ref DBInstanceName
DeletionPolicy: "Delete"
# RDS SubnetGroupの作成
DBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: DBSubnetGroupName
DBSubnetGroupDescription: create for RDS
SubnetIds:
- !ImportValue (スタック名)-PrivateSubnet1a
- !ImportValue (スタック名)-PrivateSubnet1c
ALB
SubnetはPublic1a/1cに設定し、最終的にNginx経由でロードバランサーに接続するので80番ポートを指定。
CfnALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: CfnALB
Scheme: internet-facing
SecurityGroups:
- !ImportValue (スタック名)-Sg-ALB
Subnets:
- !ImportValue (スタック名)-PublicSubnet1a
- !ImportValue (スタック名)-PublicSubnet1c
Tags:
- Key: Name
Value: CfnALB
# ALBのターゲットグループの指定
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: ALBTargetGroup
Port: 80
Protocol: HTTP
Targets:
- Id:
Ref: CfnEC21a
Port: 80
- Id:
Ref: CfnEC21c
Port: 80
VpcId: !ImportValue (スタック名)-VPCID
# ALBのリスナーの指定
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn:
!Ref ALBTargetGroup
LoadBalancerArn:
!Ref CfnALB
Port: 80
Protocol: HTTP
S3
PublicAccessBlockConfiguration
の設定で全てtrueにするとパブリックアクセスが全てブロックされる。
SampleS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
VersioningConfiguration:
Status: Enabled
これで3つのレイヤーに分けて作成したテンプレートの作成が完了となる。
終わりに
慣れるまでは、テンプレート同士の整合性の調整やエラー解消にかなり手間取ってしまった。
ここからAWS CLIを使用してターミナルから操作、完了できれば尚よしだが、今は一旦ここをゴールとする。
参考にさせて頂いた記事
- CloudFormationのテンプレートを分割して作成してみた。
- クロススタック参照でテンプレートを分割管理する
- AWS テンプレートリファレンス
- CloudFormationを使って最新のAmazonLinuxのAMIでEC2を構築する
エラー事例
テンプレートを構築していく中で、多くのエラーに遭遇。
備忘録を兼ねて事象と解決方法をメモとして残しておく。
テンプレート作成を行う段階で遭遇したエラーと解決方法[主に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のバケット名に大文字を使用していたため(大文字禁止)