CloudFormation自定义资源未完成删除

时间:2018-09-09 02:00:12

标签: aws-lambda amazon-cloudformation

我已将自定义资源设置为在删除后立即返回

const aws = require('aws-sdk')
const util = require('util')

exports.handler = (event, context) => {
  console.log('Event>>>')
  console.log(JSON.stringify(event))
  aws.config.update({ region: event.ResourceProperties.Region })

  if (event.RequestType === 'Delete') return ApiMethodCustom.sendResponse(event, context, 'SUCCESS') // HERE!

  ApiMethodCustom.setupIntegration(event, context)
}

  static async sendResponse(event, context, responseStatus, responseData = {}) {
    var responseBody = JSON.stringify({
      Status: responseStatus,
      Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
      PhysicalResourceId: context.logStreamName,
      StackId: event.StackId,
      RequestId: event.RequestId,
      LogicalResourceId: event.LogicalResourceId,
      Data: responseData
    });

    console.log("RESPONSE BODY:\n", responseBody);

    var https = require("https");
    var url = require("url");

    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
      hostname: parsedUrl.hostname,
      port: 443,
      path: parsedUrl.path,
      method: "PUT",
      headers: {
        "content-type": "",
        "content-length": responseBody.length
      }
    };

    console.log("SENDING RESPONSE...\n");

    var request = https.request(options, function (response) {
      console.log("STATUS: " + response.statusCode);
      console.log("HEADERS: " + JSON.stringify(response.headers));
      // Tell AWS Lambda that the function execution is done
      context.done();
    });

    request.on("error", function (error) {
      console.log("sendResponse Error:" + error);
      // Tell AWS Lambda that the function execution is done
      context.done();
    });

    // write data to request body
    request.write(responseBody);
    request.end();
  }

但是似乎CloudFormation卡在DELETE_IN_PROGRESS中。这是为什么?

在我的日志中,Lambda似乎正确完成了执行:

2018-09-09T01:52:06.913Z    f48808d0-b3d2-11e8-9e84-5b218cad3090
{
    "RequestType": "Delete",
    "ServiceToken": "arn:aws:lambda:ap-southeast-1:621567429603:function:income2-base-ApiVpcIntegration",
    "ResponseURL": "https://cloudformation-custom-resource-response-apsoutheast1.s3-ap-southeast-1.amazonaws.com/arn%3Aaws%3Acloudformation%3Aap-southeast-1%3A621567429603%3Astack/test/5a34d100-b370-11e8-b89d-503a138dba36%7CApiTestIntegration%7C979b1814-d94c-4a49-b9f7-2fa352ab88f5?AWSAccessKeyId=AKIAIKQZQ3QDXOJPHOPA&Expires=1536465125&Signature=O2O0entoTXHCYp5jbJehghtE9Ck%3D",
    "StackId": "arn:aws:cloudformation:ap-southeast-1:621567429603:stack/test/5a34d100-b370-11e8-b89d-503a138dba36",
    "RequestId": "979b1814-d94c-4a49-b9f7-2fa352ab88f5",
    "LogicalResourceId": "ApiTestIntegration",
    "PhysicalResourceId": "2018/09/08/[$LATEST]b8a3df0fca884fe3b8abdde3ab525ac0",
    "ResourceType": "Custom::ApiVpcIntegration",
    "ResourceProperties": {
        "ServiceToken": "arn:aws:lambda:ap-southeast-1:621567429603:function:income2-base-ApiVpcIntegration",
        "ConnectionId": "24lbti",
        "ResourceId": "x1gjyy",
        "RestApiId": "aaj0q4dbml",
        "Uri": "http://dropletapi-dev.2359media.net:3001/authentication",
        "HttpMethod": "GET"
    }
}

2018-09-09T01:52:06.914Z    f48808d0-b3d2-11e8-9e84-5b218cad3090    RESPONSE BODY:
{
    "Status": "SUCCESS",
    "Reason": "See the details in CloudWatch Log Stream: 2018/09/09/[$LATEST]29276598cb9c49c1b1da3672c8707c78",
    "PhysicalResourceId": "2018/09/09/[$LATEST]29276598cb9c49c1b1da3672c8707c78",
    "StackId": "arn:aws:cloudformation:ap-southeast-1:621567429603:stack/test/5a34d100-b370-11e8-b89d-503a138dba36",
    "RequestId": "979b1814-d94c-4a49-b9f7-2fa352ab88f5",
    "LogicalResourceId": "ApiTestIntegration",
    "Data": {}
}

1 个答案:

答案 0 :(得分:6)

我今天在使用cfn-response包时遇到了类似的问题,您的代码似乎基于该包。 cfn-response程序包基于回调,但您的代码似乎也部分使用了异步/等待(运行时选项:node.js8.10)。

就您而言,我怀疑即使将响应主体(同步)转储到日志中,也从未看到过“ STATUS:”或“ HEADERS:”消息。这反映了我将基于回调的cfn-response与async / await结合使用时的经验。

换句话说,在所有情况下,您需要确保在Lambda终止或模板最多可挂起之前,发送对Cloudformation的响应(对事件S3 ResponseURL进行PUT)。放弃并回滚之前的一个小时(可能出现Cloudformation错误,提示“未能稳定资源...”。回滚(删除)也可能需要一个小时,因为删除操作也无法正确响应。更多信息here

我最终实现了自定义资源,就像this example on GitHub(由麻省理工学院许可,由https://github.com/rosberglinhares实现),但有一些区别;我没有设置单独的lambda来处理sendResponse功能,而是使自定义资源变得无服务器(使用aws cloudformation软件包和aws cloudformation deploy命令)。

您的ApiMethodCustom尚未定义,因此我很难指导您该实现,因此我将使用async / await作为参考包含我的node.js8.10代码。

首先在Cloudformation模板中添加自定义资源:

---
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CustomResource Example Stack
Resources:
  CustomResource:
    Type: 'AWS::Serverless::Function'
    Properties:
      Runtime: nodejs8.10
      Handler: index.handler
      MemorySize: 128
      Timeout: 15
      Role: !GetAtt CustomResourceRole.Arn
      CodeUri: ./CustomResource/

  CustomResourceUser:
    Type: 'Custom::CustomResourceUser'
    Properties:
      ServiceToken: !GetAtt CustomResource.Arn
      ...

请注意,CodeUri是相对于模板路径的。您将需要为CustomResourceRole定义IAM角色和策略。

现在使用CustomResource / index.js Lambda(您还需要在CustomResource目录中运行“ npm install --save axios”):

'use strict';
const AWS = require('aws-sdk');
const axios = require('axios');

exports.handler = async (event, context) => {
  try {

    switch (event.RequestType) {
      case 'Create':
        await ApiMethodCustom.create(...);
        break;
      case 'Update':
        await ApiMethodCustom.update(...);
        break;
      case 'Delete':
        await ApiMethodCustom.delete(...);
        break;
    }
    console.info('Success for request type ${event.RequestType}');
    await sendResponse(event, context, 'SUCCESS', { } );
  } catch (error) {
    console.error('Error for request type ${event.RequestType}: ', error);
    await sendResponse(event, context, 'FAILED', { } );
  }
}

async function sendResponse (event, context, responseStatus, responseData, physicalResourceId) {

  var reason = responseStatus == 'FAILED' ? ('See the details in CloudWatch Log Stream: ' + context.logStreamName) : undefined;

  var responseBody = JSON.stringify({
    StackId: event.StackId,
    RequestId: event.RequestId,
    Status: responseStatus,
    Reason: reason,
    PhysicalResourceId: physicalResourceId || context.logStreamName,
    LogicalResourceId: event.LogicalResourceId,
    Data: responseData
  });

  var responseOptions = {
    headers: {
      'Content-Type': '',
      'Content-Length': responseBody.length
    }
  };

  console.info('Response body:\n', responseBody);

  try {
    await axios.put(event.ResponseURL, responseBody, responseOptions);

    console.info('CloudFormationSendResponse Success');
  } catch (error) {
    console.error('CloudFormationSendResponse Error:');

    if (error.response) {
      console.error(error.response.data);
      console.error(error.response.status);
      console.error(error.response.headers);
    } else if (error.request) {
      console.error(error.request);
    } else {
      console.error('Error', error.message);
    }

    console.error(error.config);

    throw new Error('Could not send CloudFormation response');
  }
}

有关在AWS Lambda上使用回调与异步的更多信息,请查看here

最后,请注意使用Axios。它基于承诺,因此支持等待而不是回调。