#はじめに
この記事はLIFULL Advent Calendar2018その2の22日目の記事です。
AWSでインフラ構築をしたことはあるけど、CloudFormationという単語はなんとなく避けておりました。
ですが、意を決して取り組んでみると思いの外簡単・便利だったので、とりあえず言葉だけ知っていて試してみたいと思っている方向けに、簡単に実行できるチュートリアルを作りました。
※テンプレートの説明は反響があれば別記事で書こうと思います。
#CloudFormationで構築する環境
今回、CloudFormation(CFNと略すらしいです)を使って構築するインフラ構成は下図のようなシンプルなものにしました。
#準備
まず、AWSをcliで実行するためのインストールを行います。
$ sudo easy_install pip
$ sudo pip install awscli
$ aws --version
次に、AWSコンソール上でアクセスキーを発行し、下記の通り登録を行います。
$ aws configure --profile cfn
AWS Access Key ID [None]: 発行したアクセスキー
AWS Secret Access Key [None]: 発行したシークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]:
また、インスタンスを作成する際に必要なキーペアをsampleという名前で作成してダウンロードしておきます。
#実行手順
テンプレートファイルは、ページ下部のものをコピーしてください。(行数がすごいですが殆どサブネット関連の定義の繰り返しだったりします)
下記のコマンドでローカルからcfnを実行します。
※file://のパスはテンプレートを配置した場所によって異なります
$ ls -al
total 24
-rw-r--r-- 1 shodak shodak 10170 12 22 17:59 sample-template.yml
$ aws cloudformation create-stack --stack-name sample --template-body file://sample-template.yml --profile cfn
下記のリンクから実際にスタックが実行されているところが見れると思います。
https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks
構成図の通りにできているかと思います。
試しにbastionサーバーとapplicationサーバーにsshログインしてみます。
※面倒なので.ssh/configに下記の設定を追加しました。
Host sample-bastion
User ec2-user
HostName 18.182.92.135
Port 22
Identityfile ~/.ssh/sample.pem
Host sample-application
User ec2-user
HostName 172.31.254.253
Port 22
Identityfile ~/.ssh/sample.pem
ProxyCommand ssh -W %h:%p sample-bastion
$ mv Downloads/sample.pem ~/.ssh/
# 鍵の権限変更を忘れずに
$ chmod 400 ~/.ssh/sample.pem
$ ssh sample-bastion
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
15 package(s) needed for security, out of 16 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-31-0-65 ~]$
bastionサーバーにログインできたので、applicationサーバーも試します。
$ ssh sample-application
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-31-254-103 ~]$ curl http://google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
applicationサーバーにもログインでき、外部にも疎通していることが確認できました。
一通り確認したらstackを削除しましょう。
stackを削除することで、cloudformationによって生成されたリソースが全て削除されるため、お試しで環境を作りたい時に撤収が捗ります。
#今回実行したサンプルテンプレート
AWSTemplateFormatVersion: 2010-09-09
Description: Sample-template
Resources:
VpcTemplate:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.31.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value: sample
BastionEIP:
Type: AWS::EC2::EIP
Properties:
InstanceId:
Ref: BastionInstance
Domain:
Ref: VpcTemplate
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain:
Ref: VpcTemplate
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: sample-igw
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatGatewayEIP
- AllocationId
SubnetId:
Ref: PublicSubnet1a
Tags:
- Key: Name
Value: sample-nat-gateway
VpcIgwAttach:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VpcTemplate
InternetGatewayId:
Ref: IGW
PublicSubnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VpcTemplate
CidrBlock: 172.31.0.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: public-subnet-1a
PublicSubnet1c:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VpcTemplate
CidrBlock: 172.31.1.0/24
AvailabilityZone: ap-northeast-1c
Tags:
- Key: Name
Value: public-subnet-1c
PrivateSubnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VpcTemplate
CidrBlock: 172.31.254.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: private-subnet-1a
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VpcTemplate
Tags:
- Key: Name
Value: public-igw
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VpcTemplate
Tags:
- Key: Name
Value: private-nat
PublicSubnetRouteTableAssociationA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet1a
RouteTableId:
Ref: PublicSubnetRouteTable
PublicSubnetRouteTableAssociationC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet1c
RouteTableId:
Ref: PublicSubnetRouteTable
PrivateSubnetRouteTableAssociationA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PrivateSubnet1a
RouteTableId:
Ref: PrivateSubnetRouteTable
RouteTableIGWAssociation:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId:
Ref: PublicSubnetRouteTable
GatewayId:
Ref: IGW
RouteTableNatGatewayAssociation:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId:
Ref: PrivateSubnetRouteTable
NatGatewayId:
Ref: NatGateway
AclPublic:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: VpcTemplate
Tags:
- Key: Name
Value: public
AclPrivate:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: VpcTemplate
Tags:
- Key: Name
Value: private
AclPublicInSsh:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: false
Protocol: 6
PortRange:
From: 22
To: 22
RuleAction: allow
RuleNumber: 100
NetworkAclId:
Ref: AclPublic
AclPublicInHttp:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: false
Protocol: 6
PortRange:
From: 80
To: 80
RuleAction: allow
RuleNumber: 200
NetworkAclId:
Ref: AclPublic
AclPublicInHttps:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: false
Protocol: 6
PortRange:
From: 443
To: 443
RuleAction: allow
RuleNumber: 300
NetworkAclId:
Ref: AclPublic
AclPublicEphemeralIn:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: false
Protocol: 6
PortRange:
From: 1024
To: 65535
RuleAction: allow
RuleNumber: 400
NetworkAclId:
Ref: AclPublic
AclPublicIn:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 172.31.0.0/23
Egress: false
Protocol: -1
RuleAction: allow
RuleNumber: 500
NetworkAclId:
Ref: AclPublic
AclPublicOutSsh:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 22
To: 22
RuleAction: allow
RuleNumber: 100
NetworkAclId:
Ref: AclPublic
AclPublicOutHttp:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 80
To: 80
RuleAction: allow
RuleNumber: 200
NetworkAclId:
Ref: AclPublic
AclPublicOutHttps:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 443
To: 443
RuleAction: allow
RuleNumber: 300
NetworkAclId:
Ref: AclPublic
AclPublicEphemeralOut:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 1024
To: 65535
RuleAction: allow
RuleNumber: 400
NetworkAclId:
Ref: AclPublic
AclPublicOut:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 172.31.0.0/23
Egress: true
Protocol: -1
RuleAction: allow
RuleNumber: 500
NetworkAclId:
Ref: AclPublic
AclPrivateInSsh:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 172.31.0.0/23
Egress: false
Protocol: 6
PortRange:
From: 22
To: 22
RuleAction: allow
RuleNumber: 100
NetworkAclId:
Ref: AclPrivate
AclPrivateInHttp:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 172.31.0.0/23
Egress: false
Protocol: 6
PortRange:
From: 80
To: 80
RuleAction: allow
RuleNumber: 200
NetworkAclId:
Ref: AclPrivate
AclPrivateInHttps:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 172.31.0.0/23
Egress: false
Protocol: 6
PortRange:
From: 443
To: 443
RuleAction: allow
RuleNumber: 300
NetworkAclId:
Ref: AclPrivate
AclPrivateEphemeralIn:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: false
Protocol: 6
PortRange:
From: 1024
To: 65535
RuleAction: allow
RuleNumber: 400
NetworkAclId:
Ref: AclPrivate
AclPrivateOutHttp:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 80
To: 80
RuleAction: allow
RuleNumber: 200
NetworkAclId:
Ref: AclPrivate
AclPrivateOutHttps:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 443
To: 443
RuleAction: allow
RuleNumber: 300
NetworkAclId:
Ref: AclPrivate
AclPrivateEphemeralOut:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: 6
PortRange:
From: 1024
To: 65535
RuleAction: allow
RuleNumber: 400
NetworkAclId:
Ref: AclPrivate
PublicSubnet1aAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: PublicSubnet1a
NetworkAclId:
Ref: AclPublic
PublicSubnet1cAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: PublicSubnet1c
NetworkAclId:
Ref: AclPublic
PrivateSubnetAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: PrivateSubnet1a
NetworkAclId:
Ref: AclPrivate
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId:
Ref: VpcTemplate
GroupDescription: SG for Bastion
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: bastion-sg
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId:
Ref: VpcTemplate
GroupDescription: SG for Application
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId:
Ref: BastionSecurityGroup
Tags:
- Key: Name
Value: bastion-sg
BastionInstance:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId:
Ref: BastionInstanceLaunchTemplate
Version:
Fn::GetAtt: 'BastionInstanceLaunchTemplate.LatestVersionNumber'
SubnetId:
Ref: PublicSubnet1a
Tags:
- Key: Name
Value: bastion
ApplicationInstance:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId:
Ref: ApplicationInstanceLaunchTemplate
Version:
Fn::GetAtt: 'ApplicationInstanceLaunchTemplate.LatestVersionNumber'
SubnetId:
Ref: PrivateSubnet1a
Tags:
- Key: Name
Value: application
BastionInstanceLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
SecurityGroupIds:
- Ref: BastionSecurityGroup
InstanceInitiatedShutdownBehavior: stop
KeyName: sample
ImageId: ami-0a2de1c3b415889d2
Monitoring:
Enabled: false
CreditSpecification:
CpuCredits: standard
InstanceType: t2.micro
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 8
VolumeType: gp2
DeleteOnTermination: true
ApplicationInstanceLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
SecurityGroupIds:
- Ref: ApplicationSecurityGroup
InstanceInitiatedShutdownBehavior: stop
KeyName: sample
ImageId: ami-0a2de1c3b415889d2
Monitoring:
Enabled: false
CreditSpecification:
CpuCredits: standard
InstanceType: t2.micro
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 8
VolumeType: gp2
DeleteOnTermination: true
#最後に
cfnで何かのリソースを定義したい時は、公式のリファレンスをみつつ、具体例が欲しいところはググって調べています。
sampleのテンプレートではパラメータを活用していなかったり、NACLの定義のところが冗長だったりするので、もっと簡潔にかけるようにこれから努力したいと思います。