Пользовательский ресурс CloudFormation не заканчивает удаление - PullRequest
0 голосов
/ 09 сентября 2018

Я настроил свой пользовательский ресурс для немедленного возврата при удалении

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. Это почему?

В моих логах кажется, что лямбда правильно завершил выполнение:

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 голосов
/ 17 сентября 2018

У меня была похожая проблема сегодня при использовании пакета cfn-response, на котором, по-видимому, основан ваш код. Пакет cfn-response основан на обратном вызове, но ваш код также частично использует async / await (опция с Runtime: node.js8.10).

В вашем случае я подозреваю, что вы никогда не видели сообщений "STATUS:" или "HEADERS:", даже если тело ответа было выгружено в журналы (синхронно). Это отражает мой опыт при использовании основанного на обратном вызове cfn-response, смешанного с async / await.

Другими словами, при всех обстоятельствах вам нужно будет убедиться, что вы отправляете ответ на Cloudformation (PUT на событие S3 ResponseURL) до того, как ваша Lambda завершит свою работу, или шаблон может зависнуть до часа перед отказом и откатом (вероятно, с ошибкой Cloudformation по линии «Не удалось стабилизировать ресурс ...». Откат (удаление), в свою очередь, также может занять час, потому что удаление также не отвечает должным образом. Чуть больше информация здесь .

В итоге я реализовал пользовательские ресурсы, очень похожие на в этом примере на GitHub от https://github.com/rosberglinhares (лицензия MIT), с несколькими отличиями; Я не настроил отдельную лямбду для обработки функциональности sendResponse и создал пользовательские ресурсы без сервера (используя пакет облачной информации aws и команды развертывания облачной информации aws).

Ваш ApiMethodCustom не определен, поэтому мне сложно представить вам эту реализацию, и поэтому я включаю свой код node.js8.10, используя async / await для справки.

Сначала пользовательский ресурс в шаблоне 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 относится к пути шаблона. Вам нужно будет определить роль и политики IAM для CustomResourceRole.

Теперь для лямбды CustomResource / index.js (вам также нужно запустить «npm install --save axios» в каталоге CustomResource):

'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 смотрите здесь .

Наконец, обратите внимание на использование Axios . Он основан на обещаниях и поэтому поддерживает ожидание вместо обратных вызовов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...