こんにちは、ロジカル・アーツの村下です。
AWSで環境構築する際、それらが過去に構築した環境と同じ、似た構成だった場合、もしくは複数の環境を構築する場合、同じ作業を行うのは面倒だと感じたことはありませんか?
こういう時、CloudFormationでテンプレートを作成しておけば、簡単に環境構築をすることができます。
今回は、そんなCloudFormationについて解説していきたいと思います。
CloudFormationとは
CloudFormationとは、AWSリソースを自動作成・管理するサービスです。
予め、「どのリソースをどのような設定で作るか」をテンプレートにJSONやYAML形式で記述して作成します。 作成したテンプレートを用いることで、AWSリソースを自動作成してくれます。
作成したテンプレートをそのまま使ったり、少しだけ編集することで、同じ・似た環境を複数構築することができます。 結果、作業時間の短縮や労力の削減に繋がります。
シナリオ
今回は、VPCと、その中にパブリック・プライベートサブネットを構築し、それぞれのサブネットの中にEC2インスタンスを構築するテンプレートを作成してみたいと思います。
また、VPC・サブネットにゲートウェイやルートテーブルの設置にルーティング、EC2用のセキュリティグループの作成も同時に行います。
CloudFormationの基本
テンプレートを作成する前に、CloudFormationの基本情報について解説します。
テンプレート構造
以下が、テンプレートの基本的な構成の例です。今回はYAML形式で作成しています。
AWSTemplateFormatVersion: 2010-09-09 #テンプレートのバージョン Resources: #構築するリソースの宣言 VPC: #論理名 Type: AWS::EC2::VPC #リソースタイプ Properties: #リソースのパラメータを設定 CidrBlock: 10.0.0.0/16
・AWSTemplateFormatVersion: テンプレートのバージョンを指定します。2023年4月時点では 2010-09-09 のみ指定できます。
・Resources: 構築するリソースを宣言します。Resourcesセクションは記述必須なため、書き忘れに注意して下さい。
・論理名: 論理名を指定します。こちらは同じテンプレート内で一意である必要があり、テンプレート内で参照する際に使用します。
・Type: 構築するリソースタイプ。例だとVPCなので、AWS::EC2::VPC を指定しています。(例えば、EC2インスタンスの場合だと、AWS::EC2::Instance を指定します。)
・Properties: リソースのパラメータを設定を記述します。どのようなパラメータがあるか、どれが必須なのかはリソースによって異なります。
リソースタイプやパラメータなどの記述方法、その他詳しい解説などは以下のAWS公式ドキュメントを参照してみて下さい。
組み込み関数
同じテンプレート内の異なるリソースの情報を参照したい場合、組み込み関数を使用することができます。
PublicSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/24 VpcId: !Ref VPC
上記は組み込み関数 Ref の使用例です。
サブネットのパラメータにはVPCのIDが必要です。 この際、組み込み関数 Ref を使用して、VPCを指定することで、指定したVPCのIDを参照できます。
「!Ref 論理名」 または 「Ref: 論理名」のように使用します。 例の場合、TestVPC のIDを参照したいため、サブネットのパラメータ VpcId に「!Ref TestVPC」を指定しています。
NATGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NATGatewayEIP.AllocationId
上記は組み込み関数 Fn::GetAtt の使用例です。
NATゲートウェイに AllocationId でEIPのIDを取得する際に使用しています。
「Fn::GetAtt: [論理名, 属性名]」 または 「!GetAtt 論理名.属性名」のように使用します。 例の場合、NATGatewayEIP という論理名のリソースが持つ、AllocationId を取得しています。
他にも組み込み関数はありますが、ここでは記事内で使用するものについて解説しました。 その他の組み込み関数の解説などは以下のAWS公式ドキュメントを参照してみて下さい。
パラメータ
環境によって変化する箇所がある場合、Parameters セクションを使用すると便利です。
AWSTemplateFormatVersion: 2010-09-09 Parameters: VPCCidrBlock: #論理名 Type: String #パラメータのタイプ Default: 10.0.0.0/16 #デフォルトの値を設定 Resources: # VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCidrBlock #組み込み関数 Ref で参照する
例えば、VPC の CidrBlock が開発環境・テスト環境で変化する場合、 Parameters セクション以下に、VPCCidrBlock というパラメータを宣言します。 そして、そのパラメータのタイプと、デフォルトの値を設定します。
値を設定したら、Resouces セクション ,VPC の CidrBlock で、組み込み関数 Ref を使い、VPCCidrBlock を参照します。 パラメータタイプは様々ありますが、String 型が自由入力で汎用的なためそちらを使用します。
Parameters の詳しい解説や、その他のパラメータタイプは、以下のAWS公式ドキュメントを参照してみて下さい。
テンプレートを用いてリソースを構築する際、Parameters セクションで宣言したパラメータの値を入力することができます。 また、デフォルトを設定すると、値が入力欄に予め入力されています。
ここの例では VPC の CidrBlock が環境によって変化する想定で書きましたが、 今回の記事で作成するテンプレートでは、EC2インスタンスのイメージIDとインスタンスタイプ、セキュリティグループの名前が変化する想定で作成していきます。
どのリソースのどのパラメータが環境によって変化するかを想定しておくと書きやすいと思います。
タグ
AWSのリソースはタグを付与することで、識別や分類に役立てることができます。 テンプレート上でも、タグを付与することができます。
Tags: - Key: Name Value: VPC
このように、Key でキー名、Value でタグの値を指定できます。
テンプレート作成
前置きが長くなりましたが、テンプレート作成をしていきます。
なお、VPCやサブネット、EC2等のAWSリソースの知識はすでに持っている前提で解説していきます。
VPC
まずはVPCを構築するテンプレートの作成をします。
# VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Tags: - Key: Name Value: VPC
以上の内容を、テンプレートの構成に記述してある、AWSTemplateFormatVersion と Resources の下に記述します。
CidrBlock でインターネット範囲を指定しています。
サブネット
次にVPC内に、パブリック・プライベートサブネットを構築するテンプレートの作成をします。
# PublicSubnet PublicSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/24 VpcId: !Ref VPC Tags: - Key: Name Value: PublicSubnet # PrivateSubnet PrivateSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.1.0/24 VpcId: !Ref VPC Tags: - Key: Name Value: PrivateSubnet
VPC同様、CidrBlock で範囲を指定します。
VpcId でVPCのIDを指定します。 組み込み関数で説明したように、「!Ref VPC」で VPC のIDを参照できます。
ゲートウェイ
続いて、ゲートウェイを構築するテンプレートを作成します。
まずは、インターネットゲートウェイ(以下、IGW)を作成し、VPCに設置する内容を記述します。
# InternetGateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: InternetGateway
IGWは作成だけでなく、別途設置の設定を記述する必要があります。
# InternetGatewayAttach AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway
VpcId で設置先のVPC、InternetGatewayId で設置するIGWを指定します。 例では、すでに作成したリソースを、組み込み関数 Ref を使用して参照しています。
次に、パブリックサブネット内に設置するNATゲートウェイ(以下、NGW)を構築するテンプレートを作成します。
NGWはパブリックサブネットに設置する場合、EIPのIDが必要なため、EIPを作成します。
# NATGatewayEIP NATGatewayEIP: Type: AWS::EC2::EIP Properties: Domain: vpc
EIPを用意できたら、NGWを設定します。
# NATGateway NATGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NATGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet Tags: - Key: Name Value: NATGateway
組み込み関数 GetAtt を使用して、作成したEIPのIDを取得します。 また、SubnetId で設置するサブネットを指定します。
ルートテーブルとルーティング
ゲートウェイのテンプレート作成が完了したら、ルートテーブルの作成とルーティング設定を行うテンプレートを作成します。 こちらは大きく分けて、3つの段階があります。
1.ルートテーブルの作成
2.作成したルートテーブルの中身を設定(Cidrブロック と通信の行き先であるゲートウェイを設定)
3.ルートテーブルをサブネットに関連付ける
まずは、パブリックルートテーブルを作成します。
# PublicRouteTable PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: PublicRoteTable
VpcId でルートテーブルを設置するVPCを指定します。
次に、ルートテーブルの中身を設定します。
# PublicRoute InternetGatewayRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway
RouteTableId で設定する対象のルートテーブル、DestinationCidrBlock でCidrブロック、GatewayId で通信先のゲートウェイをそれぞれ指定します。
最後に、ルートテーブルをサブネットに関連付けます。
PublicSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref PublicRouteTable
SubnetId と RouteTableId で関連付けるサブネットとルートテーブルを設定します。
同様に、プライベートルートテーブルの作成・設定のテンプレートを作成します。
# PrivateRouteTable PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: PrivateRoteTable # PrivateRoute NATGatewayRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NATGateway PrivateSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable
一度に書きましたが、内容は概ねパブリックと同じなので詳しい解説は省略します。 注意点としては、パブリックのIGWとは違い、NGWを指定しています。
EC2
今度は、EC2インスタンスの構築するテンプレートを作成します。 パブリック、プライベートサブネットにそれぞれインスタンスが作成されるように記述します。
ここで、Parameters の項で書いた通り、 EC2インスタンスのイメージIDのインスタンスタイプは環境によって変化する想定でテンプレートを作成します。
まずは、変化するパラメータを Parameters セクションで宣言していきます。
Parameters: # PublicInstance PublicInstanceImageId: Type: String Default: ami-079a2a9ac6ed876fc PublicInstanceType: Type: String Default: t2.micro # PrivateInstance PrivateInstanceImageId: Type: String Default: ami-079a2a9ac6ed876fc PrivateInstanceType: Type: String Default: t2.micro
注意点としては、Parameters は Resources 以下ではなく外側に書くことです。
ImageId は今回は無料枠のモノを使用します。
宣言ができたら、次は Resources を設定していきます。
# PublicInstance PublicInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref PublicInstanceImageId InstanceType: !Ref PublicInstanceType SubnetId: !Ref PublicSubnet SecurityGroupIds: - !Ref PublicInstanceSecurityGroup # PrivateInstance PrivateInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref PrivateInstanceImageId InstanceType: !Ref PrivateInstanceType SubnetId: !Ref PrivateSubnet SecurityGroupIds: - !Ref PrivateInstanceSecurityGroup
Parameters で宣言したパラメータを 組み込み関数 Ref を使用して指定します。
SubnetId でそれぞれのサブネット、SecurityGroupIds でそれぞれのセキュリティグループを指定します。
パブリックインスタンスにはEIPを付与したいので、別途EIPを用意します。
# PublicInstancEIP PublicInstancEIP: Type: AWS::EC2::EIP Properties: Domain: vpc InstanceId: !Ref PublicInstance
InstanceId でEIPを付与するインスタンスを指定します。
セキュリティグループ
最後に、それぞれのインスタンスに設定するセキュリティグループ(以下、SG)のテンプレートを作成します。
EC2インスタンス同様に、セキュリティグループの名前が環境ごとに変化する想定で作成するので、 変化するパラメータを Parameters セクションで宣言します。
# SecurityGroupName PublicInstanceSecurityGroupName: Type: String Default: Test-SG-Instance-Public PrivateInstanceSecurityGroupName: Type: String Default: Test-SG-Instance-Private
こちらをEC2インスタンスの変化するパラメータの宣言に続けて記述します。
パラメータの宣言後、まずは、パブリックインスタンスのSGから設定します。
# PublicInstancSG PublicInstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: http and ssh GroupName: !Ref PublicInstanceSecurityGroupName VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort : 80 ToPort : 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort : 22 ToPort : 22 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: -1 FromPort : -1 ToPort : -1 CidrIp: 0.0.0.0/0
GroupName を 組み込み関数 !Ref でPublicInstanceSecurityGroupName を参照し 、 VpcId を指定します。
SecurityGroupIngress でインバウンド、SecurityGroupEgress でアウトバウンドルールをそれぞれ設定できます。
インバウンドでは今回、HTTP と SSH からの全ての通信を許可します。
IpProtocol でプロトコル名、FromPort でポート範囲の開始、ToPort でポート範囲の終了、CidrIp でアドレス範囲をそれぞれ指定します。
プロトコルは両方とも tcp を指定、HTTP のポート番号 80 と SSH のポート番号 22 をそれぞれポートの範囲に指定します。
アウトバウンドでは、すべての通信を許可します。 それぞれの値を -1 にすることで、全てを指定できます。
続いて、プライベートサブネットのSGを設定します。
# PrivateInstancSG PrivateInstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: ssh GroupName: !Ref PrivateInstanceSecurityGroupName VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort : 22 ToPort : 22 SourceSecurityGroupId: !Ref PublicInstanceSecurityGroup SecurityGroupEgress: - IpProtocol: -1 FromPort : -1 ToPort : -1 CidrIp: 0.0.0.0/0
インバウンドでは SSH のみを許可します。
CidrIp の代わりに SourceSecurityGroupId を指定することで、指定したSGに属するリソースからの接続が許可されます。
アウトバウンドはパブリックインスタンス用SGと同様の設定をします。
SG の設定まで完了すれば、今回のテンプレート作成は一通り完了です。
完成テンプレート
AWSTemplateFormatVersion: 2010-09-09 Parameters: # PublicInstance PublicInstanceImageId: Type: String Default: ami-079a2a9ac6ed876fc PublicInstanceType: Type: String Default: t2.micro # PrivateInstance PrivateInstanceImageId: Type: String Default: ami-079a2a9ac6ed876fc PrivateInstanceType: Type: String Default: t2.micro # SecurityGroupName PublicInstanceSecurityGroupName: Type: String Default: Test-SG-Instance-Public PrivateInstanceSecurityGroupName: Type: String Default: Test-SG-Instance-Private Resources: # VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Tags: - Key: Name Value: VPC # PublicSubnet PublicSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/24 VpcId: !Ref VPC Tags: - Key: Name Value: PublicSubnet # PrivateSubnet PrivateSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.1.0/24 VpcId: !Ref VPC Tags: - Key: Name Value: PrivateSubnet # InternetGateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: InternetGateway # InternetGatewayAttach AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # NATGatewayEIP NATGatewayEIP: Type: AWS::EC2::EIP Properties: Domain: vpc # NATGateway NATGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NATGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet Tags: - Key: Name Value: NATGateway # PublicRouteTable PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: PublicRoteTable # PublicRoute InternetGatewayRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref PublicRouteTable # PrivateRouteTable PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: PrivateRoteTable # PrivateRoute NATGatewayRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NATGateway PrivateSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable # PublicInstance PublicInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref PublicInstanceImageId InstanceType: !Ref PublicInstanceType SubnetId: !Ref PublicSubnet SecurityGroupIds: - !Ref PublicInstanceSecurityGroup # PublicInstancEIP PublicInstancEIP: Type: AWS::EC2::EIP Properties: Domain: vpc InstanceId: !Ref PublicInstance # PrivateInstance PrivateInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref PrivateInstanceImageId InstanceType: !Ref PrivateInstanceType SubnetId: !Ref PrivateSubnet SecurityGroupIds: - !Ref PrivateInstanceSecurityGroup # PublicInstancSG PublicInstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: http and ssh GroupName: !Ref PublicInstanceSecurityGroupName VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort : 80 ToPort : 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort : 22 ToPort : 22 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: -1 FromPort : -1 ToPort : -1 CidrIp: 0.0.0.0/0 # PrivateInstancSG PrivateInstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: ssh GroupName: !Ref PrivateInstanceSecurityGroupName VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort : 22 ToPort : 22 SourceSecurityGroupId: !Ref PublicInstanceSecurityGroup SecurityGroupEgress: - IpProtocol: -1 FromPort : -1 ToPort : -1 CidrIp: 0.0.0.0/0
以上は完成したテンプレートの全体です。参考にしてみて下さい。
任意の場所にファイルを保存してください。 今回はファイル名を「cloudformation_Murasita」としてあります。
実行
では、完成したテンプレートを使用して、構築を行いましょう。
まず、AWSにログインし、CloudFormationのコンソールを開きます。 そして、「スタックの作成」を選択します。
前提条件はテンプレートの準備完了を選択し、テンプレートファイルのアップロードで先程保存したファイルを指定して次へ、をクリックします。
スタックの名前を付けます。
Parameters で宣言したパラメータの設定を行えます。 ここで環境によって値を指定できますが、今回はデフォルトの値を使用するので、 特に入力せずに次へ、をクリックします。
スタックオプションの設定、レビューは特に入力するものがないので次へ、をクリックします。
スタックが作成出来たら、テンプレートに従ったリソースが順次作られていきます。 しばらくすると、全てのリソースの構築が完了しました。
テンプレートに不備があった場合は、スタックを作成するときにエラーが出たり、リソースの構築に失敗します。 エラー文が出るので、それに従って修正してください。(インデントがズレている、権限がない、などでもエラーが出るので確認してみて下さい。)
削除
今回構築したリソースの削除は忘れずに行いましょう。
そのままにしておくと、料金が発生してしまう場合があるので、構築したリソースを使用しない場合は削除しましょう。 リソースの種類や設定、構成によっては、多額の料金が掛かってきます。
スタックを選択して、右上の削除を選択することで、構築したリソースが削除されます。
以下の画像のような表記が出ると、全てのリソースの削除が完了しました。
最後に
CloudFormation のテンプレート作成は、初めこそ時間が掛ると思いますが、 1度作ってしまえばそれを使いまわしたり、用途に合わせて編集することで簡単にリソース構築ができるようになります。
参考
組み込み関数リファレンス - AWS CloudFormation
AWS リソースおよびプロパティタイプのリファレンス - AWS CloudFormation