⛩️

[初学者向け]CloudFormationに挑戦しようと思い立った人へ向けCloudFomationドキュメントとの向き合い方

2023/08/17に公開
3

Cloudformationテンプレートってどうやって作るの?

という方へ向けての記事です。

「あくまでも私はこんな風にやっています。」になりますので正解ではありませんが、それでもよろしければ是非お付き合いいただければと思います。

※普段固めの表現を好んでしてしまいがちの為、今回はわかりやすさ重視で崩した口調でざざっと書き上げております。

最初にテンプレってこんな感じという例を載せてみます

CloudFormationで2AZにパブリック・プロテクテッド・プライベートのサブネットとルート・GWを作成。

一応テンプレで作成されるものの構成図も添えてみます。

この上によく配置するリソースの一例として、

図の上段のパブリックサブネットにNatGatewayやELB、場合によっては踏み台用のEC2を配置、
中段のプロテクテッドサブネットにEC2やECSなどを、
下段のプライベートサブネットにDBインスタンスを配置するなどよく見る構成なのではと思います。

後で載せるテンプレートの中には、構成図には描画されているもの(NatGatewayなど)と、そうでないもの(EIPなど)がありますので、構成図とテンプレを見比べてみてください。

大体の方は何から何まで書く事はせず省略する傾向にありますが、中には稀ですが意図して全て丁寧に書く場合もあるかと思います。

テンプレート

CloudFormationはJSONとYAMLで記載可能ですが、コメントが書けたり、閉じ括弧の存在に悩まされないで済むなどYAMLの方が圧倒的にメリットが多い為、YAMLで記載します。
(100人いたら99人はYAMLだと思っていいくらいです。)

ちなみにどちらでテンプレートを作成したとしても、ネット上で「JSON YAML変換」と検索してヒットしたツールを使ったり、AWSサービス上でもCloudFormationデザイナーと呼ばれる機能が存在するのでそこでどちらにも変換する事が可能です。

最初に上の構成図に対応したテンプレ例を畳んでおきます。
今回はこれを全部作る訳ではありませんので
ざっと眺めて「ふーん」と思ったらそっ閉じしていただいて構いません。

完成.yml
完成.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Create Vpc and public subnets, protected subnets, and private subnets in 2 availability zones.
#  ============== メタデータ ==============
Metadata: # 一段下のパラメータの入力を求めるコンソール画面の並び順などを統制。
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - 
        Label:
          default: Stack(CloudFormation)
        Parameters:
          - PJPrefix
      - 
        Label:
          default: VPC
        Parameters:
          - VPCCidrBlock
      - 
        Label:
          default: PublicSubnet
        Parameters:
          - PublicSubnet1CidrBlock
          - PublicSubnet2CidrBlock
      - 
        Label:
          default: ProtectedSubnet
        Parameters:
          - ProtectedSubnet1CidrBlock
          - ProtectedSubnet2CidrBlock
      - 
        Label:
          default: PrivateSubnet
        Parameters:
          - PrivateSubnet1CidrBlock
          - PrivateSubnet2CidrBlock
#  ============== パラメータ ==============
Parameters: # スタック作成者に入力を求める項目を作成。
  PJPrefix: # リソース名などの接頭辞となる文字列の入力を求める。
    Type: String
    Default: testprefix
    MinLength: 1
    MaxLength: 10
    AllowedPattern: "[a-z][a-z0-9]*"
    ConstraintDescription: Invalid input value for the PJPrefix.

  VPCCidrBlock: # VPCのCIDR範囲。
    Type: String
    Default: 10.1.0.0/16
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid VPCCidrBlock.

  PublicSubnet1CidrBlock: # パブリックサブネット1のCIDR範囲。
    Type: String
    Default: 10.1.0.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid PublicSubnet1CidrBlock.

  PublicSubnet2CidrBlock: # パブリックサブネット2のCIDR範囲。
    Type: String
    Default: 10.1.1.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid PublicSubnet2CidrBlock.

  ProtectedSubnet1CidrBlock:  # プロテクテッドサブネット1のCIDR範囲。
    Type: String
    Default: 10.1.2.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid ProtectedSubnet1CidrBlock.

  ProtectedSubnet2CidrBlock:  # プロテクテッドサブネット2のCIDR範囲。
    Type: String
    Default: 10.1.3.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid ProtectedSubnet2CidrBlock.

  PrivateSubnet1CidrBlock: # プライベートサブネット1のCIDR範囲。
    Type: String
    Default: 10.1.4.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid PrivateSubnet1CidrBlock.

  PrivateSubnet2CidrBlock: # プライベートサブネット2のCIDR範囲。
    Type: String
    Default: 10.1.5.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid PrivateSubnet2CidrBlock.
#  ============== リソース ==============
Resources: # 作成するリソース
#  -------------- VPC --------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-VPC
#-------------- インターネットゲートウェイ --------------
# IGW
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-IGW

# アタッチメント
  IGWAttach: # どこのVPCにアタッチするか。
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref IGW
      VpcId: !Ref VPC
#  -------------- EIP --------------
  NGW1EIP: # NatGateway用のEIP1。
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-NGW1EIP

  NGW2EIP: # NatGateway用のEIP2。
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-NGW2EIP
#  -------------- サブネット --------------
#  パブリック
  PubSN1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet1CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ] # そのリージョン内のサブネットの0番目を指定。
      MapPublicIpOnLaunch: true # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか。
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PubSN1

  PubSN2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet2CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PubSN2

#  プロテクテッド
  ProSN1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref ProtectedSubnet1CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-ProSN1

  ProSN2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref ProtectedSubnet2CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-ProSN2

#  プライベート
  PriSN1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PrivateSubnet1CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PriSN1

  PriSN2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock:  !Ref PrivateSubnet2CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PriSN2
#  -------------- ルートテーブル --------------
#  ルートテーブル
  PubRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PubRT

  ProRT1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-ProRT1

  ProRT2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-ProRT2

#  ルート
  PubRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PubRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

  ProRoute1: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref ProRT1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NGW1

  ProRoute2: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref ProRT2 
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NGW2

# アソシエーション
  PubSN1Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PubSN1
      RouteTableId: !Ref PubRT

  PubSN2Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PubSN2
      RouteTableId: !Ref PubRT

  ProSN1Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref ProSN1
      RouteTableId: !Ref ProRT1

  ProSN2Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref ProSN2
      RouteTableId: !Ref ProRT2
#  -------------- NATゲートウェイ --------------
  NGW1: # NatGatewayは2AZのパブリックサブネットそれぞれに必要。
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NGW1EIP.AllocationId 
      SubnetId: !Ref PubSN1
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-NGW1

  NGW2: 
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NGW2EIP.AllocationId
      SubnetId: !Ref PubSN2
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-NGW2

実際どんな風に作っているか(手順と説明)

①ファイル自体を作る

最初は自分の好きなエディタを開きます。
中身は空のファイルに名前をつけて保存してください。
「.yaml」か「.yml」であれば拡張子以前は英語でも日本語でも構いません。

「test.yaml」
「test.yml」
「テスト.yaml」
「テスト.yml」

などなんでもOKです。私は短く済むので.ymlにしています。

②最初の宣言文

次に実際に中身を書いていきますが、最初どこから手をつけていいかわからないと思います。
最初はこれです。

作成開始.yml
AWSTemplateFormatVersion: 2010-09-09

「さぁ、CloudFormationテンプレート作っちゃうよ」という宣言をするノリで毎回これを一番上に書きます。

「ヴァージョン」とありますがこの日付は私の知る限りかなり昔から変更されていません。
2010-09-09じゃないテンプレートを見た事がありません。

そして大体過去に自分が作ったテンプレか、公式ドキュメントやネット上のどこかからコピペです。
丁寧に手打ちしたりはしません。

③テンプレ自体の説明分

次は何をしたらいいでしょうか?

作成中2.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Create Vpc and public subnets, protected subnets, and private subnets in 2 availability zones.

先ほどの宣言の下に「Description:(説明)」を生やしてみました。
なくてもOKです。このテンプレのファイルをローカルで見返した時や、実際にこのテンプレを使ってリソースをデプロイした後、UPしたテンプレの中身になんて書いてあったかをマネジメントコンソールから確認出来るのですが、その時に「あぁ、こういうプロジェクトのこういうリソースを纏めてつくったのね。わかり」となる為の項目です。

繰り返しになりますが省いてもなんら問題ありません。

④リソース自体

次は実際のリソース(=モノ)を作っていきましょう。

先ほどのテンプレ例の各ブロック一番上位(インデント無しで一番左にピッタリくっ付いている部分)だけを取り出すと以下のようになります。

補足.yml
AWSTemplateFormatVersion: 2010-09-09
Description: #ほにゃらら
Metadata: 
#ほにゃらら
Parameters:
#ほにゃらら
Resources: 
#ほにゃらら

というブロックで書いてありました。
実際にはこの他にConditionsやOutputsなどのセクションも存在するのですが、覚える事が沢山あるとよくないので今回は触れません。

※補足ですが上記の「#ほにゃらら」のように「#」から書き始めたテキストはコメントとして扱えます。

一番大事なのは一番下の「Resources: 」です。
リソースと一言に言っても様々なものが存在します。

EC2やRDSのような実体として意識されていそうなものから、VPCやサブネットのような、初学者にとって概念に近く感じられるもの。ルートテーブルやセキュリティグループなどルールに近いもの。「これとこれを紐づけておくれ」という「〇〇Association」のようなものまで幅広いです。

(難しく考える必要はないですが)
メチャクチャ大雑把にいうとAWSのサービスや設定全部をここで作りあげます。

他の「Metadata:」 と「Parameters:」は後で触れるので一旦頭から消してしまいましょう。

一旦これを見てもらいます。

作成中3.yml
AWSTemplateFormatVersion: 2010-09-09
Description: hogehoge
Resources: # 作成するリソース
#  -------------- VPC --------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-VPC

最初に注目するのが、

  VPC:

と書いてある部分ですが、これは「論理名」といいます。
日本語文字とかでなく、他のリソースと被っていなければなんでもいいです。(数字スタートが可能かや文字数に制限があるかなどはドキュメントを参照ください。)

例えば

作成中3改.yml
AWSTemplateFormatVersion: 2010-09-09
Description: hogehoge
Resources: # 作成するリソース
#  -------------- VPC --------------
  hogehugapiyo123456DAYO:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-VPC

でも構いません。

大事なのが次の行です。

    Type: AWS::EC2::VPC

ここが「何作るの?」を決定する部分です。

以下でその一覧が確認出来ます。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

これからこのテンプレートリファレンスとは長いお付き合いをする事になります。
このドキュメントがないとCloudformationテンプレートを作るのは不可能と言っても過言ではないです。

例えばRDSを作りたい場合、Ctrl+Fで「Relational」とブラウザ上で検索してみてください。




こんな風にHITしますのでクリックしてみましょう。

ずらっとRDSに関係するリソースタイプが表示されます。


この中からクラスターを作成したいなら「AWS::RDS::DBCluster」を、単独のDBインスタンスを作成したいよ、という場合は「AWS::RDS::DBInstance」を先ほどの「Type:」の後に続けて貼り付ければ良いという訳です。

そしてドキュメントは英語で表示されますが、ブラウザに日本語のアドオンなどを入れて日本語に翻訳して読みましょう。(英語の方が読みやすい方はそのままでOK)

コピーしてテンプレに持っていきたい部分はうまい事英語のまま翻訳されないようドキュメントはなっていますので心配不要です。

ドキュメントには以下のように「フィルタービュー」という箇所が存在します。

JSONかYAMLかその両方かを選択出来ますが、毎回「YAML」を選択しましょう。

こうする事でJSONで書かれたテンプレート例をHiddenにしてくれて、スッキリします。

少し下にスクロールすると
「構文」という項目の下に「YAML」という箇所が登場します。

簡単に言うと、設定可能な細かい詳細項目が全部ここにあります。

この各項目を「プロパティ」というので
これを設定する際、

Properties:

として以下にそれぞれのプロパティと、それをどうしたいか書いていく訳ですね。

その隣には赤字で「文字列だよ」とか「True/False」だよという「型」
が書いてあります。

AWSに詳しくなってどの項目が何を表すかに理解が及ぶようになれば
「そうだねここはNameだから文字列だろうね」
とか
「これがいるかいらないかの二択だからBoolean」だね

とか自然と見なくてもアタリがつくようになりますが、ここに明示されているという事を一旦覚えておきましょう。

これもいちいち手書きで書く事はまずありません。このドキュメント内や、先人のありがたいブログからコピペです。

そうしてできたのが以下です。

VPCリソースだけ抜き出した部分.yml
  hogehugapiyo123456DAYO:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-VPC

なんだかプロパティがRDSに比べて少ないと思ったかもしれませんが、実際にはVPCにだって沢山の設定可能なプロパティがあります。

ただ、「設定しなきゃダメよ!VPC作るのにこれがないなんてあり得ない!」というプロパティと、
「付け足したかったらあなたのお好きに」というプロパティ、その他「AをTrueにしているならBも設定しなきゃダメよ」というプロパティがあります。

さっきのRDSのドキュメントを開きっぱなしかと思いますので、少し下にスクロールしてみると、

こんな風にそれぞれのプロパティの説明と、必須かどうか、タイプ(型)がちゃんと記載されています。

場合によっては許容値として「hoge | huga | piyo」だけ設定出来るよなどと書いてある場合もあります。

中には

このように「タイプ: DBInstanceRoleのリスト」等として青色のリンクをクリック出来るようになっている場合がありますが、この場合は階層化されているわけです。

クリックするとまた新たなページが登場しました。

要するに、以下のように新階層が登場する度に掘り下げて(一段右にインデントを増やして)設定していくわけです。

中には今回のように「リスト」形式になっている為、先頭に「- 」をつけて複数指定出来る書式などありますが、その辺はエラーを何度か経験している内に慣れます。2、3度リソース作成を失敗する経験だけで感覚掴めますのでご心配不要です。

JSON形式が指定されている場合は、先ほど書いたようにJSONで書いたり、拾ってきたものをYAMLに変換するツールを使って貼付するだけです。

hogeRdsInstance:
 Type: AWS::RDS::DBInstance
 Properties: 
  AssociatedRoles: 
    - FeatureName: String
      RoleArn: String

先ほどのVPCリソースの中にこんな風に書いてある部分があったかと思います。

      CidrBlock: !Ref VPCCidrBlock

やっていくと「何これ実際の値じゃないじゃん」という事に気が付くと思います。

詳しくは割愛しますが、「直接書くのめんどいからVPCCidrBlockっていう論理名(或いは後述するパラメーター)のとこからそれっぽい値参照(Reference)して埋めといてよ!宜しく」という書き方になります。

こんな魔法がいくつかあります。組み込み関数といいます。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

他にも「自分のAWSのアカウントID覚えてなーい(見にいくのだるい)」という時に使える「AWS::AccountId」などの擬似パラメーターというのもあります。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html

この辺は慣れてきたら多用してください。

後はサブネットをつくろうと思ったら「一体どこのVPCに作りたいねん」などと逐一聞かれ一生懸命埋めている内にAWSのリソース同士の親子関係というか依存関係性というか色々な事がわかっていきます。

⑤ParametersとMetadata

先ほど後回しにしていた部分です。

ざっくり言うと、Parametersは「テンプレにベタ書きしたくない値」や、一つのテンプレを使い回す想定且つ、作成都度別の値を埋めたい等の目的がある時に

「テンプレを実行する人に手入力してよって事にしといちゃおう」という為のもの。

Metadataはそれを「マネコンに表示させる時バラバラだと汚いから順番を決めておこう」というだけの項目です。

最初のテンプレを実行すると、パラメーター入力画面はこのように表示されます。

以上でした。

雑な説明にはなりますが、案外こんな感じの方が読んでて気疲れしないのかもと思いざっと書き殴りました。Cloudformationに興味を持たれた方の一助になれば幸いです。

Discussion

ueue

参考になりました〜
今度、知り合いと一緒に勉強する時、一緒に見ながら取り組んでみます。

katokato

とんでもないです〜✨何か不明点あれば一緒に考えますので是非お声がけください^^