AWS SAMでIgnoreGlobals属性を使用してGlobalsセクションの継承をリソース単位で制御してみました
初めに
普段あまり見ないのですが、ふとSAM CLIではなくSAM側のリポジトリ見ていたところ2023/11月頃のリリースで各リソースに定義可能な属性としてIgnoreGlobals
属性が追加されていました。
Globalsセクションは現時点ではCloudFormationにはないSAM独自のセクションの一つですがこちらに値を指定することでテンプレート内のリソースに対して共通の設定値を定義することができます。
たとえばログレベルをシステム内共通で取り扱いたい場合LoggingConfigをGlobalsセクションで指定することで関数個別で指定が不要で、テンプレート自体もすっきりしますし設定漏れの防止にもなります。
値の優先度としては比較的弱リソース側で同属性別の値を指定することで上書きできますので、ユーザ側定義のデフォルト値の指定を行うようなものと考えるのが良いかもしれません。
その優先度から従来でも明示的にリソース側で値を指定することで値を上書きし対応すること可能でしたが、新たにGlobalsセクションに値を追加する場合リソースの量によっては変更時の都度の指定変更が大変であったり場合によっては予期せぬ変更を与えてしまう可能性があるためあらかじめ影響を与えないようにしておきたい場合があります。
こういった場合に今回追加されたIgnoreGlobals
属性が有効であり個別のリソースに対して指定することでそのリソースはGlobalsセクションで指定した値を継承させないようにできます。
ちなみに大元のIssues的にはGlobalsセクションでRuntime
の値が指定されている状態で同テンプレートでPackageType: Image
のLambda関数を定義してしまうと、コンテナデプロイされたLambda関数のなのにRuntime
の値が指定されているためエラー扱いとなるため例外的な処理をさせたかったようです(Runtime
はPackageType: Zip
の時に指定する値)。
IgnoreGlobals属性の指定
IgnoreGlobals
属性はDependsOn
やMetadata
のようなリソース属性と同列で各種リソース名直下のレベルで指定が可能な属性となる...はずです。
大元のソースコードを見る限りそれらとResourceAttributes
クラスに含まれているためこ同列取り扱いだと思いますが現時点でAWSドキュメント側のResource attributesでは特に言及は無いため確定は避けておきます。
(他のドキュメントでも見当たらないのでおそらくまだ記載が追いついてないだけとは思いますが...)
特定の属性のみを無視したい場合は属性名を配列で引き渡します。
Globals: Function: Timeout: 300 MemorySize: 256 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Architectures: - arm64 IgnoreGlobals: - 'TimeOut'
*
を指定することでGlobalsセクションから全ての属性を継承しないようになります。
Globals: Function: Timeout: 300 MemorySize: 256 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Architectures: - arm64 IgnoreGlobals: '*'
使用してみる
実際に値を設定して利用します。
まずは以下のようなテンプレートでデプロイします。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 300 MemorySize: 512 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Architectures: - arm64
この場合HelloWorldFunction
側にはTimeout
、MemorySize
の指定がないのでGlobalsセクションの値が採用されます。
% aws lambda get-function --function-name sam-app-HelloWorldFunction --query '{Timeout: Configuration.Timeout, MemorySize: Configuration.MemorySize}' { "Timeout": 300, "MemorySize": 512 }
IgnoreGlobals
にMemorySize
を指定しそちらの値のみを無視しTimeOut
はGlobalsセクションの値を利用してみます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 300 MemorySize: 512 Resources: HelloWorldFunction: Type: AWS::Serverless::Function IgnoreGlobals: - MemorySize Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Architectures: - arm64
この場合HelloWorldFunction
側ではMemorySize
の指定はありませんがLambda関数のデフォルトのMemorySize
は128MBのためその値が採用されます。
% aws lambda get-function --function-name sam-app-HelloWorldFunction --query '{Timeout: Configuration.Timeout, MemorySize: Configuration.MemorySize}' { "Timeout": 300, "MemorySize": 128 }
*
を指定しGlobalsセクションの値を全て継承しないようにしてみます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 300 MemorySize: 512 Resources: HelloWorldFunction: Type: AWS::Serverless::Function IgnoreGlobals: '*' Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Architectures: - arm64
MemorySize
に加えTimeout
もデフォルト値の3秒が採用されています。
% aws lambda get-function --function-name sam-app-HelloWorldFunction --query '{Timeout: Configuration.Timeout, MemorySize: Configuration.MemorySize}' { "Timeout": 3, "MemorySize": 128 }
なお前述した通りリソース側で値を指定することでそちらが優先されますので例えば以下のように指定することでGlobals以外の値が採用されます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 300 MemorySize: 512 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.12 Timeout: !Ref AWS::NoValue MemorySize: 256 Architectures: - arm64
同パラメータの指定がない場合Globalsセクションの値が採用されますが、AWS::NoValue
を明示的に指定した値なしの場合はGlobals側の値ではなくデフォルトの値が採用されます。
% aws lambda get-function --function-name sam-app-HelloWorldFunction-xxxxx --query '{Timeout:Configuration.Timeout,MemorySize:Configuration.MemorySize}' { "Timeout": 3, "MemorySize": 256 }
ただこの辺りの仕様を理解していないとなぜ未指定ではなくあえてAWS::NoValue
を指定しているのか?といった部分で混乱を生む可能性があり開発・運用者にこの辺りの理解を求めることになりますので不親切かもしれません。
終わりに
リソース側でGlobalsセクションで指定された共通属性の利用を拒否するGlobalsIgnore
を利用してみました。
前述した通り従来であってもパラメータの上書きで管理できますが、デフォルト値を利用したい場合Timeout: !Ref AWS::NoValue
のように単体のリソースとしては一件意味の無い値を指定することで混乱を生んでしまったり、リソース量によっては影響の確認が大変であっ対するためそれを分離できるので便利なものとなりそうです。
一方で不用意に自由に継承を除外しすぎると本来継承すべき設定が抜けてしまったりどのリソースが何を除外しているか等の把握が困難となりより複雑性を生んでしまう可能性がありますので使い方には十分注意しましょう。