【CloudFormation】!Subと!Refの使い分け
CloudFormationのテンプレートでよく使う組み込み関数に!Refと!Subがあります。 これらの違いを整理します。
基本的な使い方
基本的な作用
基本的な作用は同一で、論理名を参照し、規定の値に変換します 変換可能な値は、!Refの公式ドキュメントで参照できる他、各リソースの「戻り値」の項目で確認できる(例えば、Lambdaの公式ドキュメント。
なお、ドキュメントでは!Refの戻り値しか紹介されていませんが、!Subを使っても全く同じ値が帰ってきます。
使い方
値を参照するに当たり、論理名を記載します。各関数は、リソースのタイプごとに定められた値を参照し、置換します。
使い方の注意点として、!Refは論理名を直接表記するのに対し、!Subは論理名を${}
でくくって使用します。
Parameters: TestLambdaDescription: Type: String Default: This is a test message Resources: SampleA: Type: AWS::Serverless::Function Properties: Description: !Ref TestLambdaDescription Handler: test.lambda_handler Runtime: python3.6 CodeUri: calcData SampleB: Type: AWS::Serverless::Function Properties: Description: !Sub ${TestLambdaDescription} Handler: test.lambda_handler Runtime: python3.6 CodeUri: calcData
#LambdaのDescriptionにはこのように表記される - SampleA: - This is a test message
#LambdaのDescriptionにはこのように表記される - SampleB: - This is a test message
誤った使い方
!Ref で参照する論理名を${}
でくくると、CloudFormationはエラーを返します。
SampleA: Type: AWS::Serverless::Function Properties: Description: !Ref ${TestLambdaDescription} Handler: test.lambda_handler Runtime: python3.6 CodeUri: calcData
また、!Subで${}
を忘れると、エラーは発生しないが、後述の文字列を結合する作用のせいで、論理名が文字列として認識されるため、変換が行われません。
SampleB: Type: AWS::Serverless::Function Properties: Description: !Sub TestLambdaDescription Handler: test.lambda_handler Runtime: python3.6 CodeUri: calcData
そのため、論理名がそのまま表記されます。
#LambdaのDescriptionにはこのように表記される - SampleB: - TestLambdaDescription
!Subの問題点
今回は Descriptionで実験したため、特に影響はありませんが、実際にリソースの構築を行う際にこのミスのせいでMember must satisfy regular expression pattern
が発生する場合があります。
例として、私がたまに起こすケアレスミスを紹介します。 Serverless変換を用いてLambdaにLayerを適用するためには、以下のような項目が必要となります。
ExampleFunction: Type: AWS::Serverless::Function Properties: #略 Layers: - !Ref layerLogicalID
正常に動作するためには、上記のように使うLayerのARNをリストにして表記する必要があります。
しかしここで、Layerを!Subで参照し、!Refで参照するときの感覚で${}
を忘れてしまう場合があります。
ExampleFunction: Type: AWS::Serverless::Function Properties: #略 Layers: - !Sub layerLogicalID
すると、以下のようなエラーが発生します
#Error message 1 validation errors detected: Value 'testRole' at 'role' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*)?:iam::\d{12}:role/?[a-zA-Z_0-9+=,.@\-_/]+; (Service: AWSLambdaInternal; Status Code: 400; Error Code: ValidationException; Request ID: e8ba965e-8554-11e9-a28d-5b2bc395254c)
文字列との結合
!Subの場合
!Subは、単体で変数名と文字列を結合することができます。
Properties: Description: !Sub ${TestLambdaDescription}-test Handler: calcData.lambda_handler Runtime: python3.6 Role: !GetAtt SQSRoleOfCalcData.Arn CodeUri: calcData
#LambdaのDescriptionにはこのように表記される - SampleB: - This is a test message-test
また、複数の値を置換することもできます。
Properties: Description: !Sub ${TestLambdaDescription}-test-${AWS::Partition} Handler: calcData.lambda_handler Runtime: python3.6 Role: !GetAtt SQSRoleOfCalcData.Arn CodeUri: calcData
#LambdaのDescriptionにはこのように表記される - SampleB: - This is a test message-test-aws
!Refの場合
!Ref では文字列の結合を行うことが行うことができません。無理矢理やろうとしてもエラーメッセージが出ます。参照する値を複数にしても同様となります。
そのため、このような使い方を行う場合は別に!Join
関数を使う必要があります。
!Join - '-' - !Ref TestLambdaDescription - test - !Ref AWS::Partition
#結果 This is a test message-test-aws
結論
!Ref と!Subの違いは次の通りです。
項目 | !Ref | !Sub |
---|---|---|
作用 | 論理名を規定の値に変換する | 論理名を規定の値に変換する |
値の参照方法 | 論理名をそのまま記載 | 論理名を${} でくくる |
文字列の結合 | 単体ではできない | 単体でできる |
このため、このような使い分けをしております
- 単体で値を参照する必要があるとき
- 文字数が少なくて済むほか、ポカヨケになるので!Refを使う
- 他の文字列と参照した値を結合する必要があるとき
- !Subを使う
何かの参考になればと思います