aws Lambda 创建的 ENI 在删除堆栈时不删除

aws Lambda created ENI not deleting while deletion of stack

CloudFormation 创建 Lambda 函数。执行函数时,lambda 会自动提供 ENI。 ENI似乎是在函数执行后留下来的,以加速后续函数的执行。 CloudFormation 删除了 lambda 函数。 EN 留在后面。尝试删除 VPC CloudFormation stack 时,堆栈删除失败,因为 ENI 正在使用 security group and subnet.

在我的 lambda role 中有 delete permission

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

我正在使用自定义资源 运行 来自 CloudFormation 模板的 lambda,因此 lambda 将同时调用堆栈创建和删除。 ENI 将用于堆栈的创建和堆栈的删除。现在如何处理eni删除?

在 VPC 中使用 Lambda 函数时存在一个已知问题,如 Configuring a Lambda Function to Access Resources in an Amazon VPC 中所述:

There is a delay between the time your Lambda function executes and ENI deletion. If you do delete the role immediately after function execution, you are responsible for deleting the ENIs.

文档没有具体说明这个 "delay" 会持续多长时间,但是 forum post by Richard@AWS 表明它最多可以持续 6 小时(! ). (根据我使用 AWS CloudTrail 的观察,Lambda 执行和 ENI 删除之间的延迟约为 一个小时 。)

在 AWS 进一步解决此问题之前,您可以通过在删除 Lambda 函数和删除关联的安全组和子网之间分离和删除剩余的 ENI 来解决此问题。这就是Terraform currently handles this issue在其框架中的方式。

您可以通过将 VPC/Subnet/SG 层和 Lambda-function 层分成两个不同的 CloudFormation 堆栈来手动执行此操作,或者您可以通过实施自定义资源来自动执行此操作以使用 AWS 删除 ENI SDK.

这是创建 VPC-Lambda 自定义资源的完整工作示例,使用 VPCDestroyENI 自定义资源删除时清理其 ENI:

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和Private Subnet,您可以使用AWS Quick Start Amazon VPC Architecture template.