🐕

Datadogに連携させるLambdaを増やしすぎるとポリシーサイズでエラーが出た話

2023/01/23に公開

はじめに

AWSLambdaのログをDatadogに連携させて使っていますが、最近それ関係でエラーに遭遇した際の対処法をまとめます。
具体的には、Lambdaのリソースベースのポリシーが肥大化し、上限値に達してしまったことが直接原因でした。
CDKHigh Level Constructを用いていたこが間接的に影響していたので、その辺りにも触れたいと思います。

予備知識

Datadogとの連携方法については、自分の個人ブログの方に記載してます。
https://nekoniki.com/20220601_aws-datadog

この方法を用いると、AWSアカウント側にDatadogにログを送信する用のLambdaが自動的に作成されます。これを以下Forwardarとします。
このForwardaを、Cloud Watch Logsのサブスクリプションフィルターに設定することで、
【Lambdaのログ蓄積】→【サブスクリプションフィルター経由でForwardarに伝達】→【Datadogに反映】という動きを取ることができます(下図)。

lambda-to-datadog

発生したエラー

発生したエラーは「The final policy size is bigger than the limit」というものです。
これは、公式にも言及している記事がありました。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-resource-based-policy-size-error/

それによると

Lambda 関数のリソースベースのポリシーが 20 KB を超える場合、Lambda は、[The final policy size is bigger than the limit」エラーを返します。

とあります。

原因となったコード

だいぶ端折っていますが、下記が原因となっていた実際のCDKのコードです。
作成したFunction中のlogGroupに対してaddSubscriptionFilterを呼び出して、サブスクリプションフィルターを作成しています。

import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as logsn from 'aws-cdk-lib/aws-logs-destinations';

// ...略

const datadogForwarder; // Datadogから提供されたForwardar関数
const functionId = 'HogeFunction1';

// Lambda Functionの作成
const function = new lambda.Function(this, functionId, {
  runtime: lambda.Runtime.NODEJS_16_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, 'hoge-function1')),
});

function.logGroup.addSubscriptionFilter(`${functionId}ErrorLog`, {
    filterPattern: logs.FilterPattern.literal("ERROR"),
    destination: new logsn.LambdaDestination(datadogForwarder)
})

ここでいうlogGroupは所謂High Level Constructに相当します。
CDKではAWSの特定のリソースを作成する記法が複数あり、High Level Constructは「複数のリソースをまとめて作成する」ような振る舞いをします。便利関数のようなものです。

具体的に説明すると、CloudWatchのログを利用するLambdaのサブスクリプションフィルターを作成するには、最低以下のリソースに変更が必要です。

  • サブスクリプションフィルターそのもの
  • Lambdaのリソースベースのポリシーに当該のCloudWatchのログから呼び出しを許可する権限

CloudFormationを用いる場合等は上記2つを記載する必要がありますが、CDKではこれらの記載を簡略するために、それらをラップした便利関数・クラスが豊富に用意されています。それがHigh Level Constructです。

ここでいうlogGroup.addSubscriptionFilterは内部的に

  • サブスクリプションフィルターそのものの作成
  • Lambdaのリソースベースのポリシーに当該のCloudWatchのログから呼び出しを許可する権限

という2種類の変更を行ってくれるのですが、これが曲者で、
自動でForwardarのリソースベースのポリシーにCloudWatchのログのARNを記載するため、addSubscriptionFilterを行った数だけForwardarのリソースベースのポリシーの行数が嵩んでいきます。これが直接原因で前述の20KBの制限に達してしまいました。

解消後のコード

続いて下記が解消後のコードです。
addSubscriptionFilterをやめて、CfnSubscriptionFilterを用いています。
CfnXXXXの形式はLow Level Constructと呼ばれ、1記述につき必ず1リソースという対応関係にあります。

意図として、

  • サブスクリプションフィルターの作成はCfnSubscriptionFilterで行い
  • Forwardarとの連携用のリソースベースのポリシーはaddPermissionを用いて、ワイルドカード指定でおこなう
    • その結果、リソースベースのポリシーの肥大化は起こらない

という形にしてあります。

import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as logs from 'aws-cdk-lib/aws-logs';

// ...略

const datadogForwarder; // Datadogから提供されたForwardar関数
const functionId = 'HogeFunction1';

// Lambda Functionの作成
const function = new lambda.Function(this, functionId, {
  runtime: lambda.Runtime.NODEJS_16_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, 'hoge-function1')),
});

// ワイルドカードでsourceArnを指定して、肥大化しないようにする
 datadogForwarder.addPermission("DatadogForwarderPermission", {
      principal: new iam.ServicePrincipal("logs.amazonaws.com"),
      action: "lambda:InvokeFunction",
      sourceArn: `arn:aws:logs:us-east-1:${this.account}:log-group:/aws/lambda/*`
});

// CfnSubscriptionFilterを使ってサブスクリプションフィルターを作成
new logs.CfnSubscriptionFilter(stack, `${functionId}ErrorLog`, {
    destinationArn: datadogForwarder.functionArn,
    filterPattern: 'ERROR',
    logGroupName: function.logGroup.logGroupName,
})

Low Level Constructを使っているため、元々のコードに比べて記述は長くなってしまいますが、そもそもリソースベースのポリシーをワイルドカードで記載したいというのはこちらのアプリケーション側の都合であるため、必要であると判断しました。

まとめ

今回はLambdaDatadogを紐づけた際に発生したエラーについての原因の解説と対処方法について消会しました。
Datadogは抜きにして、似たような構成でAWSを利用する例はあるかと思います。

その際の対処の目安になりましたら幸いです。

Discussion