通过CloudWatch Events检测EC2实例是否属于CloudFormation

时间:2017-01-09 07:56:51

标签: amazon-web-services amazon-ec2 amazon-cloudformation

当CloudWatch Events收到有关创建新EC2实例的通知时,检测该实例是否属于CloudFormation堆栈的最简单方法是什么?

我的第一个方法是在实例上调用DescribeInstances来查找aws:cloudformation:stack-id标记,但显然在添加该标记之前会有一段延迟,所以这会变得混乱。

我应该直接查询CloudFormation API吗?当我看到关于新EC2实例的CloudWatch事件时,是否有一个订购保证,如果我调用CloudFormation API,此EC2实例将显示为成员?

2 个答案:

答案 0 :(得分:1)

要确定CloudWatch Event中返回的EC2实例ID是否与正在运行的CloudFormation堆栈中的实例相对应,您可以使用当前堆栈ID调用DescribeStackResources API作为StackName request parameter。如果返回任何StackResource并且PhysicalResourceId与事件返回的EC2实例ID匹配,那么您可以确定该事件对应于当前堆栈中的实例。

这是一个完整的,有效的例子:

Launch Stack

Description: Run a Lambda function when the EC2 instance is created using a CloudWatch Event.
Mappings:
  # amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2
  RegionMap:
    us-east-1:
      "64": "ami-9be6f38c"
Parameters:
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: m3.medium
    AllowedValues: [m3.medium, m3.large, m3.xlarge, m3.2xlarge, c3.large,
      c3.xlarge, c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge,
      r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge]
    ConstraintDescription: Please choose a valid instance type.
Resources:
  WebServer:
    Type: AWS::EC2::Instance
    DependsOn: EventLambdaPermission
    Properties:
      ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", 64]
      InstanceType: !Ref InstanceType
  EventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: EventRule
      EventPattern:
        source: ["aws.ec2"]
        detail-type: ["EC2 Instance State-change Notification"]
        detail:
          state: [pending]
      State: ENABLED
      Targets:
      - Arn: !GetAtt EC2StateChange.Arn
        Id: TargetFunction
  EC2StateChange:
    Type: AWS::Lambda::Function
    Properties:
      Description: Sends a Wait Condition signal to Handle when an EC2 Instance State-change from this stack is received.
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var AWS = require('aws-sdk');
          exports.handler = function(event, context) {
            console.log("Request received:\n", JSON.stringify(event));
            var instanceId = event.detail['instance-id'];
            var cloudformation = new AWS.CloudFormation();
            cloudformation.describeStackResources({StackName: '${AWS::StackId}'}).promise().then((stackData)=>{
              if (stackData.StackResources.find((stack)=> stack.PhysicalResourceId == instanceId)) {
                finish(context, {
                  "Status" : "SUCCESS",
                  "UniqueId" : "InstanceId",
                  "Data" : instanceId,
                  "Reason" : ""
                });
              } else {
                console.log("Instance ID not found in this stack");
                context.done();
              }
            }).catch((e)=>{
              console.log("Error:\n",JSON.stringify(e));
              finish(context, {
                "Status" : "FAILED",
                "UniqueId" : "InstanceId",
                "Data" : instanceId,
                "Reason" : e.message
              });
            });
          };
          function finish(context, response) {
            responseBody = JSON.stringify(response);
            var https = require("https");
            var url = require("url");
            var parsedUrl = url.parse('${Handle}');
            var options = {
                hostname: parsedUrl.hostname,
                port: 443,
                path: parsedUrl.path,
                method: "PUT",
                headers: {
                    "content-type": "",
                    "content-length": responseBody.length
                }
            };
            console.log("Options:\n", JSON.stringify(options));
            var request = https.request(options, function(response) {
                console.log("Status code: " + response.statusCode);
                console.log("Status message: " + response.statusMessage);
                console.log("Done!");
                context.done();
            });
            request.on("error", function(error) {
                console.log("send(..) failed executing https.request(..): " + error);
                context.done();
            });
            request.write(responseBody);
            request.end();
          }
      Timeout: 30
      Runtime: nodejs4.3
  EventLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref EC2StateChange
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt EventRule.Arn
  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/AWSLambdaBasicExecutionRole"
      Policies:
      - PolicyName: EC2Policy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
              - 'cloudformation:DescribeStackResources'
              Resource: ['*']
  Handle:
    Type: AWS::CloudFormation::WaitConditionHandle
  Wait:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: !Ref Handle
      Timeout: 300
Outputs:
  Result:
    Value: !GetAtt Wait.Data

答案 1 :(得分:0)

wjordan comment中的答案对我有用,似乎可靠,没有任何竞争条件。

解决方案:只要在Cloudwatch事件流中检测到实例,就针对cloudformation API调用DescribeStackResources(..)并传递{PhysicalResourceId: instanceId}。它将返回实例所属的堆栈ID,如果它不是任何cloudformation堆栈的一部分,则返回空结果。

我在Go中实现了它并将其放在tleyden/awsutil repo中的github上。