AWS再入門ブログリレー AWS CloudFormation編

2020.08.28

こんにちは、コンサル部の鈴木(純)です。

当エントリは弊社コンサルティング部による『AWS 再入門ブログリレー 2020』の 19日目のエントリです。

このブログリレーの企画は、普段 AWS サービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。

AWS をこれから学ぼう!という方にとっては文字通りの入門記事として、またすでに AWS を活用されている方にとっても AWS サービスの再発見や 2020 年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。

では、さっそくいってみましょう。19日目のテーマは『AWS CloudFormation』です。

CloudFormationとは

 

インフラをコードで管理できるInfrastructure as Code(IaC)を実現するAWSの構成管理サービスです。 JSONかYAML形式でテンプレートを作成し、実行することでAWSサービスをプロビジョニングすることができます。 テンプレートを実行すると、CloudFormationではスタックと呼ばれる単位でリソースを管理してくれます。

料金

CloudFormation自体は無料で利用することができます。 ただし、CloudFormationを使って作成したリソースは通常通り課金されるため注意しましょう。

対応リソース

CloudFormationは全てのAWSリソースに対応しているわけではないので、公式ドキュメントを確認しましょう。 新機能などは対応していない場合がありますが、普通に利用するものは概ね対応しています。

Cloudformationを利用するメリット

CloudFormationを利用するにあたり、一度メリットについて確認しておきましょう。

インフラの管理が簡単に

AWSをコンソールから利用していると構築する際にも手作業で行っている場合は、どうしても人が行うことなのでオペミスが発生します。 それがCloudFormationを利用すると、テンプレートで一度リソースを定義してしまえば、全て自動でリソースをプロビジョニングしてくれるのでインフラの構築が簡単になります。

さらに、環境を削除したい場合もスタックを削除するだけで依存関係を解消しながら全てのリソースを削除してくれるので、開発環境など一時的にしか利用しない環境であれば、使い終わったあとは削除することでランニングコストを削減することも可能です。

すばやく複製が可能

CloudFormationで利用するテンプレートは別リージョン、別アカウントにも簡単に展開することができます。 一度テンプレートさえ作成してしまえば、同じような構成の環境を作成する際にすぐ環境を構築することができるため、大幅な効率化が見込めます。

コードで管理できる

JSON、YAMLどちらもテキストなのでgitなどでコードと同じように管理することができます。 そのため修正をしたとしてもバージョン管理ができるため、何か問題があれば元のバージョンに戻すことも可能です。

インフラを可視化できる

従来のインフラ構築は手作業で変更してきたものを積み上げて管理せざるを得ないものであり、こうした過去の状態を含めると管理が非常に難しいものでした。 しかしコードでインフラが構築できるようになると、一度コードで管理したものを手で修正しない限りは、コードからインフラを確認することが可能になります。

と、CloudFormationを利用することでハッピーになれることがたくさんあります。是非CloudFormationを活用して、Infrastructure as Codeを体験してみてください。

それでは、実際にCloudFormationのテンプレートを書いていきましょう!

CloudFormationのテンプレート

それではCloudFormationの肝であるテンプレートについて詳しくみていきます。 少しボリュームがあるので気合を入れていきましょう。

テンプレートのセクション

CloudFormationのテンプレートは以下の要素から成り立っています。

セクションがたくさんありますが、必須で利用する必要があるのはResourcesだけです。 テンプレートのバージョンはお作法として書いておきましょう。

他のセクションは任意で利用するものでなので本エントリでは説明は省きます。実際に利用する際に調べて利用してみてください。

テンプレートを理解する

CloudFormationではJSONとYAMLどちらかを選ぶことができますが、可読性が高いことやコメントが記述できることから、YAML形式で記述することをおすすめします。 ここではサンプルのテンプレートを用意しました。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  # VPC
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub sample-vpc
  # Subnets
  PublicSubnetAZ1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.0.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet1'
  PublicSubnetAZ2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1c'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.1.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet2'

  # Internet Gateway
  Igw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: 'sample-igw'
  IgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref Igw

  # RouteTable
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: IgwAttachment
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: 'sample-public-rtb'
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: IgwAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref Igw

いきなり長いテンプレートが出てきましたね。 少し細かくみていきましょう。

Resourcesの記述方法ですが、以下の構文から成り立っています。

Resources:
<論理ID>:
Type: <リソースタイプ>
Properties:
<リソースプロパティ>

論理ID

論理IDは英数字(A-Za-z0-9)を使って自分で定義するもので、テンプレート内で一意に決める必要があります。

リソースタイプ

リソースタイプは「この論理IDはどんなAWSリソースを定義するか」というのを宣言する場所です。

リソースプロパティ

リソースプロパティは、宣言したリソースに対して必須の情報を記述したり、任意で設定できる項目を定義できます。

少し分かりづらいので、VPCを例に考えてみましょう。 例えば、先ほどのテンプレート内でVPCを作成している部分はこのようになっています。

  # VPC
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub sample-vpc

これを先ほどの構文に当てはめてみると、以下のような構成になっていることが分かるでしょうか。`

論理ID: Vpc
リソースタイプ: AWS::EC2::VPC
リソースプロパティ: CidrBlock: '10.0.0.0/16' EnableDnsSupport: true…

論理IDとリソースタイプはとても分かりやすいですね。単純に任意の名前をつけ、VPCというリソースを宣言しています。

しかしリソースプロパティについては多くのものが設定されていて、分かりづらいため少し掘り下げましょう。

まずリソースプロパティに書く項目ですが、必ずテンプレートリファレンスを参照してください。 CloudFormationを書く場合は、まずテンプレートリファレンスとお友達になりましょう。

テンプレートリファレンスからVPCの項目を探してみると、AWS::EC2::VPCというページを見つけることができます。

各リソースごとのページにはJSONとYAMLの構文例と、リソースプロパティに設定できる値、後述する戻り値について記載されています。 例えば今回リソースプロパティで定義しているCidrBlockをリファレンスで確認してみると

CidrBlock VPC のプライマリ IPv4 CIDR ブロック。 必須: はい タイプ: 文字列

と記載があるため、今回のテンプレート上では'10.0.0.0/16'という文字列を定義しています。

これを作成したいリソースごとに繰り返すことで、作成したい環境のテンプレートが作成できるようになります。

リソース属性

Resourcesの構文にないDependsOnという言葉も登場しているのに気が付いたでしょうか。 これはリソース属性と呼ばれ、追加の動作や関係を制御などを定義することができます。 DependsOnの場合は特定のリソースが他のリソースの後に作成するように指定することができます。

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: IgwAttachment
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: 'sample-public-rtb'

この場合はPublicRouteTableはインターネットゲートウェイがVPCにアタッチされた後に、作成するということを明示的に宣言しています。 基本的にはCloudFormation側で依存関係を解決してくれるのですが、明示的に宣言する必要があるリソースも一部存在しています。

DependsOnの他にも便利なリソース属性があるため、興味のある方はこちらのドキュメントも参照してみてください。

組み込み関数

組み込み関数は実行するまで分からない値を代入する時に利用します。 サンプルのテンプレート上では!Refという組み込み関数が何度も登場していますね。

PublicSubnetAZ1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.0.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet1'

例えば、サブネットを作成したいときには、「どのVPCに作成するか?」というのをVpcIdに宣言しなければなりません。 しかし、VPCのIDはリソースが作成された後にしか確認することができませんよね。

その時に、!Refの後にVPCを定義してる論理IDを書いてあげることで、VPCIDを返してくれるようになります。 Refがどんな値を返してくれるかはテンプレートリファレンスの戻り値に書かれていて、VPCの場合はVPCのIDとなっています。

他にもRefでは取れない他の値を返してくれるものなど便利な組み込み関数が多くあるので、こちらも合わせてドキュメントを見て活用してみてください。

擬似パラメータ

テンプレートには登場していませんが、擬似パラメータという事前にCloudFormationによって定義されているパラメータが存在します。 リージョンや、アカウント名、スタック名などRef関数の引数として指定してあげることでパラメータを返してくれます。

!Ref 'AWS::Region'→ap-northeast-1
!Ref 'AWS::AccountId'→999999999999

これらをうまく活用することで、より汎用性の高いテンプレートにすることが可能です。 CloudFormationに慣れてきたら、少しずつ擬似パラメータを活用してみましょう。

CloudFormationの実行

テンプレートさえ理解できれば、CloudFormationの実行は非常に簡単です。 AWSへログインして、東京リージョンからCloudFormationを開きましょう。 先ほどのサンプルで用意したテンプレートを拡張子.yamlで保存。「テンプレートファイルのアップロード」からファイルをアップロードして次へを押下します。

スタック名を入力しましょう。今回は「SampleStack」とました。その後は全てデフォルトのまま進めて大丈夫です。

最後にスタックの作成をクリックすると、CloudFormationがテンプレートで定義したリソースを作成してくれます。

テンプレートをマルチリージョン対応にしてみる

これだけでも十分いいのですが、もう一歩テンプレートを編集してみます。 このテンプレート、実は東京リージョンでのみしか動きません。 試しにバージニア北部で実行するとこのようにエラーとなります。

なぜでしょうか?

その理由は、サブネットを作成するときにのアベイラビリティゾーンに固定値でap-northeast-1aap-northeast-1cを書いてしまっているからです。

  PublicSubnetAZ1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.0.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet1'
  PublicSubnetAZ2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1c'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.1.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet2'

せっかく手間をかけてテンプレートを作成するなら、どのリージョンでも使えるようにしたいですよね。

そんなときは先ほど紹介した組み込み関数と擬似パラメータを駆使して、マルチリージョン対応にしてみましょう。

  PublicSubnetAZ1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select 
        - 0
        - Fn::GetAZs: !Ref 'AWS::Region'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.0.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet1'
          
  PublicSubnetAZ2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select 
        - 1
        - Fn::GetAZs: !Ref 'AWS::Region'
      VpcId: !Ref Vpc
      CidrBlock: '10.0.1.0/24'
      Tags:
        - Key: Name
          Value: 'sample-public-subnet2'

!Ref 'AWS::Region'は実行したリージョンの文字列、 Fn::GetAZsはリージョンにあるアベイラビリティゾーンをアルファベット順にしたリスト、 !Selectはリストから1つのオブジェクトを返す関数です。

Fn::GetAZsで取得できるバージニア北部リージョンのアベイラビリティゾーンはこんな感じのリストになってます。

[us-east-1a,us-east-1b,us-east-1c,us-east-1d,us-east-1e,us-east-1f]

!Select関数を使うと、この中のリストから指定したインデックスから値を取得することができます。

今回のテンプレートではインデックスに0と1を指定しているので、us-east-1aus-east-1bが取得できます。 実際に実行してみると、問題なく実行することができました。

ちょっと複雑ですが、使いこなせると柔軟にテンプレートが書けるようになるので是非活用してみてください。

その他の機能について

最後にここでは紹介できなかった、便利な機能についてご紹介します。

変更セット

今回はスタックの作成のみでしたが、スタックを更新したい場合もあると思います。 その場合に変更セットを使えば、更新内容によってスタック管理されているリソースにどんな影響があるかを確認することができます。 スタックの更新画面でプレビューとして確認することもできるので、更新をかける前に一度変更セットを確認しましょう。

ドリフト検出

スタックで管理しているリソースに手動やCLIで変更があった場合に検出することが可能です。 最初はCloudFormationで管理で管理していたものを、誤って手動で変更してしまった際にはテンプレートとの差分が分からずに管理できなくなることがあります。 実際のリソースとCloudFormationで管理しているスタックの内容と乖離がある部分を可視化することができます。

スタックセット

CloudFormationを複数のリージョンやアカウントに実行したい場合に便利な機能です。 先ほど紹介した組み込み関数や擬似パラメータを活用すれば、様々な環境に一括でスタックを作成することができます。 全てのリージョンでCloudTrailやConfigを有効化するケースなどが考えられますね。

既存リソースの取り込み

CloudFormation以外で作成したリソースをCloudFormationで管理できるようにするインポート機能です。 CloudFormationを利用して運用する場合は、全ての変更をコード上で管理するのが前提ですが、どうしても手作業やCLIでリソースを作成する場合がありますよね。 そんなリソースをCloudFormationでも管理できるようになる機能で、テンプレートに既存のリソースと同様の内容を追加すれば、スタック管理にすることができます。

CloudFormation デザイナー

CloudFormation デザイナーはCloudFormation テンプレートを作成、参照、および変更するためのグラフィックツールです。ドラッグアンドドロップでリソースを追加するとJSONやYAML形式でコード化することができます。 チュートリアルも用意されているので、一度試してみると使い勝手が分かるかと思います。

小ネタですが、デザイナーで利用できるエディタでJSONとYAMLの形式を変換できます。参考に見つけたコードがJSONだった場合に変換用としても使い勝手がいいです。

最後に

入門ということで、基本となるテンプレートの書き方を中心にまとめてみました。 CloudFormationは入門のハードルが非常に高いサービスではありますが、一度覚えてしまえば苦労に見合うだけのメリットがある破壊力のあるサービスです。

これまでテンプレートを書くのをためらっていた方が一歩踏み出すきっかけになれば嬉しいです。

以上、『AWS 再入門ブログリレー 2020』の 19日目のエントリ『AWS CloudFormation』編でした。次回 (8/31) は平野さんの「Amazon CloudFront」の予定です。お楽しみに!!