CloudFormation道 ~Part2~
はじめに
CloudFormation道~Part1~では、CloudFormationの概要を解説しました。今回は簡単なインフラ構成を用いてCloudFormationのテンプレートの記載方法や、リソースの作成、変更、削除の解説をしていきます。
テンプレート
テンプレート形式
CloudFormationのテンプレートの記載形式として、JSONとYAML形式が使用することができます。私はテンプレートにコメントが記載できることなどを考慮してYAML形式をお勧めします。(JSONは一から記述するものではないと筆者は思っております。笑)
テンプレートの要素
テンプレートの雛形です。
---
AWSTemplateFormatVersion: "2010-09-09"
Description:
String
Metadata:
template metadata
Parameters:
set of parameters
Rules:
set of rules
Mappings:
set of mappings
Conditions:
set of conditions
Transform:
set of transforms
Resources:
set of resources
Outputs:
set of outputs
- AWSTemplateFormationVersion
CloudFormationのテンプレートのバージョンを表します。最新テンプレートのバージョンは"2010-09-09"となっており、テンプレートの一番最初に記載します。 - Description
テンプレートの詳細情報や説明を記載します。主にどんなリソースを作成するのか簡単に記載することが多いです。 - Metadata
テンプレートに関する追加情報を記載します。私はAWS::CloudFormation::Interfaceといったメタデータキーを使用することで、テンプレート内のパラメータの順番を制御していました。 - Parameters
テンプレートで使用する変数や値をスタック作成前にユーザに入力を求めることで、カスタム値を利用することができます。入力したパラメータの値はテンプレート内で参照することができます。 - Mappings
Key:valueでマッピングされた入力データを定義します。アカウントごとやリージョンごとに使用する値を分けたい時に使用します。 - Conditions
条件名と条件判断内容を記載します。のちに記載するResourcesセクションで使用し、例えば本番環境のみ作成したいリソースがあった場合に、parametersで入力した環境情報を利用することで、本番環境のみリソースを作成することができます。 - Transform
サーバレスアプリケーションや定型コンテンツを挿入するための、マクロを指定します。AWS SAMでよく使用されるパラメータです。 - Resources
CloudFormationのテンプレートで必須のパラメータになっており、構築したいリソースについての情報を記載していきます。 - Outputs
スタック構築後にAWS CloudFormationから出力させたい(URLやDNS名など)値を記載していきます。
テンプレートの作成、スタックの作成/更新/削除
構築するインフラ構成図
今回CloudFormationで作成していく簡単なインフラ構成図です。VPC内にPublic subnetを構築し、public subnet内にEC2インスタンスを構築します。構築したEC2インスタンスにSSHでログインするまでをゴールとしたいので、EC2にアタッチするセキュリティグループを作成します。また、インターネットと通信するためにInternetGatawayを構築し、VPCにアタッチします。
構築するリソース
インフラ構成図から今回の構築に必要なリソースを洗い出します。
- VPC
- subnet
- internetgateway
- routetable
- securitygroup
- ec2
- iam
今回はこれらのリソースを「ネットワーク」、「セキュリティグループ」、「EC2」と分けてテンプレートを作成していきました。スタックの依存関係も上記の順番になるようにしています。
テンプレート作成
- create-network-resouce.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create VPC Subnet RouteTable InternetGateWay"
Mappings:
NetWork:
CidrBlock:
VPC: "10.0.0.0/16"
PublicSubnet1a: "10.0.0.0/24"
InternetGateway: "0.0.0.0/0"
Tag:
Name:
VPC: "MyVPC"
PublicSubnet1a: "MyPublicSubnet1a"
RouteTable: "MyRouteTable"
InternetGateway: "MyInternetGateway"
Resources:
###################################################################
# VPC Counfiguration
###################################################################
myVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [ NetWork, CidrBlock, VPC ]
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, VPC ]
###################################################################
# Subnet Counfiguration
###################################################################
PublicSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: 'AWS::Region'
VpcId: !Ref myVPC
CidrBlock: !FindInMap [ NetWork, CidrBlock, PublicSubnet1a ]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, PublicSubnet1a ]
###################################################################
# Create InternetGateWay
###################################################################
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, InternetGateway ]
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref myVPC
InternetGatewayId: !Ref MyInternetGateway
###################################################################
# RouteTable Counfiguration
###################################################################
# Routetable
RouteTableforPublicSubnet:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref myVPC
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, RouteTable ]
PublicSubnet1aRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1a
RouteTableId: !Ref RouteTableforPublicSubnet
# Routing rule
Route01:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableforPublicSubnet
DestinationCidrBlock: !FindInMap [ NetWork, CidrBlock, InternetGateway ]
GatewayId: !Ref MyInternetGateway
Outputs:
MyVPCID:
Description: "MyVPC LogicalID"
Value: !Ref myVPC
Export:
Name: !Sub ${AWS::StackName}-VPCID
MyVPCCidrBlock:
Description: "MyVPC CidrBlock"
Value: !GetAtt myVPC.CidrBlock
Export:
Name: !Sub ${AWS::StackName}-VPCCidrBlock
PublicSubnet1aID:
Description: "PublicSubnet 1a LogicalID"
Value: !Ref PublicSubnet1a
Export:
Name: !Sub ${AWS::StackName}-Pub1aID
- create-security-group.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create SecurityGroup"
Mappings:
SecurityGroup:
ForEC2:
GroupName: "testsecuritygroup"
GroupDescription: "Test Security Group"
Tag:
Name:
SecurityGroup: "EC2SecurityGroup"
Resources:
###################################################################
# SecurityGroup Counfiguration
###################################################################
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !FindInMap [ SecurityGroup, ForEC2, GroupName]
GroupDescription: !FindInMap [ SecurityGroup, ForEC2, GroupDescription]
VpcId: !ImportValue create-network-resource-VPCID
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, SecurityGroup]
SGBaseIngress01:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt EC2SecurityGroup.GroupId
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Outputs:
EC2SecurityGroupID:
Description: "EC2SecutiryGroup LogicalID"
Value: !Ref EC2SecurityGroup
Export:
Name: !Sub ${AWS::StackName}-EC2SecurityGroup
- create-ec2.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create EC2"
Parameters:
InstanceTypeParameter:
Type: String
Default: t2.micro
Mappings:
EC2:
ap-northeast-1:
ImageId: "ami-09ebacdc178ae23b7"
KeyPair:
Name: "instanceaccess"
Tag:
Name:
EC2: "TestEC2"
Resources:
###################################################################
# EC2 Counfiguration
###################################################################
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
KeyName: !FindInMap [ EC2, KeyPair, Name ]
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: false
VolumeType: gp2
VolumeSize: 8
DisableApiTermination: false
ImageId: !FindInMap [ EC2, !Ref 'AWS::Region', ImageId ]
InstanceType: !Ref InstanceTypeParameter
Monitoring: false
Tenancy: default
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: "0"
SubnetId: !ImportValue create-network-resource-Pub1aID
GroupSet:
- !ImportValue create-secutiry-group-EC2SecurityGroup
IamInstanceProfile: !Ref MyInstanceProfile
Tags:
- Key: Name
Value: !FindInMap [ Tag, Name, EC2 ]
###################################################################
# IAM Counfiguration
###################################################################
MyInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref EC2forIAMRole
EC2forIAMRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "root"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "*"
Resource: "*"
Outputs:
EC2InstanceID:
Description: "EC2Instance LogicalID"
Value: !Ref MyEC2Instance
Export:
Name: !Sub ${AWS::StackName}-EC2ID
テンプレートファイルをS3に配置
テンプレートファイルを作成したら、S3に配置します。awscliからs3 cpコマンドを実行し、ローカルにあるテンプレートファイルをS3に転送します。
$ aws s3 cp create-ec2.yaml s3://my-devops/cloudformation/
upload: ./create-ec2.yaml to s3://my-devops/cloudformation/create-ec2.yaml
$ aws s3 cp create-network-resource.yaml s3://my-devops/cloudformation/
upload: ./create-network-resource.yaml to s3://my-devops/cloudformation/create-network-resource.yaml
$ aws s3 cp create-security-group.yaml s3://my-devops/cloudformation/
upload: ./create-security-group.yaml to s3://my-devops/cloudformation/create-security-group.yaml
スタックの作成
S3にテンプレートをアップロードできたら、今回はマネジメントコンソール上からスタックの作成を行います。
-
CloudFormationのマネジメントコンソールにアクセスし、スタックの作成をクリックします
-
前提条件-テンプレートの準備は「テンプレートの準備完了」を選択します。テンプレートの指定は「Amazon S3 URL」を選択し、S3からテンプレートのオブジェクトURLをコピーアンドペーストします。
-
作成するスタックの名前、パラメータを入力し、次へをクリックします。
-
タグはスタックのリソースに含めることができるタグを定義することができます。また、アクセス許可はスタックを作成する時にIAMロールを指定することで適切な権限でリソースの作成/変更/削除を行うことができます。今回は指定せず、IAMユーザの権限を利用します。スタックポリシー、ロールバック設定、通知オプション、スタックの作成オプションは設定せず、次へをクリックします。
-
入力内容を確認したら、スタックの作成をクリックします。
-
スタックの作成が終了すると、テンプレートで指定したリソースが作成されます。
-
出力タブには、テンプレート内でoutputで定義した項目が出力されています。出力されたエクスポート名はセキュリティグループのスタック作成や、EC2のスタック作成のテンプレートでインポートして利用することができます。
同じ容量でcreate-security-group.yamlとcreate-ec2.yamlのスタックを作成していきます。
SSHでサーバにアクセスする
EC2の作成が完了したら、EC2のマネジメントコンソール上から、パブリックIPv4アドレスを確認し、ローカルからSSHでログインします。
$ ssh -i ~/.ssh/instanceaccess.pem ec2-user@18.183.248.145
The authenticity of host '18.183.248.145 (18.183.248.145)' can't be established.
ECDSA key fingerprint is SHA256:d8C8qmLYykhwbrjpC2WvXb5Qe9LHLJd+A+K5UvjjxIM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '18.183.248.145' (ECDSA) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-10-0-0-122 ~]$
無事作成したEC2インスタンスにログインすることができました。
変更セットの作成
今回は作成したEC2インスタンスのインスタンスタイプをt2.micro->t3.microにへ変更します。
作成したスタックを選択し、更新をクリックします。
現在のテンプレートを選択します。
instancetypeparameterをt2.micro->t3.microに変更します。
変更セットを確認して、OKなら更新をクリックする。
EC2インスタンスのマネジメントコンソールからインスタンスタイプがt3.microに変更されたことを確認する。
スタックの削除
削除するスタックを選択し、削除をクリックする。
終わりに
今回はCloudFormationのテンプレートの解説や、実際にスタックを作成/更新/削除する流れを説明しました。テンプレートも使いこなせるようになると、1ファイルで環境ごとに使いまわせたり、メンテナンス性が向上するのでたくさんテンプレートを書いてみてください。
Discussion