【CloudFormation】Rainを利用してネストスタックをらくらくデプロイしよう!

どもども、十数年ぶりに音楽ゲームにのめり込み老を感じている今日この頃です。やっぱりゲームセンターの雰囲気はとてもいいですね。


皆さん、CloudFormationは利用されておりますでしょうか。


現職ではAWSリソースの作成にTerraformを利用することがほとんどなのですが、現場やスキルセットによってはCloudFormationを選択されている会社様も多いのではないでしょうか。


CloudFormationはテンプレートをエディタで作成しつつ、デプロイはコンソール上で行うという行ったり来たりのジレンマがあります。このデプロイやスタックのマネジメントに非常に強力なツールが以前の記事で紹介した"Rain"です。


Rainの基本説明は下記記事を参照ください!
【AWS】CloudFormation実行ツール”rain”がとても素晴らしい件


今回はそんな便利ツール"Rain"を利用してネストスタック構成のCloudFormationもらくらくデプロイしてみましょう!

ネストスタックの特徴

実践の前に簡単な事前説明です。すぐにデプロイしたい方は読み飛ばしても問題ありません。


さて、前のセクションでも紹介した通り僕はすでにrainを利用してローカルのテンプレートからCloudFormationをデプロイし、リソースの作成まで行っています。


しかし、なぜ今回新たに記事を作成しているかというとCloudFormationネストスタックのデプロイにある特徴があるためです。


ネストスタックされたテンプレートはリソース作成の起点となる親テンプレートファイルにて実際にリソースを作成する子テンプレートを指定し、リソースを作成します。

root.yaml (親テンプレート)を指定してデプロイ
↓
root.yamlはs3.yamlを呼び出す
↓
s3.yamlは実際にS3バケットを作成する

つまり親テンプレートはスタック作成時、子テンプレートがどこに存在しているかを把握しておく必要があります。


そこで親テンプレートに子テンプレートが存在する場所をあらかじめ記載しておくわけですが、CloudFormationデプロイ中に子テンプレートを呼び出すことになるため子テンプレートもAWS内のどこかに保管しアクセスできるようにする必要があります。


という訳で子テンプレートは事前にどこかのS3バケットに保存しておき、親テンプレートからS3バケット内のファイルを指定して呼び出すということになります。


ここである疑問が浮かびます。Rainで単体のテンプレートファイルをデプロイする際はローカルから指定していたわけですが、S3にテンプレートを保存しなければならない今回のケースでは一体どのようにデプロイすればいいのかと。


それでは実際にネストスタック構成のCloudFormationをRainでデプロイし、こちらの疑問も解消していきましょう。

使用するテンプレートファイル

今回使用するサンプルテンプレートファイルをまず紹介します。


今回はCloudFormationを利用し特定のリファラに対しGetオブジェクトを許可するバケットポリシーが定義されたS3バケットを作成してみます。ファイル構成は下記の通りです。

.
├── child-templates
│   ├── s3-bucket-policy.yaml
│   └── s3.yaml
└── root.yaml

一旦親テンプレートは置いておき、まずは実際にリソースを作成する子テンプレートから見てみましょう。

S3バケットを作成するテンプレートは下のようなものを作成しました。

AWSTemplateFormatVersion: "2010-09-09"

Description: Provision of S3 Bucket

Parameters:
  SampleBucketName:
    Description: Name of S3 Bucket
    Type: String

Resources:
  S3Bucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref SampleBucketName

Outputs:
  BucketName:
    Value: !Ref S3Bucket

  S3BucketArn:
    Value: !GetAtt S3Bucket.Arn

Prametersは親テンプレートから引き継ぎます。あとは特に特筆すべきポイントはありません。

続いてバケットポリシーを作成するテンプレートです。

AWSTemplateFormatVersion: "2010-09-09"

Description: Provision of S3 Bucket Policy

Parameters:
  BucketName:
    Description: Name of S3 bucket
    Type: String

Resources:
  SampleBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref BucketName
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Resource: !Join
              - ""
              - - 'arn:aws:s3:::'
                - !Ref BucketName
                - /*
            Principal: '*'
            Condition:
              StringLike:
                aws:Referer:
                  - http://www.example.com/*
                  - http://example.net/*

こちらもPrametersの値は親テンプレートのものを参照するという点以外変わったところはないです。


次に起点となる親テンプレートです。ファイル名はroot.yamlとします。

AWSTemplateFormatVersion: "2010-09-09"

Description: Manage myself verification environment

Parameters:
  TemplateS3:
    Description: S3 template
    Type: String
    Default: !Rain::S3Http ./child-templates/s3.yaml

  TemplateS3BucketPolicy:
    Description: S3BucketPolicy template
    Type: String
    Default: !Rain::S3Http ./child-templates/s3-bucket-policy.yaml

  ENV:
    Description: env of resources
    Type: String
    AllowedValues:
      - staging
      - production
    Default: staging

  SampleBucketNamePrefix:
    Description: Prefix of bucket name
    Type: String
    Default: cfn-sample-bucket

Resources:
  S3:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateS3
      Parameters:
        SampleBucketName: !Sub ${SampleBucketNamePrefix}-${ENV}-${AWS::AccountId}

  S3BucketPolicy:
    Type: AWS::CloudFormation::Stack
    DependsOn: S3
    Properties:
      TemplateURL: !Ref TemplateS3BucketPolicy
      Parameters:
        BucketName: !GetAtt S3.Outputs.BucketName

Outputs:
  RainBucketName:
    Description: Name of S3 bucket created by Rain
    Value: !Rain::S3Http

  BucketArn:
    Description: ARN of S3 bucket
    Value: !GetAtt S3.Outputs.S3BucketArn

ここで注目すべきなのがResourcesセクションです。先ほど説明したとおり子テンプレートを呼び出しているのがこれらのリソースです。Parametersの値もここで子に渡しています。

最も注目すべきなのがその中のプロパティの一つである"TemplateURL" です。ここで子テンプレートがどこにあるかを指定しています。実際の値はParametersを参照しているため確認してみると

  TemplateS3:
    Description: S3 template
    Type: String
    Default: !Rain::S3Http ./child-templates/s3.yaml

となっています。本来こちらではS3バケットのURLを指定し子テンプレートを呼び出すはずですが、何やらみたことのないディレクティブが使用れています。

実はRainを利用してデプロイを行う場合、本来必要であるテンプレートを保存するS3バケットの作成とテンプレートの保存という作業が不要になります。

なぜならrainデプロイするとネストスタックであることを認識自動でこれらの動作を行ってくれるためです。

さらに"!Rain::S3Http"ディレクティブを使用することでこのとき作成されるS3バケットURLを自動で指定してくれるためわざわざ手動で設定する必要がないというわけです。

つまり、単体のテンプレートでもネストスタックされていてもrainならコマンド一つでデプロイしてくれるのでとても頼もしいツールですね!

これが本記事前のセクションで生まれた疑問の解消、そしてこの記事を作るに至った一番大きなRainの仕様です。

それでは実際にデプロイしてその様子を見ていただきたいと思います!

CloudFormationをデプロイ

デプロイの前にフォーマットの確認と整形を行いましょう。

# 整形
$ rain fmt -w ./child-templates/*yaml
$ rain fmt -w ./*yaml
# 確認
$ rain fmt -v ./child-templates/*yaml
$ rain fmt -v ./*yaml

コマンドは単体でデプロイする場合と同じ下記コマンドで行います。

$ rain deploy ./root.yaml <スタック名>

あとは必要パラメータを入力して完了を待ちましょう。今回のテンプレートではすべてデフォルトでも問題なくリソース作成されるようになっています。

まずテンプレートを配置するS3バケットを作成しファイルを配置していることがわかります。

Rain needs to create an S3 bucket called 'rain-artifacts-xxxxxxxxxx-ap-northeast-1'. Continue? (Y/n) 

その後テンプレートに沿ってリソースが作成されます。今回はS3バケットが一つ作成されるのみなので、すぐに完了するはずです。

指定したバケットポリシーをもつバケットが作成されていることが確認できたら検証終了です!最後に下記コマンドでスタックごとリソースを削除しておきましょう。(部分的に外部公開されているのでサンプルテンプレートを使用された方はそのままリソースを使用せず削除しておくこと推奨します。)

$ rain rm <スタック名>

これでお片付けも完了です!

最後に

お疲れ様でした!今回はわりとボリューミーな記事でしたがいかがだったでしょうか。

昨今様々なIaCツールが登場していますが、何を利用するにもどこまでコード、コマンドベースでシンプルに完結させられるかが鍵であると僕は考えています。

CloudFormationネストスタックにおいてもAWS CLIやコンソール操作ではないもっとシンプルな方法でデプロイできないかと考えrainを思い出したという背景があります。

CI/CDの質が厳しく問われる現在、多くのツールから最適な選択をし快適な開発環境を提供できるよう日頃からアンテナを張っていきたいとおもう所存です。


以上、最後まで読んでいただきありがとうございました!

筆者オススメ↓

AWS

Posted by CY