CloudFormationでループ処理を実装する

## はじめに

CloudFormationテンプレートで同じ設定のリソースを複数展開したいとき、ループ処理が使えたらいいなと思うことがありませんか？

CloudFormationにはループ処理を実現する機能は提供されていませんが、[マクロ機能](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-macros.html)を使うことにより、機能を追加することが出来ます。

awslabsにループ処理を実現する[Count Macroが公開](https://github.com/awslabs/aws-cloudformation-templates/tree/master/aws/services/CloudFormation/MacrosExamples/Count)されているので利用方法を紹介します。



## 事前準備

CloudFormationテンプレートでCount Macroを利用するために、まずはCount Macroのスタックをデプロイします。

- CloudFormation用S3バケットの作成

  - `<bucket-name>`はお好きな名前を付け、これ以降`<bucket-name>`となっている箇所は置換してください。

    ```
    S3バケットを作成します。
    $ aws s3 mb s3://<bucket-name>
    ```

- Count Macroのスタックをデプロイ

  - [Git Hub](https://github.com/awslabs/aws-cloudformation-templates)からCount Macroを展開するCloudFormationテンプレートを取得し、Count Macroを利用するAWSアカウントにスタックをデプロイします。

    ```
    gitからクローンします。
    $ git clone https://github.com/awslabs/aws-cloudformation-templates
    
    Count Macroのディレクトリに移動します。
    $ cd aws-cloudformation-templates/aws/services/CloudFormation/MacrosExamples/Count
    
    Count MacroはAWS SAMを利用しているので、LambdaコードをS3にアップロードし、アップロードされたLambdaコードをデプロイするテンプレートに変換します。<bucket-name>は置換してください。
    $ aws cloudformation package \
        --template-file template.yaml \
        --s3-bucket <bucket-name> \
        --output-template-file packaged.yaml
    
    Count-macroという名前でスタックをデプロイします。
    $ aws cloudformation deploy \
      --template-file packaged.yaml \
      --stack-name Count-macro \
      --capabilities CAPABILITY_IAM
    ```
  
- Count Macroのスタックのデプロイ結果確認

  - AWSマネージメントコンソールでCloudFormationを開き、Count-macroスタックが作成されたことを確認します。![image-20211021144854563.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611601/ebf0b921-f84a-6141-f1da-7a724bed7565.png)


  - MacroのLambdaも展開されています。![image-20211021145049510.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611601/ab7f48ea-780e-bc43-8591-428816f3cd67.png)




## Count Macroの使い方

### サンプルCloudFormationテンプレートを使って複数のS3バケットをデプロイしてみる

では実際にCount Macroを使ってみましょう。GitHubのリポジトリに同梱されていたサンプルファイル([test.yaml](https://github.com/awslabs/aws-cloudformation-templates/blob/master/aws/services/CloudFormation/MacrosExamples/Count/test.yaml))をデプロイしてみます。

- CloudFormationテンプレート

  - Count Macroを使用するため、テンプレートの初めに`Transform: Count`が記述されています。

  - S3バケットの定義のプロパティでは`Count: 3`が指定されています。

  - これにより、1つのS3バケット定義で3つのバケットが作成されます。

    ```yaml:test.yaml
    Transform:
      - Count
    Resources:
      Bucket:
        Type: AWS::S3::Bucket
        Count: 3
    ```

- CloudFormationテンプレートのデプロイ

  - `Count-test`というスタック名でデプロイを実行します。

    ```
    $ aws cloudformation deploy \
        --stack-name Count-test \
        --template-file test.yaml \
        --capabilities CAPABILITY_IAM
    ```

- デプロイ結果の確認

  - 作成されたスタックのリソースタブを開くと、3つのS3バケットが作成されていることを確認できます。
  - 作成されるS3バケット名は`CloudFormationテンプレートの論理ID ＋ カウントインデックス ＋ ランダム文字列`になります。![image-20211021152311386.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611601/9a969886-3029-2d7e-9c54-5193882a52a2.png)




### 応用編 バケット名を個別に指定してみる

バケット名は自分で指定したいので、バケット名を個別に指定しつつ、一つの定義でまとめて作成するテンプレートにしてみます。

- CloudFormationテンプレート

  - パラメータ`BucketNameList`で作成するバケット名をカンマ区切りで定義します。

  - パラメータ`BucketNameCount`で作成するバケット数を定義します。

  - バケット名は split関数を使いパラメータ`BucketNameList`をカンマで分割しリスト化、select関数を使いリストのインデックス`％d`を指定してバケット名を取得します。

    - Count Macroはプレースホルダー`％d`でカウントインデックスが取得できます。

    ````yaml:named_s3_bucket.yaml
    Transform: Count
    Parameters:
      BucketNameList:
        Type: String
        Default: "matu-20211021,take-20211021,ume-20211021"
      BucketNameCount:
        Type: Number
        Default: 3
    Resources:
      NamedS3Bucket:
        Type: AWS::S3::Bucket
        Properties:
          BucketName: !Select [ "%d", !Split [",", !Sub ",${BucketNameList}"] ]
        Count: !Ref BucketNameCount
    ```
  
- CloudFormationテンプレートのデプロイ

  - `named-s3-bucket`というスタック名でデプロイを実行します。

    ```
    $ aws cloudformation deploy \
        --stack-name named-s3-bucket \
        --template-file named_s3_bucket.yaml \
        --capabilities CAPABILITY_IAM
    ```

- デプロイ結果の確認

  - パラメータ`BucketNameList`で指定したバケット名でS3バケットが一括作成されました。![image-20211021152324934.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611601/e60e9b59-12b0-7913-afe8-f44109360dde.png)




## 最後に

Count Macroを利用することで、CloudFormationでも繰り返し処理を実装することが出来ました。

CloudFormationは宣言的にリソースを定義していくことで読みやすく扱いすい反面、少し複雑な処理をするのが苦手です。

複雑な処理を実装したいときには[Terraform](https://www.terraform.io/docs/language/meta-arguments/for_each.html)や[AWS CDK](https://github.com/aws-samples/aws-cdk-examples)を使った方が楽なので、それぞれのツールのメリットや提供されているテンプレートなど活かしつつ使い分けしたいですね。