はじめに
CloudFormationを使用するとインフラ環境をコードとしてテンプレート化できるので、開発環境をサクッと構築することができます。
そこで、キャッチアップがてら、個人開発環境をCloudformationを使用してミニマムな構成で作ってみたいと思います!
今回構築する環境は↓のような環境。
ざっくり要件はこんな感じに設定しました。
【EC2】
- OS : AmzonLinux2
- 踏み台サーバはsshのみ許可
- 踏み台サーバとNatGatewayへのEIPの割り当てはテンプレートファイルに記載し自動で割り当て。
【RDS】
- エンジン : MySQL 5.7
- マルチAZ構成
- サブネットグループは手動で設定
- パラメータグループは自動割り当て
※この辺は各自のやりたい要件に合わせて柔軟にテンプレートファイルを変更してみてください!
私は基本的に環境構築用にしか使用しないので、何も指定せずデフォルト設定のまま行いました。
実装
スタック分割
AWS公式BlackBelt
にもある通り、テンプレートファイルは膨大な記述量になりがちです。
そこで、可読性が下がるのを防ぐためにも、スタックはレイヤーごとに分割するほうが望ましいとされています。
これに則って、今回は、Network , Security, Application
という3つのスタックに分割して行いました。
(それぞれテンプレートファイルは、network.yml
, security.yml
, application.yml
を使用)
詳しくは↓AWS公式BlackBelt
のスライド85ページ目・86ページ目を参照
テンプレートファイル作成
今回、作成したテンプレートファイルはご自由にコピーして使っていただいて構いません!
各自に合わせて入力する必要がある箇所は、テンプレートファイルにXXXXX
で記載しているので、そこだけ自分用に変えていただけたらと思います。
■ network.yml
InternetGateway, NatGateway, VPC, Subnet, RouteTableなどを記載
AWSTemplateFormatVersion: 2010-09-09
Description: Personal Development Environment CloudFormation
Parameters:
AvailabilityZone:
Type: AWS::EC2::AvailabilityZone::Name
Default: "ap-northeast-1a"
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
DevelopVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: DevelopVPC
# ------------------------------------------------------------#
# Internet Gateway
# ------------------------------------------------------------#
DevelopIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: DevelopIGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref DevelopVPC
InternetGatewayId: !Ref DevelopIGW
# ------------------------------------------------------------#
# Public Subnet Route Table
# ------------------------------------------------------------#
PublicRouteTable:
Type: AWS::EC2::RouteTable
# DependsOn:特定のリソースが他のリソースに続けて作成されるように指定
DependsOn: AttachGateway
Properties:
VpcId: !Ref DevelopVPC
Tags:
- Key: Name
Value: PublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref DevelopIGW
# ------------------------------------------------------------#
# Private Subnet Route Table
# ------------------------------------------------------------#
PrivateRouteTable01:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId: !Ref DevelopVPC
Tags:
- Key: Name
Value: PrivateRouteTable01
PrivateRoute01:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PrivateRouteTable01
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref DevelopNat
# PrivateSubnet02に関連づける
# RDSにインターネットから何かインストールするときに使用
PrivateRouteTable02:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId: !Ref DevelopVPC
Tags:
- Key: Name
Value: PrivateRouteTable02
PrivateRoute02:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PrivateRouteTable02
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref DevelopNat
# ------------------------------------------------------------#
# Pubulic Subnet 01
# ------------------------------------------------------------#
PublicSubnet01:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
AvailabilityZone: !Ref AvailabilityZone
VpcId: !Ref DevelopVPC
CidrBlock: 10.0.10.0/24
Tags:
- Key: Name
Value: PublicSubnet01
PublicRouteTableAssocName:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref PublicRouteTable
# ------------------------------------------------------------#
# Private Subnet 01
# ------------------------------------------------------------#
PrivateSubnet01:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
AvailabilityZone: !Ref AvailabilityZone
VpcId: !Ref DevelopVPC
CidrBlock: 10.0.20.0/24
Tags:
- Key: Name
Value: PrivateSubnet01
PrivateRouteTable01AssocName:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref PrivateRouteTable01
# ------------------------------------------------------------#
# Private Subnet 02
# ------------------------------------------------------------#
PrivateSubnet02:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
AvailabilityZone: !Ref AvailabilityZone
VpcId: !Ref DevelopVPC
CidrBlock: 10.0.30.0/24
Tags:
- Key: Name
Value: PrivateSubnet02
PrivateRouteTable02AssocName:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet02
RouteTableId: !Ref PrivateRouteTable02
# ------------------------------------------------------------#
# Private Subnet 03
# RDSのSubnetGroup用のサブネット
# ------------------------------------------------------------#
PrivateSubnet03:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
AvailabilityZone: "ap-northeast-1c"
VpcId: !Ref DevelopVPC
CidrBlock: 10.0.40.0/24
Tags:
- Key: Name
Value: PrivateSubnet03
# ------------------------------------------------------------#
# Nat Gateway
# ------------------------------------------------------------#
DevelopNat:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt DevelopNatEIP.AllocationId
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: DevelopNat
DevelopNatEIP:
DependsOn: AttachGateway
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: DevelopNatEIP
Outputs:
DevelopVPC:
Value: !Ref DevelopVPC
Export:
Name: DevelopVPCName
DevelopVPCCidr:
Value: !GetAtt DevelopVPC.CidrBlock
Export:
Name: DevelopVPCCidrBlock
PuclicSubnet01:
Value: !Ref PublicSubnet01
Export:
Name: PublicSubnet01Name
PrivateSubnet01:
Value: !Ref PrivateSubnet01
Export:
Name: PrivateSubnet01Name
PrivateSubnet03:
Value: !Ref PrivateSubnet03
Export:
Name: PrivateSubnet03Name
■ security.yml
セキュリティグループなどを記載
AWSTemplateFormatVersion: 2010-09-09
Description: Personal Development Environment CloudFormation
Resources:
# ------------------------------------------------------------#
# Bastion Security Group
# ------------------------------------------------------------#
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Bastion-SG
GroupDescription: Bastion-SG
VpcId: !ImportValue DevelopVPCName
SecurityGroupIngress:
IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: 22
ToPort: 22
Tags:
- Key: Name
Value: Bastion-SG
# ------------------------------------------------------------#
# Web Security Group
# ------------------------------------------------------------#
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Web-SG
GroupDescription: Web-SG
VpcId: !ImportValue DevelopVPCName
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: !ImportValue DevelopVPCCidrBlock
FromPort: 22
ToPort: 22
- IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: 80
ToPort: 80
- IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: 443
ToPort: 443
Tags:
- Key: Name
Value: Web-SG
# ------------------------------------------------------------#
# Application Database Security Group
# ------------------------------------------------------------#
ApplicationDatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: DB-SG
GroupDescription: DB-SG
VpcId: !ImportValue DevelopVPCName
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: !ImportValue DevelopVPCCidrBlock
Tags:
- Key: Name
Value: DevelopApplicationDB-SG
Outputs:
BastionSecurityGroup:
Value: !Ref BastionSecurityGroup
Export:
Name: BastionSecurityGroupName
WebSecurityGroup:
Value: !Ref WebSecurityGroup
Export:
Name: WebSecurityGroupName
ApplicationDBSecurityGroup:
Value: !Ref ApplicationDatabaseSecurityGroup
Export:
Name: ApplicationDBSecurityGroupName
■ application.yml
EC2, RDS, ElasticIPなどを記載
AWSTemplateFormatVersion: 2010-09-09
Parameters:
KeyName:
Description: Development Kye Pair
Type: "AWS::EC2::KeyPair::KeyName"
Default: XXXXX
EC2AMIId:
Description: AMI ID
Type : String
Default: ami-07b4f72c4c356c19d
# RDSパラメータ
UserName:
Description: RDS User Name
Type : String
Default: XXXXX
UserPassword:
Description: RDS User Password
Type : String
Default: XXXXX
DBIdentifier:
Description: DBInstanceIdentifier
Type : String
Default: XXXXX
Resources:
# ------------------------------------------------------------#
# Bastion EC2 Instance
# ------------------------------------------------------------#
BastionEC2Instance:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref EC2AMIId
InstanceType: t2.micro
Monitoring: false
SecurityGroupIds:
- !ImportValue BastionSecurityGroupName
SubnetId: !ImportValue PublicSubnet01Name
Tags:
- Key: Name
Value: Bastion
# ------------------------------------------------------------#
# Web EC2 Instance
# ------------------------------------------------------------#
WebEC2Instance:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref EC2AMIId
InstanceType: t2.micro
Monitoring: false
SecurityGroupIds:
- !ImportValue WebSecurityGroupName
SubnetId: !ImportValue PrivateSubnet01Name
Tags:
- Key: Name
Value: Develop-Web01
# ------------------------------------------------------------#
# Elastic IP Address
# ------------------------------------------------------------#
BastionEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref BastionEC2Instance
ElasticIPAssociate:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt BastionEIP.AllocationId
InstanceId: !Ref BastionEC2Instance
# ------------------------------------------------------------#
# RDS
# ------------------------------------------------------------#
ApplicationDatabase:
Type: AWS::RDS::DBInstance
Properties:
Engine: MySQL
EngineVersion: 5.7
DBInstanceClass: db.t2.micro
AllocatedStorage: 10
StorageType: gp2
MasterUsername: !Ref UserName
MasterUserPassword: !Ref UserPassword
DBName: DevelopRDS
DBInstanceIdentifier: !Ref DBIdentifier
VPCSecurityGroups:
- !ImportValue ApplicationDBSecurityGroupName
DBSubnetGroupName: !Ref ApplicationDatabaseSubnetGroup
MultiAZ: "true"
Tags:
- Key: Name
Value: DevelopApplicationDB
ApplicationDatabaseSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Application Database Subnet Group
SubnetIds:
- !ImportValue PrivateSubnet01Name
- !ImportValue PrivateSubnet03Name
Tags:
- Key: Name
Value: DevelopDBSubnetGroup
スタックを作成する順番が重要!
テンプレートファイルが作成できたら、あとはスタックを作成するだけです。
※ここで注意点!
スタックは、Netowork
→ Security
→ Application
の順番でStackを作成しないといけない!
いったい、なぜか?
security.yml
はnetwork.yml
のOutputsを参照しているから
application.yml
はsecurity.yml
のOutputsを参照しているから
つまり、Network
の出力をSecurity
で受け取り、Security
の出力をApplication
が受け取っているのである
完全に呪文ですね。
テンプレートファイルの中身をみると感覚的に理解できるかと思います。
スタックの作成は、Cloudformationのマネジメントコンソール
> スタックの作成
> テンプレートファイルのアップロード
で作成したXXXXX.yml
を選択すればOK
確認
全てのスタックが作成完了したところで、本当に環境構築できているのか確認してみます。
※事前に、キーペアを作成し、ローカルの~/.ssh
配下に設置しておきます。
1.scp
コマンドでローカルマシンから踏み台サーバへ鍵を送信
$ scp ~/.ssh/秘密鍵.pem ec2-user@踏み台サーバのEIP:~/.ssh
2./.ssh/config
にssh情報を追記
#ssh接続先情報を追記
Host bastion
#接続先サーバーのIPアドレス
Hostname 踏み台サーバのEIP
#接続するサーバーのユーザー名
User ec2-user
Port 22
IdentityFile /Users/ユーザー名/.ssh/秘密鍵.pem
Host Web
#接続先サーバーのIPアドレス
Hostname プライベートIP
User ec2-user
Port 22
IdentityFile /Users/ユーザー名/.ssh/秘密鍵.pem
#開発サーバーは踏み台サーバーをプロキシして接続する
ProxyCommand ssh -W %h:%p bastion
3.踏み台サーバーへssh接続
$ ssh -i ~/.ssh/秘密鍵.pem ec2-user@踏み台サーバのEIP
4.開発Webサーバーへssh接続
$ ssh -i ~/.ssh/秘密鍵.pem ec2-user@開発WebサーバのプライベートIP
5.開発WebサーバにMySQLクライアントのインストール
開発Webサーバにssh接続できたら、DBへ接続するため、MySQLクライアントを↓の手順でインストール
調べてみると・・・
無事成功🙌
$ mysql --version
=> mysql Ver 14.14 Distrib 5.7.37, for Linux (x86_64) using EditLine wrapper
6.RDSのmysqlに接続
$ mysql -u マスターユーザー名 -pマスターパスワード -h RDSエンドポイント
無事、踏み台サーバからWebサーバ、DBまで全ての接続を確認することができた!
さいごに
Cloudfomationを使用するとインフラのコード化でき、テンプレートとして登録できる点はやはり素晴らしいと感じました!
やってみて感じたことは、application.yml
内でも起点となる踏み台サーバとそれ以外のリソースで分割したほうが良いなと感じました。
踏み台サーバの部分だけ別のスタックで実装しておき、踏み台サーバ上にテンプレートファイルを設置し、踏み台サーバー以外の環境をaws cliでコマンドで操作(↓のような感じに)できたほうが便利だと感じたので次回はそのような構成でチャレンジしたいと思います
$ aws cloudfomation deploy --template-file XXXXX --stack-name XXXXX
また、ELBの設置やEC2のAutoScaling、本番環境とのVPCピアリングなど、実際のWebアプリケーションを駆動させるインフラ環境として必要な部分もやってみたいと思います。
参考