【AWS】CloudFormationを使ってみよう

Amazon Web Services,AWS CloudFormation

こんにちは。プラットフォーム技術部の近藤です。

AWSを使っていて
「サクッと検証できる環境を作りたい…」
「以前作った検証環境を使いまわしたかったけど、削除しちゃったな…」
そんなシーンは多いかと思います。

そんな時に便利なのが「CloudFormation」です。CloudFormationでリソース作成する流れを、簡単なWebシステムを想定した以下の構成を作成しながら紹介します。

CloudFromationで作成する構成

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~個別リソース編~

今回設定したテンプレートを、リソースごとに分けて紹介します。もう一度構成図を確認しておきましょう。

CloudFromationで作成する構成

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へのアクセスを均等に分配するような設定を記載してみました。

公式ドキュメントはこちら

補足

今回は上記のリソースを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の画面から[スタックの作成]をクリックします。

CloudFormationを使ってAWSリソースを構築する手順

今回はデザイナーへ先ほどのテンプレートを張り付ける形で作成します。[デザイナーでテンプレートを作成]をクリックします。

CloudFormationを使ってAWSリソースを構築する手順

[テンプレート]タブを開き、先ほどのテンプレートを貼り付けます。画面左上の[テンプレートを検証]ボタンをクリックすると、基本的な文法などが間違っていないかチェックしてくれます。

CloudFormationを使ってAWSリソースを構築する手順

問題なければ画面左上の[スタックの作成]をクリックして進みましょう。

CloudFormationを使ってAWSリソースを構築する手順

[次へ]ボタンをクリックして、

CloudFormationを使ってAWSリソースを構築する手順

[スタックの名前]を入力します。入力後[次へ]をクリックします。

CloudFormationを使ってAWSリソースを構築する手順

[スタックオプションの設定]が表示されますが、今回はデフォルトのまま作成しますのでそのまま[次へ]。

CloudFormationを使ってAWSリソースを構築する手順

レビュー画面が表示されます。内容を確認して問題なければ[スタックの作成]をクリックしましょう。

CloudFormationを使ってAWSリソースを構築する手順

正常にすべてのリソースが作成されると、ステータスに「CREATE_COMPLETE」が表示されます。

CloudFormationを使ってAWSリソースを構築する手順

[リソース]タブから作成されたリソースの一覧が確認できます。

CloudFormationを使ってAWSリソースを構築する手順

注意点

CloudFormationで作ったリソースの変更・削除について

CloudFormationで作成したリソースの更新・削除は、CloudFormationを介しておこなう必要があります。CloudFormationを介さず更新・削除を行うとテンプレートとリソースの状態に不一致が生じ、その後CloudFormationを通しての更新・削除ができなくなる可能性があります。

CloudFormationの便利なところ・大変なところ

実は私、CloudFormationを使うのは今回が初めてでした。その中で感じたCloudFormationの「便利なところ」「大変なところ」を共有します。

便利なところ

気軽に手早く環境を立てられる

テンプレートさえ作ってしまえば、複数のリソースをまとめて作成できます。マネジメントコンソールの画面を行ったり来たりしなくてよいので楽ですし、構築ミスも減らせます。

スタック単位でリソースをまとめて削除してくれる

作成同様、削除が簡単なところも魅力です。リソース同士の依存関係を考慮しなくてよいので、リソースの削除する順番を考える必要がありません。また、リソースを消し忘れて課金されていた…なんてことも減らせます。

大変なところ

テンプレートの作成

テンプレート自体の作成がなかなか大変に感じました。「どのパラメータが必須で、どのように記述するんだ…?」と公式ドキュメントとにらめっこしながら作成していました。やっと完成したテンプレートをウキウキでいざ流すと失敗の文字…。エラー文を基にテンプレートを修正してまた流して…を繰り返していたので、なかなか時間がかかりました。公式ドキュメントにあるサンプルのテンプレートをうまく参考にしたり、工夫が必要です。

まとめ

今回はCloudFormationの基本的な使い方を紹介しました。テンプレート自体を作るのは少し大変でしたが、一度作ってしまえば素早く環境を構築・削除ができるので便利です。各自作ったテンプレートを使いまわせますし、さらにチームで共有して活用できれば、品質確保や作業効率アップなどのメリットが出せるようになると思います。

次回は、CloudFormationの少し応用的な使い方を紹介します。

  • 株式会社アークシステムの予約・来訪管理システム BRoomHubs
  • 低コスト・短納期で提供するまるごとおまかせZabbix