🍆

CloudFormationでForEachを利用し、パラメータに記載した複数ホストゾーンを一括作成する

2024/08/24に公開

初技術ブログです。拙いかもしれません。
本題だけ読みたい方はこちらからどうぞ。

CloudFormationのForEach機能

ForEach関数の概要については以下の記事が参考になるかと思います。
https://dev.classmethod.jp/articles/cloudformation-foreach/

ForEachの具体例として、以下のymlファイルを実行すると「pesi-test-bucket」という名前のS3バケットが一つ作成されます。

s3.yml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: pesi-test-bucket

上記テンプレートを以下のように修正することで、「pesi-test-bucket-first」「pesi-test-bucket-second」「pesi-test-bucket-third」の三つのS3バケットが作成されます。

s3.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::LanguageExtensions'
Resources:
  'Fn::ForEach::S3Loop':
    - S3Names
    - - first
      - second
      - third
    - 'S3Bucket${S3Names}':
        Type: 'AWS::S3::Bucket'
        Properties:
          BucketName: !Sub "pesi-test-bucket-${S3Names}"

Transform: 'AWS::LanguageExtensions'という拡張機能の宣言をし、ForEach関数を使用しています。

実際に処理されたテンプレートは以下の通りです。

S3Namesの要素(first/second/third)の数だけループするイメージです。
リソースの論理ID(上記ではS3Bucket${S3Names})にそれぞれの要素名をくっつけることで複数リソースの作成を実現しています。

パラメータを使用して、入力した値によってリソースを一括作成するためには、テンプレートを以下のように修正します。

s3.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::LanguageExtensions'
Parameters:
  S3NameList:
    Type: CommaDelimitedList
    Default: first,second,third,fourth
    Description: "Enter S3 Names Separated by Commas"
Resources:
  'Fn::ForEach::S3Loop':
    - S3Names
    - !Ref S3NameList
    - 'S3Bucket${S3Names}':
        Type: 'AWS::S3::Bucket'
        Properties:
          BucketName: !Sub "pesi-test-bucket-${S3Names}"

パラメータS3NameListをDefaultに設定した内容(first/second/third/fourth)で実行すると、入力した要素の数だけリソースが作成されます。
今回は4つのS3バケットが作成されました。

ForEachでは論理IDに半角英数しか使用できない

それでは、上記を元にホストゾーンを複数作成するコードを準備します。

hostzone.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::LanguageExtensions'
Parameters:
  DomainNameList:
    Type: CommaDelimitedList
    Default: pesitest.com,pesitest.net
    Description: "Enter Domain Names Separated by Commas"
Resources:
  'Fn::ForEach::HostzoneLoop':
    - Domains
    - !Ref DomainNameList
    - HostedZone${Domains}:
        Type: "AWS::Route53::HostedZone"
        Properties:
          Name: DomainNameList

パラメータDomainNameListをDefaultに設定した内容(pesitest.com/pesitest.net)で実行すると、以下のエラーが出力されて作成に失敗します。
Transform AWS::LanguageExtensions failed with: OutputKey 'HostedZonepesitest.com' should be alphanumeric. Rollback requested by user.

これは論理ID(上記ではHostedZone${Domains})の値がalphanumeric、つまり英数字以外許可されていないために、ドメイン名の「.」が引っかかってエラーとなっています。

以下リンクでも同様のエラーが紹介されています。
https://dev.classmethod.jp/articles/cloudformation-foreach-sns-email/#%25E3%2583%2589%25E3%2583%25A1%25E3%2582%25A4%25E3%2583%25B3%25E3%2581%258C%25E7%2595%25B0%25E3%2581%25AA%25E3%2582%258B%25E3%2583%25A1%25E3%2583%25BC%25E3%2583%25AB%25E3%2582%25A2%25E3%2583%2589%25E3%2583%25AC%25E3%2582%25B9
また、以下リンクでは自分とは少し別のやり方でエラー回避する方法を紹介しています。
https://dev.classmethod.jp/articles/cloudformation-foreach-non-alphanumeric-params/#toc-foreach

パラメータに記載した複数ホストゾーンを一括作成

上記リンクの方法でもエラー回避はできますが、以下の方法であればもっと単純にエラー回避が可能かと思います。
以下のようにテンプレートを修正することで、ホストゾーンをForEachで複数作成することが可能です。

hostzone.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::LanguageExtensions'
Parameters:
  DomainNameList:
    Type: String
    Default: pesitest.com,pesitest.net
    Description: "Enter Domain Names Separated by Commas"
Resources:
  'Fn::ForEach::HostzoneLoop':
    - Domains
    - !Split [",", !Join ["tMpDoT", !Split [".", !Ref DomainNameList]]]
    - HostedZone${Domains}:
        Type: "AWS::Route53::HostedZone"
        Properties:
          Name: !Join [".", !Split ["tMpDoT", !Ref Domains]]

主な修正点は以下の通りです。

  1. パラメータのタイプをCommaDelimitedListからStringに変更
  2. Join関数とSplit関数を使用し、「.」を一時的に「tMpDoT」という文字列に置き換える処理を追加

実際に処理されるテンプレートは以下の通りです。


「.」を一時的に「tMpDoT」という文字列に置き換えているため論理IDが非常に不格好ですが、テンプレートファイルの内容やパラメータの入力値はスッキリしているのではないかと思います。
また、「.」以外の文字(ハイフンやアットマーク等)の場合でも上記を参考に置き換えが可能なはずです。
一応注意点としては、入力値内に「tMpDoT」という文字列が含まれている場合、処理がうまくいかないのではないかと思われます(ドメイン名は大文字小文字を区別しないはずなので基本的にないとは思いますが)。

終わりに

業務で複数ホストゾーンをまとめて作成する必要があったため、勉強もかねてForEachを利用しました。
ForEach関数は思ったより自由度が低い気もしますが、セキュリティグループの設定でポートだけ or IPアドレスだけ変えたものを複数作成したい場合などには重宝するのかなとか考えたりしました。
また、本記事内で紹介させていただいた各先行記事には大変お世話になりました。
この記事がどなたかの参考になれば幸いです。

Discussion