【CloudFormation】 Parametersの空白値を判定して、動的にリソースプロパティの作成有無を制御する

記事タイトルとURLをコピーする

こんにちは。アプリケーションサービス部の河野です。

タイトル付けに悩んだのですが、CloudFormation(以下 Cfn)の Parameters, Condition, AWS::NoValue を使用したテンプレート記法のお話です。

導入

タイトルで実現したいことを記載しているのですが、そもそも何故このような仕様を実現することになったのか説明します。

Cfn で 以下のように、CloudWatch Alarm のテンプレートを記載していました。
(見やすいように本タイトルには関係ない項目は省略し、最低限の項目を記載しています)

Parameters:
  AlarmName:
    Type: String
  MetricName:
    Type: String
  Namespace:
    Type: String
  DimensionName01:
    Type: String
  DimensionValue01:
    Type: String
  Threshold:
    Type: Number

Resources:
  Alarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref AlarmName
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      EvaluationPeriods: 1
      MetricName: !Ref MetricName 
      Namespace: !Ref Namespace 
      Statistic: Average
      Dimensions:
        - Name: !Ref DimensionName01
          Value: !Ref DimensionValue01
      Threshold: !Ref Threshold
      Period: 60

複数のリソースでも同じテンプレートで対応できるように、アラーム名や対象メトリクスなどはパラメータに渡して汎用的なテンプレートにしています。
Dimensions値は、CloudWatch Alarm の対象とするメトリクス関連付けることができる名前/値のペア値で、EC2 であれば InstanceId/i-xxxxxxxx、ECS であればServiceName/xxxxxxとClusterName/yyyyyyy の二つを指定します。
ここで問題になるのが、Dimensions値はリソースによって指定しなくてよいものもあれば、二つ以上指定する場合もあるということです。

docs.aws.amazon.com

そのため、以下仕様を満たすテンプレートを記載できれば良いということになります。

  • Dimenstion が1つ(DimensionName01, DimensionValue01)指定された場合は、Dimensions を1つ指定して作成
  • Dimenstion が2つ(DimensionName01, DimensionValue01, DimensionName02, DimenstionValue02)指定された場合は、Dimensions を2つ指定して作成
  • Dimenstion が3つ(DimensionName01, DimensionValue01, DimensionName02, DimenstionValue02, DimensionName03, DimensionValue03)指定された場合は、Dimensions を3つ指定して作成
  • Dimenstion が1つも指定されていない場合は、Dimensions を指定せずに作成

実践

では、上記で示した仕様を満たすようにテンプレートを修正していきます。
まず、「xxの場合はyyをする」といったようにテンプレート内で条件付きの制御をする場合は、組み込み関数のConditionFn::Ifを使用します。

Condition は条件の評価を返します。
docs.aws.amazon.com

例えば Parameters の値が空白であることを判定する場合は以下のように定義します。
Fn::Not で NOT 演算子、Fn::And で AND 演算子を定義できるので、以下は、Name, Value ともに空白でなければ true、 Name, Value ともに空白の場合は false を返します。

Conditions:
 CreateResourcePerDemensions: 
    Fn::And:
    - Fn::Not: [!Equals [!Ref DimensionName01, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue01, '']]

Fn::If は指定した条件のそれぞれの結果(true, false)に対して返す値を定義します。 docs.aws.amazon.com

例えば、先ほどの条件を使用すれば CreateResourcePerDemensions が true の場合(値が指定されている場合)は、Dimenstion にそれぞれ値をセットし、 false の場合(値が指定されていない場合)は、Dimensions は定義しないということになります。

Dimensions:
  Fn::If:
   - CreateResourcePerDemensions
     - - Name: !Ref DimensionName01
       - Value: !Ref DimensionValue01
    - !Ref AWS::NoValue  

AWS::NoValue が出てきましたが、これは対応するプロパティを削除する組み込み関数です。上記の場合、上述の通り Dimenstions は定義しないということになります。

docs.aws.amazon.com

ここまでくれば、以下二つの仕様を満たすテンプレートはかけそうです。

  • Dimenstion が1つ(DimensionName01, DimensionValue01)指定された場合は、Dimensions を1つ指定して作成
  • Dimenstion が1つも指定されていない場合は、Dimensions を指定せずに作成
Parameters:
  AlarmName:
    Type: String
  MetricName:
    Type: String
  Namespace:
    Type: String
  DimensionName01:
    Type: String
  DimensionValue01:
    Type: String
  Threshold:
    Type: Number

Conditions:
  CreateResourcePerDemensions: 
    Fn::And:
    - Fn::Not: [!Equals [!Ref DimensionName01, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue01, '']]

Resources:
  Alarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref AlarmName
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      EvaluationPeriods: 1
      MetricName: !Ref MetricName 
      Namespace: !Ref Namespace 
      Statistic: Average
      Dimensions:
        Fn::If:
        - CreateResourcePerDemensions
        - - Name: !Ref DimensionName01
            Value: !Ref DimensionValue01
        - !Ref AWS::NoValue
      Threshold: !Ref Threshold
      Period: 60

残りの仕様については、Fn::If をネストして指定することで実現します。

Parameters:
  AlarmName:
    Type: String
  MetricName:
    Type: String
  Namespace:
    Type: String
  DimensionName01:
    Type: String
  DimensionValue01:
    Type: String
  Threshold:
    Type: Number

Conditions:
  CreateResourcePerDemensions: 
    Fn::And:
    - Fn::Not: [!Equals [!Ref DimensionName01, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue01, '']]
  CreateResourcePer2Demensions: 
    Fn::And:
    - Fn::Not: [!Equals [!Ref DimensionName01, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue01, '']]
    - Fn::Not: [!Equals [!Ref DimensionName02, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue02, '']]
  CreateResourcePer3Demensions: 
    Fn::And:
    - Fn::Not: [!Equals [!Ref DimensionName01, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue01, '']]
    - Fn::Not: [!Equals [!Ref DimensionName02, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue02, '']]
    - Fn::Not: [!Equals [!Ref DimensionName03, '']]
    - Fn::Not: [!Equals [!Ref DimensionValue03, '']]

Resources:
  Alarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref AlarmName
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      EvaluationPeriods: 1
      MetricName: !Ref MetricName 
      Namespace: !Ref Namespace 
      Statistic: Average
      Dimensions:
        Fn::If:
        - CreateResourcePer3Demensions
        - - Name: !Ref DimensionName01
            Value: !Ref DimensionValue01
          - Name: !Ref DimensionName02
            Value: !Ref DimensionValue02
          - Name: !Ref DimensionName03
            Value: !Ref DimensionValue03
        - Fn::If: 
          - CreateResourcePer2Demensions
          - - Name: !Ref DimensionName01
              Value: !Ref DimensionValue01
            - Name: !Ref DimensionName02
              Value: !Ref DimensionValue02
          - Fn::If:
            - CreateResourcePerDemensions
            - - Name: !Ref DimensionName01
                Value: !Ref DimensionValue01
            - !Ref AWS::NoValue
      Threshold: !Ref Threshold
      Period: 60

上記は、3つ指定 → 2つ指定 → 1つ指定の順番で評価することで、それぞれ指定した数によって Dimentions の値をセットすることができます。

さいごに

Cfn の組み込み関数は便利ですが、慣れるまでは少し時間がかかる印象でしたので、本エントリーが Cfn 理解の手助けになれば幸いです。