はじめに
AWSで環境構築したいけど、マネジメントコンソールぽちぽちは手順書の作成めんどくさいよね、ということで、CloudFormationを使用して、シンプルな構成のシステムを自動で構築してみました。
YAML(もしくはJSON)でテンプレートを作成するだけで、マネジメントコンソールをぽちぽちしなくても自動で環境を作ってくれて超便利です!
環境の自動化がなぜ重要か
- 環境構築での人為的ミスを減らせる→信頼性の向上
- 開発速度を上げることができる
- DevOps(開発と運用の一体化)とCI/CD(継続的デリバリー)での開発を実現できる
AWSにおける環境を自動化するための主なサービス
- Codeシリーズ(Git上のコードのコミット・実行・デプロイの自動化)
- Cloud Formation
- ECS(Dockerコンテナによる環境構築の自動化)
- Elastic Beanstalk(Webアプリケーションのデプロイ自動化)
- OpsWorks(サーバの設定、デプロイ、管理の自動化)
など
今回は、Cloud Formationでの環境構築の自動化をやっていきます。
Cloud Formationとは
- インフラストラクチャリソースのテンプレートを利用して自動で構築することができる環境自動設定サービス
- AWS内の全てのインフラリソースをテンプレート化することができる
- テンプレートはJSONかYAMLで記述する
- Code Piplineと組み合わせることで、テンプレートの変更、実行、展開を自動化できる
- Cloud Formationデザイナーを使うことで、視覚的にテンプレートを作成することも可能
Cloud Formationを使う利点
- 環境構築を効率化できる
- いつでも同じ設定の環境を構築することができる(標準化)
- ソフトウェアと同じように構成管理ができる
テンプレートについて
AWS公式ドキュメントのテンプレートリファレンスに全て情報が載っているので、こちらを見ながら作成していきます。
AWSTemplateFormatVersion: '2010-09-09'
Description: A sample template
Metadata:
Parameters:
Mappings:
Conditions:
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
# 省略
- AWSTemplateFormatVersion: 必ず頭に記述する
- Description: テンプレートの説明
- Metadata: メタデータ
- Parameters: 共通で使いたい要素を参照値として記述
- Mappings: Parametersの値が複数バージョン
- Conditions: リソース作成時の条件内容を記述
- Resources: 実際に作りたいリソースの内容を記述
Cloud Formationで環境構築をしてみる
どんな環境を作るか
以下の構成の環境を、Cloud Formationを使って構築していきます。
VPCの作成
YAMLファイルの記述
AWSTemplateFormatVersion: '2010-09-09'
Description: A sample template
Parameters:
Resources:
MyVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: '10.10.0.0/16'
Tags:
- Key: 'Name'
Value: 'sample-vpc'
- MyVPC: 任意のリソース名
- Type: 作成するリソースを記載。VPCのリソース名は、"AWS::EC2::VPC"
- Properties: プロパティ
- CiderBlock: 任意のCIDRブロックを設定する。"10.10.0.0/16"を設定することで、このVPC内では10.10.0.0~10.10.255.255までのIPアドレスが使用可能になる。
- Tags: タグをつけるときに使用する
サブネットの作成
パブリックサブネットを1つ、プライベートサブネットを2つ作成します。
YAMLファイルの記述
# 省略
Resources:
# 省略
MyPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.11.0/24'
MapPublicIpOnLaunch: true
Tags:
- Key: 'Name'
Value: 'sample-public-subnet'
MyPrivateSubnet1A:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.21.0/24'
AvailabilityZone: 'ap-northeast-1a'
Tags:
- Key: 'Name'
Value: 'sample-private-subnet1a'
MyPrivateSubnet1C:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.22.0/24'
AvailabilityZone: 'ap-northeast-1c'
Tags:
- Key: 'Name'
Value: 'sample-private-subnet1c'
- VpcId: このサブネットが所属するVPCのIDもしくはリソース名を記述する
- Ref関数: 指定したパラメータまたはリソースの値を返す(ここではMyVpcを返す)
- MapPublicIpOnLaunch: このサブネットで起動されたインスタンスがパブリックIPアドレスを受け取る場合はtrueにする(デフォルトはfalse)
インターネットゲートウェイの作成
YAMLファイルの記述
# 省略
Resources:
# 省略
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: 'Name'
Value: 'sample-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MyVpc
InternetGatewayId: !Ref MyInternetGateway
AttachGatewayでVPCにインターネットゲートウェイをアタッチする
パブリックサブネットの作成
インターネットゲートウェイとパブリックサブネットをルートテーブルで紐付ける
YAMLファイルの記述
# 省略
Resources:
# 省略
MyRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: 'Name'
Value: 'sample-rt'
VpcId: !Ref MyVpc
MyRoute:
Type: AWS::EC2::Route
DependsOn: MyInternetGateway
Properties:
RouteTableId: !Ref MyRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
MySubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref MyPublicSubnet
RouteTableId: !Ref MyRouteTable
インターネットゲートウェイを指定したルート(MyRoute)をルートテーブル(MyRouteTable)に設定し、ルートテーブルをパブリックサブネットにMySubnetRouteTableAssociationで紐付けた
ここまでできました
EC2インスタンスを作成
パブリックサブネットにEC2インスタンスを設置する
YAMLファイルの記述
# 省略
Parameters:
KeyPair:
Description: Select KeyPair Name.
Type: AWS::EC2::KeyPair::KeyName
Resources:
# 省略
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: 'ami-xxxxxx' # xにはidがはいる
InstanceType: t2.micro
SubnetId: !Ref MyPublicSubnet
BlockDeviceMappings:
- DeviceName: '/dev/xvda'
Ebs:
VolumeType: 'gp2'
VolumeSize: 8
Tags:
- Key: 'Name'
Value: 'sample-ec2-instance'
SecurityGroupIds:
- !Ref MyEC2SecurityGroup
KeyName: !Ref KeyPair
MyEC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref MyVpc
Tags:
- Key: 'Name'
Value: 'sample-ssh-sg'
GroupDescription: Allow ssh
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
- ImageId: 使用するAMIのIDを記述する
- SubnetId: EC2インスタンスを設置するサブネットを指定
- BlockDeviceMappings: インスタンスにアタッチするブロックデバイスを定義
- KeyName: KeyPairはParameterで設定しており、マネジメントコンソールで指定する
- SecurityGroupIngress: 受信規則を指定
DBインスタンスの作成
プライベートサブネットにDBインスタンスを設置する。RDSを使用する。マルチAZ構成にする。
YAMLファイルの記述
# 省略
Parameters:
# 省略
MyRDSMasterUser:
Type: String
Default: admin
MinLength: 1
MaxLength: 16
NoEcho: true
AllowedPattern: '[a-z]+'
MyRDSMasterPassword:
Type: String
Default: password
MinLength: 8
MaxLength: 16
NoEcho: true
AllowedPattern : '[^\/@"]+'
Resources:
# 省略
MyRDSSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: SubnetGroup for RDS
SubnetIds:
- !Ref MyPrivateSubnet1A
- !Ref MyPrivateSubnet1C
MyRDSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for DB
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: '3306'
ToPort: '3306'
VpcId: !Ref MyVpc
MyRDSDBInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: '5'
DBSubnetGroupName: !Ref MyRDSSubnetGroup
VPCSecurityGroups:
- !Ref MyRDSSecurityGroup
MultiAZ: true
DBInstanceClass: db.t2.micro
DBName: 'sample'
Engine: MySQL
EngineVersion: 5.7.22
MasterUsername: !Ref MyRDSMasterUser
MasterUserPassword: !Ref MyRDSMasterPassword
DeletionPolicy: Snapshot
- MyRDSMasterUser(Parameter): DBのユーザ名のバリデーションを設定(ユーザ名はマネジメントコンソールで設定する)
- MyRDSMasterPassword(Parameter): DBのパスワードのバリデーションを設定(パスワードはマネジメントコンソールで設定する)
- MultiAZ: マルチAZ構成にする場合はtrue(デフォルトはfalse)
- Engine: DBエンジンを指定(今回はMySQL)
- MasterUsername: マネジメントコンソールで設定したMyRDSMasterUserをユーザ名として使用
- MasterUserPassword: マネジメントコンソールで設定したMyRDSMasterPasswordをパスワードとして使用
- ハマりポイント: VPCSecurityGroupsは配列での指定じゃないとダメでした
完成!
完成したテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: A sample template
Parameters:
KeyPair:
Description: Select KeyPair Name.
Type: AWS::EC2::KeyPair::KeyName
MyRDSMasterUser:
Type: String
Default: admin
MinLength: 1
MaxLength: 16
NoEcho: true
AllowedPattern: '[a-z]+'
MyRDSMasterPassword :
Type: String
Default: password
MinLength: 8
MaxLength: 16
NoEcho: true
AllowedPattern : '[^\/@"]+'
Resources:
MyVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: '10.10.0.0/16'
Tags:
- Key: 'Name'
Value: 'sample-vpc'
MyPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.11.0/24'
MapPublicIpOnLaunch: true
Tags:
- Key: 'Name'
Value: 'sample-public-subnet'
MyPrivateSubnet1A:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.21.0/24'
AvailabilityZone: 'ap-northeast-1a'
Tags:
- Key: 'Name'
Value: 'sample-private-subnet1a'
MyPrivateSubnet1C:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVpc
CidrBlock: '10.10.22.0/24'
AvailabilityZone: 'ap-northeast-1c'
Tags:
- Key: 'Name'
Value: 'sample-private-subnet1c'
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: 'Name'
Value: 'sample-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MyVpc
InternetGatewayId: !Ref MyInternetGateway
MyRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: 'Name'
Value: 'sample-rt'
VpcId: !Ref MyVpc
MyRoute:
Type: AWS::EC2::Route
DependsOn: MyInternetGateway
Properties:
RouteTableId: !Ref MyRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
MySubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref MyPublicSubnet
RouteTableId: !Ref MyRouteTable
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: 'ami-xxxxxx' # xにはidがはいる
InstanceType: t2.micro
SubnetId: !Ref MyPublicSubnet
BlockDeviceMappings:
- DeviceName: '/dev/xvda'
Ebs:
VolumeType: 'gp2'
VolumeSize: 8
Tags:
- Key: 'Name'
Value: 'sample-ec2-instance'
SecurityGroupIds:
- !Ref MyEC2SecurityGroup
KeyName: !Ref KeyPair
MyEC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref MyVpc
Tags:
- Key: 'Name'
Value: 'sample-ssh-sg'
GroupDescription: Allow ssh
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
MyRDSSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: SubnetGroup for RDS
SubnetIds:
- !Ref MyPrivateSubnet1A
- !Ref MyPrivateSubnet1C
MyRDSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for DB
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: '3306'
ToPort: '3306'
VpcId: !Ref MyVpc
MyRDSDBInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: '5'
DBSubnetGroupName: !Ref MyRDSSubnetGroup
VPCSecurityGroups:
- !Ref MyRDSSecurityGroup
MultiAZ: true
DBInstanceClass: db.t2.micro
DBName: 'sample'
Engine: MySQL
EngineVersion: 5.7.22
MasterUsername: !Ref MyRDSMasterUser
MasterUserPassword: !Ref MyRDSMasterPassword
DeletionPolicy: Snapshot