AWS CDKで構築したLambda@Edge関数を削除する際の注意点

2022.02.21

こんにちは、CX事業本部 IoT事業部の若槻です。

AWS CDKで構築したLambda@Edge関数を削除する機会があったのですが、その際に少々注意が必要だったので共有します。

先にまとめ

次の順番で削除操作を行えば、上手く削除することができました。

  1. 「CloudFront Distributionとの関数紐付け設定」を削除
  2. 「Lambda@Edge関数本体」を削除

環境

  • typescript@3.9.10
  • aws-cdk@1.129.0

事象:Distributionに紐付けたLambda@Edge関数が削除できない

Lambda@Edge関数は、Webフロントエンドのシステムにおいて、CloudFront Distributionに紐づけて使用するために作成する場合がほとんどだと思います。

このLambda@Edge関数が不要になったためリソースとして削除したくなったとします。

このフロントエンドをAWS CDKで構築をしている場合は、下記のように「Lambda@Edge関数本体」と「CloudFront Distributionとの関数紐付け設定」を削除する必要が出てきます。

lib/aws-cdk-app-stack.ts

import * as path from 'path';
import * as cdk from '@aws-cdk/core';
import * as cloudfront from '@aws-cdk/aws-cloudfront';
import * as s3 from '@aws-cdk/aws-s3';
import * as s3deploy from '@aws-cdk/aws-s3-deployment';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
      websiteErrorDocument: 'index.html',
      websiteIndexDocument: 'index.html',
    });

    const websiteIdentity = new cloudfront.OriginAccessIdentity(
      this,
      'WebsiteIdentity',
      {
        comment: `sample-stack-identity`,
      }
    );

    const webSiteBucketPolicyStatement = new iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: iam.Effect.ALLOW,
      principals: [
        new iam.CanonicalUserPrincipal(
          websiteIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    //削除対象その1:Lambda@Edge関数本体
    /*
    const websiteAddHeaderFunction = new lambda.Function(
      this,
      'WebsiteAddHeaderFunction',
      {
        code: lambda.Code.fromAsset(
          path.join(__dirname, '../src/lambda/website-add-header')
        ),
        functionName: 'add-header-function',
        handler: 'index.handler',
        role: new iam.Role(this, 'AllowLambdaServiceToAssumeRole', {
          assumedBy: new iam.CompositePrincipal(
            new iam.ServicePrincipal('lambda.amazonaws.com'),
            new iam.ServicePrincipal('edgelambda.amazonaws.com')
          ),
          managedPolicies: [
            {
              managedPolicyArn:
                'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
            },
          ],
        }),
        runtime: lambda.Runtime.NODEJS_14_X,
      }
    );

    const websiteAddHeaderFunctionVersion =
      websiteAddHeaderFunction.addVersion('v3');
    */

    const websiteDistribution = new cloudfront.CloudFrontWebDistribution(
      this,
      'WebsiteDistribution',
      {
        comment: `sample-distribution`,
        errorConfigurations: [
          {
            errorCachingMinTtl: 300,
            errorCode: 403,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
          {
            errorCachingMinTtl: 300,
            errorCode: 404,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
        ],
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: websiteBucket,
              originAccessIdentity: websiteIdentity,
            },
            behaviors: [
              {
                isDefaultBehavior: true,
                //削除対象その2:CloudFront Distributionとの関数紐付け設定
                /*
                lambdaFunctionAssociations: [
                  {
                    eventType: cloudfront.LambdaEdgeEventType.ORIGIN_RESPONSE,
                    lambdaFunction: lambda.Version.fromVersionArn(
                      this,
                      'WebsiteAddHeaderFunctionVersion',
                      websiteAddHeaderFunctionVersion.functionArn
                    ),
                  },
                ],
                */
              },
            ],
          },
        ],
        priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
      }
    );

    new s3deploy.BucketDeployment(this, 'WebsiteDeploy', {
      sources: [s3deploy.Source.asset('./web/build')],
      destinationBucket: websiteBucket,
      distribution: websiteDistribution,
      distributionPaths: ['/*'],
    });
  }
}

削除対象をスタックの記述から削除(コメントアウト)したので、CDKデプロイでAWSに反映をさせます。しかしデプロイを実行するとエラーとなってしまいました。

$  cdk deploy

✨  Synthesis time: 2.52s

AwsCdkAppStack: deploying...
[0%] start: Publishing e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68:current
[33%] success: Published e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68:current
[33%] start: Publishing a3058ccb468d757ebb89df5363a1c20f5307c6911136f29d00e1a68c9b2aa7e8:current
[66%] success: Published a3058ccb468d757ebb89df5363a1c20f5307c6911136f29d00e1a68c9b2aa7e8:current
[66%] start: Publishing a4159747c3d46d567d259ff7acb6bcf5922eafebffff617ab2e280988297696c:current
[100%] success: Published a4159747c3d46d567d259ff7acb6bcf5922eafebffff617ab2e280988297696c:current
AwsCdkAppStack: creating CloudFormation changeset...
6:29:59 PM | DELETE_FAILED        | AWS::Lambda::Function                           | WebsiteAddHeaderFunctionE7F821C0
Resource handler returned message: "Lambda was unable to delete arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:add-header-function:2 because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functi
ons and Replicas. (Service: Lambda, Status Code: 400, Request ID: ecb36778-f769-48b4-b508-dc961b09cd88, Extended Request ID: null)" (RequestToken: 812f5544-3374-70b8-ddab-754b94635962, HandlerErrorCode: InvalidRequest)

6:33:06 PM | DELETE_FAILED        | AWS::Lambda::Function                           | WebsiteAddHeaderFunctionE7F821C0
Resource handler returned message: "Lambda was unable to delete arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:add-header-function:2 because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functi
ons and Replicas. (Service: Lambda, Status Code: 400, Request ID: 00a59762-164c-4ff8-8b0e-80ca414013de, Extended Request ID: null)" (RequestToken: 6956098d-de97-982c-6127-f331912495b2, HandlerErrorCode: InvalidRequest)

6:36:11 PM | DELETE_FAILED        | AWS::Lambda::Function                           | WebsiteAddHeaderFunctionE7F821C0
Resource handler returned message: "Lambda was unable to delete arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:add-header-function:2 because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functi
ons and Replicas. (Service: Lambda, Status Code: 400, Request ID: 0c3c23fb-34b5-417d-928d-4cfcff9c277d, Extended Request ID: null)" (RequestToken: 4e7fc3bc-b54f-e7e8-9425-c5f7f16998c6, HandlerErrorCode: InvalidRequest)


 ✅  AwsCdkAppStack

✨  Deployment time: 626.36s

Stack ARN:
arn:aws:cloudformation:us-east-1:XXXXXXXXXXXX:stack/AwsCdkAppStack/caa8d160-926b-11ec-b61d-0ee97e0a4e77

✨  Total time: 628.88s

原因、解決

エラーによると、Lambdaがレプリカを持っているので削除できないと言っていますね。

Lambda was unable to delete arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:add-header-function:2 because it is a replicated function.

Lambda@Edgeでは高速な処理を行うためにエッジロケーションごとにレプリカが作成されます。ドキュメントを見ると、Lambda@Edge 関数を削除する前にレプリカが削除されている必要があり、またレプリカが削除される前にCloudFront Distributionとの関連付けが削除される必要があるとのことです。

You can delete a Lambda@Edge function only when the replicas of the function have been deleted by CloudFront. Replicas of a Lambda function are automatically deleted in the following situations:

- After you remove the last association for the function from all of your CloudFront distributions. If more than one distribution uses a function, the replicas are deleted only after you remove the function association from the last distribution.
- After you delete the last distribution that a function was associated with.

と言うわけで、2回に分けて削除のデプロイを行うことにより対処できました。

最初に「CloudFront Distributionとの関数紐付け設定」を削除(スタックから記述を削除してデプロイ)します。するとLambda@Edge関数のレプリカが削除されます。

次に「Lambda@Edge関数本体」を削除(スタックから記述を削除してデプロイ)します。するとLambda@Edge関数が削除できました。

おわりに

AWS CDKで構築したLambda@Edge関数を削除する際の注意点でした。タネが分かれば簡単ですね。

しかし「レプリカ」というのは通常のLambdaには無い概念なので意識していないとハマってしまうので気をつけたいです。

参考

以上