web-dev-qa-db-ja.com

スタックの削除中にaws LambdaがENIを削除しない

CloudFormationはLambda関数を作成します。関数が実行されると、ENIはラムダによって自動的にプロビジョニングされます。 ENIは、後続の関数の実行を高速化するために、関数の実行後も残っているようです。 CloudFormationはラムダ関数を削除します。 The ENは取り残されています。 VPC CloudFormation stackを削除しようとすると、ENIがsecurity group and subnetを使用しているため、スタックの削除に失敗します。

私のlambda roleにはdelete permissionがあります。

"Effect": "Allow"、 "Action":["ec2:CreateNetworkInterface"、 "ec2:DeleteNetworkInterface"、 "ec2:DescribeNetworkInterfaces"]、 "Resource": "*"

CloudFormationテンプレートからラムダを実行するためにカスタムリソースを使用しているので、ラムダはスタックの作成と削除の両方で呼び出されます。 ENIはスタックの作成と削除に使用されます。次に、eniの削除をどのように処理しますか?

13
laxman

Amazon VPCのリソースにアクセスするためのLambda関数の設定 に記載されているように、VPCでLambda関数を使用する場合に既知の問題があります。

Lambda関数が実行されてからENIが削除されるまでには遅延があります。関数の実行直後にロールを削除する場合は、ENIを削除する必要があります。

ドキュメントでは、この「遅延」の長さを正確に指定していませんが、 Richard @ AWSによるフォーラムの投稿 は、最大で6時間続くことが示唆されています(!) (AWS CloudTrailを使用した私の観察では、Lambdaの実行とENIの削除の間の遅延はおよそ1時間でした。)

AWSがこの問題にさらに対処するまで、Lambda関数の削除と関連するセキュリティグループおよびサブネットの削除の間に残ったENIを切り離して削除することにより、問題を回避できます。これが Terraform 現在 handles この issue のフレームワークでの方法です。

これは、VPC /サブネット/ SGレイヤーとLambda関数レイヤーを2つの異なるCloudFormationスタックに分離して手動で行うか、AWS SDKを使用してカスタムリソースを実装してENIを削除することで自動化できます。

以下は、VPC-Lambdaカスタムリソースを作成し、VPCDestroyENIカスタムリソースを使用して削除したときにENIをクリーンアップする完全な動作例です。

Launch Stack

Description: Creates a VPC-Lambda Custom Resource, cleaning up ENIs when deleted.
Parameters:
  VPCId:
    Description: VPC Id
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Description: Private Subnet Id
    Type: AWS::EC2::Subnet::Id
Resources:
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Lambda VPC security group
      VpcId: !Ref VPCId
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: "/"
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
      - PolicyName: DetachNetworkInterface
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action: ['ec2:DetachNetworkInterface']
            Resource: '*'
  AppendTest:
    Type: Custom::Split
    DependsOn: VPCDestroyENI
    Properties:
      ServiceToken: !GetAtt AppendItemToListFunction.Arn
      List: [1, 2, 3]
      AppendedItem: 4
  AppendItemToListFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          exports.handler = function(event, context) {
             var responseData = {Value: event.ResourceProperties.List};
             responseData.Value.Push(event.ResourceProperties.AppendedItem);
             response.send(event, context, response.SUCCESS, responseData);
          };
      Timeout: 30
      Runtime: nodejs4.3
      VpcConfig:
        SecurityGroupIds: [!Ref SecurityGroup]
        SubnetIds: [!Ref SubnetId]
  VPCDestroyENIFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          var AWS = require('aws-sdk');
          exports.handler = function(event, context) {
            console.log("REQUEST RECEIVED:\n", JSON.stringify(event));
            if (event.RequestType != 'Delete') {
              response.send(event, context, response.SUCCESS, {});
              return;
            }
            var ec2 = new AWS.EC2();
            var params = {
              Filters: [
                {
                  Name: 'group-id',
                  Values: event.ResourceProperties.SecurityGroups
                },
                {
                  Name: 'description',
                  Values: ['AWS Lambda VPC ENI: *']
                }
              ]
            };
            console.log("Deleting attachments!");
            // Detach all network-interface attachments
            ec2.describeNetworkInterfaces(params).promise().then(function(data) {
              console.log("Got Interfaces:\n", JSON.stringify(data));
              return Promise.all(data.NetworkInterfaces.map(function(networkInterface) {
                var networkInterfaceId = networkInterface.NetworkInterfaceId;
                var attachmentId = networkInterface.Attachment.AttachmentId;
                return ec2.detachNetworkInterface({AttachmentId: attachmentId}).promise().then(function(data) {
                  return ec2.waitFor('networkInterfaceAvailable', {NetworkInterfaceIds: [networkInterfaceId]}).promise();
                }).then(function(data) {
                  console.log("Detached Interface, deleting:\n", networkInterfaceId);
                  return ec2.deleteNetworkInterface({NetworkInterfaceId: networkInterfaceId}).promise();
                });
              }));
            }).then(function(data) {
              console.log("Success!");
              response.send(event, context, response.SUCCESS, {});
            }).catch(function(err) {
              console.log("Failure:\n", JSON.stringify(err));
              response.send(event, context, response.FAILED, {});
            });
          };
      Timeout: 300
      Runtime: nodejs4.3
  VPCDestroyENI:
    Type: Custom::VPCDestroyENI
    Properties:
      ServiceToken: !GetAtt VPCDestroyENIFunction.Arn
      SecurityGroups: [!Ref SecurityGroup]
Outputs:
  Output:
    Description: output
    Value: !Join [",", !GetAtt AppendTest.Value]

注:上記の例で必要なVPCとプライベートサブネットを作成するには、 AWSクイックスタートAmazon VPCアーキテクチャテンプレート を使用できます。

22
wjordan