【AWS】CloudFormationを使ってみよう
こんにちは。プラットフォーム技術部の近藤です。
AWSを使っていて
「サクッと検証できる環境を作りたい…」
「以前作った検証環境を使いまわしたかったけど、削除しちゃったな…」
そんなシーンは多いかと思います。
そんな時に便利なのが「CloudFormation」です。CloudFormationでリソース作成する流れを、簡単なWebシステムを想定した以下の構成を作成しながら紹介します。
CloudFormationとは
CloudFormationでリソース作成を始める前に、まずは簡単にCloudFormationについて説明します。
CloudFormationとは?
CloudFormationは、AWSリソースの構築や管理をコードによっておこなえるサービスです。JSONまたはYAML形式でリソースの設定を記述し、リソースの作成や設定の変更ができます。
テンプレートとスタック
CloudFormationを使う上で頻出する用語が「テンプレート」と「スタック」です。それぞれについて簡単に紹介します。
テンプレート
リソースの各種パラメーターを、JSONまたはYAML形式で記述し作成します。テンプレートは手で打ち込んで作成するのはもちろん、CloudFormationデザイナーでリソースを視覚的に配置して作成できます。
スタック
関連したリソースをひとまとめにしたユニットです。テンプレートをもとにスタックを作成し、スタック単位でリソースを作成できます。リソースの変更や削除もスタック単位でおこないます。
テンプレートを準備しよう1~テンプレートの基本~
個別リソースのテンプレート作成に入る前に、テンプレートの基本について簡単に説明します。
テンプレートの構造
テンプレートの基本的な構造を見てみましょう。なお、今回はYAML形式を使用します。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Resources:
kondoVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
・
・
・
AWSTemplateFormatVersion
テンプレートのフォーマットバージョンを指定します。2022年11月現在の最新バージョンは「2010-09-09」で、このバージョンのみ指定できます。
Description
テンプレートの説明やコメントを記述できます。
Resources
このセクションで、作成するAWSリソースの宣言や設定を記述します。以下の要素で構成されます。
- 論理名:同じテンプレート内で一意の論理名を指定します。
- Type:作成したいリソースのリソースタイプを指定します。
- Properties:リソースの設定を記述します。
リソースのリソースタイプや各リソースのパラメーターの記述方法は、AWS公式ドキュメントから確認できます。
組み込み関数について
リソースの中には、他リソースの情報(例えばARNなど)が設定上必要なものがあります。情報が必要なリソースを同じテンプレートで作成している場合は、以下の組み込み関数を使用して、他リソースの情報を参照できます。今回のテンプレートではRefとGetAttを使用しています。組み込み関数が返却する値は、公式ドキュメントの各リソースタイプページのReturn valuesに記載されています。
Ref
「Ref: リソースの論理名」もしくは「!Ref リソースの論理名」の形式で記述し、指定したリソースの物理IDなどの情報を取得します。今回のテンプレートでは、例えばSubnetを作成する際に、VpcIdのパラメーターとして、kondoVpcのVPC IDを取得しています。
Fn::GetAtt
「Fn::GetAtt: [リソースの論理名, 属性名]」もしくは「!GetAtt リソースの論理名.属性名」の形式で記述し、指定したリソースの属性を取得できます。今回のテンプレートでは、例えばSecurityGroupのInboundRuleEC2のセクションの中で、GroupIdのパラメーターとして、SecurityGroupEC2のGroupIdを取得しています。
テンプレートを準備しよう2~個別リソース編~
今回設定したテンプレートを、リソースごとに分けて紹介します。もう一度構成図を確認しておきましょう。
VPC
kondoVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: kondoVpc
今回、VPCではCidrBlockのみ指定しました。
公式ドキュメントはこちら
Subnet
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref kondoVpc
CidrBlock: 10.0.0.0/24
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: PublicSubnet1
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref kondoVpc
CidrBlock: 10.0.1.0/24
AvailabilityZone: "ap-northeast-1c"
Tags:
- Key: Name
Value: PublicSubnet2
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref kondoVpc
CidrBlock: 10.0.2.0/24
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: PrivateSubnet1
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref kondoVpc
CidrBlock: 10.0.3.0/24
AvailabilityZone: "ap-northeast-1c"
Tags:
- Key: Name
Value: PrivateSubnet2
SubnetはPublicSubnetとPrivateSubnetを2つずつ、計4つ作成しています。
公式ドキュメントはこちら
SecurityGroup
SecurityGroupALB:
Type: AWS::EC2::SecurityGroup
DependsOn: kondoVpc
Properties:
GroupDescription: For ALB
GroupName: SecurityGroupALB
SecurityGroupEgress:
- DestinationSecurityGroupId: !Ref SecurityGroupEC2
FromPort: 80
IpProtocol: tcp
ToPort: 80
SecurityGroupIngress:
- CidrIp: x.x.x.x/x
FromPort: 80
IpProtocol: tcp
ToPort: 80
Tags:
- Key: Name
Value: SecurityGroupALB
VpcId: !Ref kondoVpc
SecurityGroupEC2:
Type: AWS::EC2::SecurityGroup
DependsOn: kondoVpc
Properties:
GroupDescription: For EC2
GroupName: SecurityGroupEC2
Tags:
- Key: Name
Value: SecurityGroupEC2
VpcId: !Ref kondoVpc
InboundRuleEC2:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !GetAtt SecurityGroupALB.GroupId
GroupId: !GetAtt SecurityGroupEC2.GroupId
ALB用とEC2用の2種類作成しています。このコードを参考にリソースを作成する場合は「SecurityGroupALB:SecurityGroupIngress:CidrIp」のIPをご自身の要件に合わせて変更してください。
なお「AWS::EC2::SecurityGroupIngress」はインバウンドルールにSecurityGroupを指定する際に必要なリソースタイプです。今回だとSecurityGroupEC2にSecurityGroupALBからのインバウンドを許可するために使用しています。
公式ドキュメントはこちら
InternetGateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref kondoVpc
InternetGatewayId: !Ref InternetGateway
InternetGatewayセクションでInternetGateway自体、そしてAttachGatewayセクションでInternetGatewayをアタッチする先を指定しています。
公式ドキュメントはこちら
NAT,EIP
NAT1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP1.AllocationId
ConnectivityType: public
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: NAT1
EIP1:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NAT2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP2.AllocationId
ConnectivityType: public
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: NAT2
EIP2:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NATとEIPをそれぞれ2つずつ作成します。EIPは、NATのAllocationIdで紐づけています。
公式ドキュメントはこちら
RouteTable
RouteTableForPrivateSubnet1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref kondoVpc
Tags:
- Key: Name
Value: RouteTableForPrivateSubnet1
RouteToNAT1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableForPrivateSubnet1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NAT1
SubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId:
Ref: RouteTableForPrivateSubnet1
RouteTableForPrivateSubnet2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref kondoVpc
Tags:
- Key: Name
Value: RouteTableForPrivateSubnet2
RouteToNAT2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableForPrivateSubnet2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NAT2
SubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId:
Ref: RouteTableForPrivateSubnet2
RouteTableForPublicSubnet:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref kondoVpc
Tags:
- Key: Name
Value: RouteTableForPublicSubnet
RouteToIGW:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableForPublicSubnet
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation3:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref RouteTableForPublicSubnet
SubnetRouteTableAssociation4:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref RouteTableForPublicSubnet
RouteTableはInternetGateway向け1つと、NAT1、NAT2向けそれぞれ1つずつ、計3つ作成します。「AWS::EC2::RouteTable」でRouteTable、「AWS::EC2::Route」でルート、「AWS::EC2::SubnetRouteTableAssociation」でそのRouteTableを割り当てるSubnetを定義しています。
公式ドキュメントはこちら
EC2
kondoEC2a:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdb
Ebs:
VolumeSize: 8
InstanceType: t2.micro
ImageId: ami-0a3d21ec6281df8cb
KeyName: kondo-key
SecurityGroupIds:
- !Ref SecurityGroupEC2
SubnetId: !Ref PrivateSubnet1
Tags:
- Key: Name
Value: kondoEC2a
kondoEC2c:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdb
Ebs:
VolumeSize: 8
InstanceType: t2.micro
ImageId: ami-0a3d21ec6281df8cb
KeyName: kondo-key
SecurityGroupIds:
- !Ref SecurityGroupEC2
SubnetId: !Ref PrivateSubnet2
Tags:
- Key: Name
Value: kondoEC2c
EC2は指定できるパラメーターの数が多く、選別が大変です。公式ドキュメントの「Examples」にサンプルテンプレートが掲載されているので、そちらなども参考にしながら自分に必要なパラメーターを選んでいきましょう。
公式ドキュメントはこちら
ALB,TargetGroup,Listener
TargetGroupEC2a:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 80
Protocol: HTTP
Tags:
- Key: Name
Value: TargetGroupEC2a
Targets:
- Id: !Ref kondoEC2a
VpcId: !Ref kondoVpc
TargetGroupEC2c:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 80
Protocol: HTTP
Tags:
- Key: Name
Value: TargetGroupEC2c
Targets:
- Id: !Ref kondoEC2c
VpcId: !Ref kondoVpc
kondoALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: kondo-alb
SecurityGroups:
- !Ref SecurityGroupALB
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Tags:
- Key: Name
Value: kondoALB
Listener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref TargetGroupEC2a
Weight: 1
- TargetGroupArn: !Ref TargetGroupEC2c
Weight: 1
Port: 80
Protocol: HTTP
LoadBalancerArn: !Ref kondoALB
EC2用のTargetGroupが2つとALB、そしてALBに設定するListenerを作成しています。今回はListenerのForwardConfigで、2台のEC2へのアクセスを均等に分配するような設定を記載してみました。
公式ドキュメントはこちら
- AWS::ElasticLoadBalancingV2::TargetGroup
- AWS::ElasticLoadBalancingV2::LoadBalancer
- AWS::ElasticLoadBalancingV2::Listener
補足
今回は上記のリソースを1つのテンプレートにまとめて作成しますが、ベストプラクティスを参考にご自身の環境に合わせてテンプレートを分割することなどもご検討ください。
CloudFormationを実行してみよう
実行前に権限周りの確認
実行前に、必要な権限周りについて整理しておきましょう。CloudFormationでリソースを作成する際、ユーザー(またはサービスロール)に対して以下のポリシーが必要になります。
- CloudFormationへのアクセス許可ポリシー
- S3へのアクセス許可ポリシー
- SNSへのアクセス許可ポリシー
- 作成したいリソースへのアクセス許可ポリシー
- IAMへのアクセス許可ポリシー
CloudFormationへのアクセス許可ポリシー
CloudFormationの操作を行う上で必要です。
権限をスタックの閲覧のみにする、特定のテンプレートからしかスタックを作成できないようにする、など細かくコントロールもできますが、今回の検証ではAWS管理ポリシーの「AWSCloudFormationFullAccess」を使用します。
S3へのアクセス許可ポリシー
コンソール上でCloudFormationを利用してリソースを作成する場合、テンプレートをS3バケットへアップロードする必要があります。よってS3への以下操作を、ユーザーに対して許可する必要があります。
- s3:PutObject
- s3:ListBucket
- s3:GetObject
- s3:CreateBucket
ひとまず今回の検証ではAWS管理ポリシーの「AmazonS3FullAccess」を使用して権限を付与します。
SNSへのアクセス許可ポリシー
CloudFormationではスタックオプションの中で、スタックイベントをSNSトピックに対して通知できます。その機能を使用する際ユーザーに対してSNSへのアクセス許可が必要です。既存のSNSトピックを使用する場合は読み取り権限、スタックオプションの中で新規にSNSトピックを作成する場合は、加えて作成・登録のための権限が必要です。
今回の検証ではAWS管理ポリシーの「AmazonSNSReadOnlyAccess」を使用します。
作成したいリソースへのアクセス許可ポリシー
テンプレートに記載したリソースに対するアクセス許可ポリシーが必要です。リソースに対するアクセス許可ポリシーはユーザーに対して付与できますが、ユーザーの権限とCloudFormationで使える権限を分けたい場合は、CloudFormation用のサービスロールを作成し、それに対して付与もできます。
今回の検証ではAWS管理ポリシーの「AmazonEC2FullAccess」をユーザーに対して付与して使用します。
IAMへのアクセス許可ポリシー
「作成したいリソースへのアクセス許可ポリシー」の項目でも少し紹介しましたが、CloudFormationではサービスロールを設定でき、その設定のためにIAMへの読み取り権限が必要です。なお、サービスロールを設定しない場合、CloudFormationはユーザーに割り当てられている権限を使用してリソースを構築します。
今回の検証では、AWS管理ポリシーの「IAMReadOnlyAccess」を使用します。
実行してみよう
作成したテンプレートを使ってCloudFormationで実行してみましょう。
まずCloudFormationの画面から[スタックの作成]をクリックします。
今回はデザイナーへ先ほどのテンプレートを張り付ける形で作成します。[デザイナーでテンプレートを作成]をクリックします。
[テンプレート]タブを開き、先ほどのテンプレートを貼り付けます。画面左上の[テンプレートを検証]ボタンをクリックすると、基本的な文法などが間違っていないかチェックしてくれます。
問題なければ画面左上の[スタックの作成]をクリックして進みましょう。
[次へ]ボタンをクリックして、
[スタックの名前]を入力します。入力後[次へ]をクリックします。
[スタックオプションの設定]が表示されますが、今回はデフォルトのまま作成しますのでそのまま[次へ]。
レビュー画面が表示されます。内容を確認して問題なければ[スタックの作成]をクリックしましょう。
正常にすべてのリソースが作成されると、ステータスに「CREATE_COMPLETE」が表示されます。
[リソース]タブから作成されたリソースの一覧が確認できます。
注意点
CloudFormationで作ったリソースの変更・削除について
CloudFormationで作成したリソースの更新・削除は、CloudFormationを介しておこなう必要があります。CloudFormationを介さず更新・削除を行うとテンプレートとリソースの状態に不一致が生じ、その後CloudFormationを通しての更新・削除ができなくなる可能性があります。
CloudFormationの便利なところ・大変なところ
実は私、CloudFormationを使うのは今回が初めてでした。その中で感じたCloudFormationの「便利なところ」「大変なところ」を共有します。
便利なところ
気軽に手早く環境を立てられる
テンプレートさえ作ってしまえば、複数のリソースをまとめて作成できます。マネジメントコンソールの画面を行ったり来たりしなくてよいので楽ですし、構築ミスも減らせます。
スタック単位でリソースをまとめて削除してくれる
作成同様、削除が簡単なところも魅力です。リソース同士の依存関係を考慮しなくてよいので、リソースの削除する順番を考える必要がありません。また、リソースを消し忘れて課金されていた…なんてことも減らせます。
大変なところ
テンプレートの作成
テンプレート自体の作成がなかなか大変に感じました。「どのパラメータが必須で、どのように記述するんだ…?」と公式ドキュメントとにらめっこしながら作成していました。やっと完成したテンプレートをウキウキでいざ流すと失敗の文字…。エラー文を基にテンプレートを修正してまた流して…を繰り返していたので、なかなか時間がかかりました。公式ドキュメントにあるサンプルのテンプレートをうまく参考にしたり、工夫が必要です。
まとめ
今回はCloudFormationの基本的な使い方を紹介しました。テンプレート自体を作るのは少し大変でしたが、一度作ってしまえば素早く環境を構築・削除ができるので便利です。各自作ったテンプレートを使いまわせますし、さらにチームで共有して活用できれば、品質確保や作業効率アップなどのメリットが出せるようになると思います。
次回は、CloudFormationの少し応用的な使い方を紹介します。