AWS SAMでIgnoreGlobals属性を使用してGlobalsセクションの継承をリソース単位で制御してみました

2024.03.13

初めに

普段あまり見ないのですが、ふとSAM CLIではなくSAM側のリポジトリ見ていたところ2023/11月頃のリリースで各リソースに定義可能な属性としてIgnoreGlobals属性が追加されていました。

Globalsセクションは現時点ではCloudFormationにはないSAM独自のセクションの一つですがこちらに値を指定することでテンプレート内のリソースに対して共通の設定値を定義することができます。

たとえばログレベルをシステム内共通で取り扱いたい場合LoggingConfigをGlobalsセクションで指定することで関数個別で指定が不要で、テンプレート自体もすっきりしますし設定漏れの防止にもなります。

値の優先度としては比較的弱リソース側で同属性別の値を指定することで上書きできますので、ユーザ側定義のデフォルト値の指定を行うようなものと考えるのが良いかもしれません。

その優先度から従来でも明示的にリソース側で値を指定することで値を上書きし対応すること可能でしたが、新たにGlobalsセクションに値を追加する場合リソースの量によっては変更時の都度の指定変更が大変であったり場合によっては予期せぬ変更を与えてしまう可能性があるためあらかじめ影響を与えないようにしておきたい場合があります。

こういった場合に今回追加されたIgnoreGlobals属性が有効であり個別のリソースに対して指定することでそのリソースはGlobalsセクションで指定した値を継承させないようにできます。

ちなみに大元のIssues的にはGlobalsセクションでRuntimeの値が指定されている状態で同テンプレートでPackageType: ImageのLambda関数を定義してしまうと、コンテナデプロイされたLambda関数のなのにRuntimeの値が指定されているためエラー扱いとなるため例外的な処理をさせたかったようです(RuntimePackageType: Zipの時に指定する値)。

IgnoreGlobals属性の指定

IgnoreGlobals属性はDependsOnMetadataのようなリソース属性と同列で各種リソース名直下のレベルで指定が可能な属性となる...はずです。

大元のソースコードを見る限りそれらと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側にはTimeoutMemorySizeの指定がないのでGlobalsセクションの値が採用されます。

% aws lambda get-function --function-name sam-app-HelloWorldFunction --query '{Timeout: Configuration.Timeout, MemorySize: Configuration.MemorySize}'
{
    "Timeout": 300,
    "MemorySize": 512
}

IgnoreGlobalsMemorySizeを指定しそちらの値のみを無視し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のように単体のリソースとしては一件意味の無い値を指定することで混乱を生んでしまったり、リソース量によっては影響の確認が大変であっ対するためそれを分離できるので便利なものとなりそうです。

一方で不用意に自由に継承を除外しすぎると本来継承すべき設定が抜けてしまったりどのリソースが何を除外しているか等の把握が困難となりより複雑性を生んでしまう可能性がありますので使い方には十分注意しましょう。