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の削除をどのように処理しますか?
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をクリーンアップする完全な動作例です。
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アーキテクチャテンプレート を使用できます。