Infrastructure as Codeを理解する(第2回)AWS CloudFormationの導入

2020.11.27 Cloud

こんにちは、クラウドCoEの西村です。

前回の記事では、Infrastructure as Code(以下、IaCと略します。)について紹介させていただきました。IaCを導入する目的やメリットについてはご理解いただけたかと思います。また、その中でIaCを実現するためのAWSサービスとして、AWS CloudFormationについて簡単に触れています。概念を理解しても、実際にインフラをコード化するイメージがわかない方もいらっしゃるかと思いますので、導入部分についてご紹介したいと思います。

ということで、今回のBTC Tech Blogの内容は「Infrastructure as Codeを理解する(第2回)AWS CloudFormationの導入」です。

本記事ではAWS CloudFormationはどういったサービスなのか、基本的な使い方と注意点についてまとめていきたいと思います。

 

AWS CloudFormationの概要

AWS CloudFormationは、テンプレートを使用して、アプリケーションに必要とされるAWSリソースをプロビジョニングできるサービスです。

プロビジョニングされたAWSリソースに対する変更も可能で、ソフトウェアの管理と同じ方法でインフラを管理できます。コード化することで、バージョン管理やコードの共有が可能となり、共同作業がしやすくなります。

また、AWS CloudFormationの利用に対する追加料金は発生しません。

特徴

AWS CloudFormationには、以下のような特徴があります。

  • JSON/YAMLで記述可能
  • ChangeSetで既存リソースに与える影響を確認可能
  • 依存関係を自動で管理

特に、テンプレート化したことで実際の環境とテンプレート間で差異が生じることはよくありますので、ChangeSetの機能は便利です。

AWS CloudFormationは多くのAWSサービスをサポートしていますが、すべてのサービスをコードに落とし込めばいいというわけでもないと思います。また、テンプレートは一枚岩で作成するのではなく、ライフサイクル別に分割し、ネストスタック形式で実装すべきです。

 

コンセプト

AWS CloudFormationを利用する場合の全体像のイメージは以下の通りです。

YAML/JSON形式でテンプレートを作成(リソースを定義)し、AWS CloudFormationを利用してスタックと呼ばれる単位でリソース群をプロビジョニングします。

テンプレートの要素

テンプレートはAWS CloudFormationの心臓部となるもので、スタックの設計図です。
リソースを定義し、どのように起動するかをJSON/YAML形式で記述します。
リソース間の依存関係はAWS CloudFormationが自動で判別しますが、テンプレート上では依存関係がない形で定義していても、実際のリソース間に依存関係が必要な場合は、後述する「DependsOn」などで明確に前後関係を作る必要があります。

---
AWSTemplateFormatVersion: "version date"

Description:
  String

Metadata:
  template metadata

Parameters:
  set of parameters

Mappings:
  set of mappings

Conditions:
  set of conditions

Transform:
  set of transforms

Resources:
  set of resources

Outputs:
  set of outputs

多用する要素はParameters、Resources、Outputsですので、これらを取り入れたサンプルテンプレートを以下に記載します。

---
AWSTemplateFormatVersion: 2010-09-09

Description: Creates New VPC

Parameters:
  VpcCidr:
    AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$"
    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
    Default: 10.0.0.0/16
    Description: CIDR block for the VPC
    Type: String
  VpcTenancy:
    AllowedValues:
    - default
    - dedicated
    Default: default
    Description: The allowed tenancy of instances launched into the VPC
    Type: String
  PublicSubnetCidr:
    AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$"
    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
    Default: 10.0.10.0/24
    Description: CIDR block for Public subnet
    Type: String

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Join [ '', [ 'Vpc / ', !Ref 'AWS::AccountId' ] ]

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnetCidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Join [ '', [ 'PublicSubnet / ', !Ref 'AWS::AccountId' ] ]
        - Key: SubnetType
          Value: Public
      VpcId: !Ref Vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Join [ '', [ 'InternetGateway / ', !Ref 'AWS::AccountId' ] ]
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref Vpc

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachInternetGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Join [ '', [ 'PublicRouteTable / ', !Ref 'AWS::AccountId' ] ]
        - Key: Network
          Value: Public
      VpcId: !Ref Vpc
  PublicRouteTableAssociation0:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable   

Outputs:
  Vpc:
    Value: !Ref Vpc
  VpcCidr:
    Value: !Ref VpcCidr
  PublicSubnet:
    Value: !Ref PublicSubnet

Parameterで入力変数として受け取った値をResourceセクションの組み込み関数「!Ref」などで受け取っています。
「!Join」は一連の文字列を結合する組み込み関数、「AWS::AccountId」はアカウントIDを取得する疑似パラメータです。
また、OutputではParameterやResourceで定義したリソース情報を出力できます。

多種多様な疑似パラメータや組み込み関数も用意されていますので、AWS CloudFormationの扱いに慣れてきたら試してみるとよいかと思います。

スタック

テンプレートからプロビジョニングされるリソース群で、スタック単位でリソースの管理が可能です。定義されたリソースを一斉に作成/削除することができるため、運用負荷を軽減できます。

スタックの展開にはマネジメントコンソール、コマンドラインツール、各種SDKが利用できますので、慣れている方法で実行することをお勧めします。

AWS CloudFormation利用時の注意点

AWS CloudFormationを利用してリソースを作成した場合、テンプレートで定義したリソースについては、テンプレートを更新していくことになります。
基本的にはマネジメントコンソールからリソースの設定を変更しません。
マネジメントコンソールから設定を変更し、テンプレートに変更を反映していない状態でスタックを展開してしまうと、デグレが発生したり、エラーにより更新ができなくなってしまったりすることがあります。

また、テンプレートは一枚岩で作成するのではなく、「依存関係」、「ライフサイクル」、「ステートレス/ステートフル」、「所有権」によって分割することが望ましいです。
テンプレートを分割して運用していく際に使用する機能としては、「クロススタック参照(Cross Stack Reference)」と「ネストスタック(Nested Stacks)」が用意されています。
特に定義するリソースが増加し、テンプレートが巨大化していく可能性のある場合はこれらの機能を活用しましょう。

さいごに

これまでAWS CloudFormationの基本について説明してきました。

インフラをコード化することで全く同じ構成のインフラを簡単に再現できたり、設定パラメータをコードとして管理したりすることができたりといったメリットがあります。
しかし、適切にテンプレートを管理・運用していかないと逆に運用負荷が上がってしまうこともありますので、しっかりと理解した上で活用していただければと思います。

では、また次の記事でお会いしましょう。