こんにちは。アプリケーションサービス部の河野です。
タイトル付けに悩んだのですが、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
値はリソースによって指定しなくてよいものもあれば、二つ以上指定する場合もあるということです。
そのため、以下仕様を満たすテンプレートを記載できれば良いということになります。
- 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をする」といったようにテンプレート内で条件付きの制御をする場合は、組み込み関数のCondition
とFn::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 は定義しないということになります。
ここまでくれば、以下二つの仕様を満たすテンプレートはかけそうです。
- 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 理解の手助けになれば幸いです。