通过 CloudWatch Events 检测 EC2 实例是否是 CloudFormation 的一部分
Detecting if EC2 instances are part of a CloudFormation via CloudWatch Events
当 CloudWatch Events 收到有关创建新 EC2 实例的通知时,检测该实例是否属于 CloudFormation 堆栈的最简单方法是什么?
我的第一个方法是在实例上调用 DescribeInstances
来查找 aws:cloudformation:stack-id
标签,但显然在添加该标签之前存在延迟,所以这变得很混乱。
我应该直接查询 CloudFormation API 吗?当我看到有关新 EC2 实例的 CloudWatch 事件时,如果我调用 CloudFormation API,是否有此 EC2 实例将显示为成员的排序保证?
要确定 CloudWatch Event 中返回的 EC2 实例 ID 是否对应于 运行 CloudFormation 堆栈中的实例,您可以调用 DescribeStackResources
API with the current stack ID as the StackName
request parameter。如果返回任何 StackResource
以及与事件返回的 EC2 实例 ID 匹配的 PhysicalResourceId
,那么您可以确定该事件对应于当前堆栈中的实例。
这是一个完整的工作示例:
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
wjordan's 中的答案对我有用并且似乎是可靠的并且没有任何竞争条件。
解决方案:一旦在 Cloudwatch 事件流上检测到实例,就针对 cloudformation API 调用 DescribeStackResources(..)
并传递 {PhysicalResourceId: instanceId}
。它将 return 实例所属的堆栈 ID,如果它不属于任何 cloudformation 堆栈,则为空结果。
我在 Go 中实现了它,并将其放在 tleyden/awsutil 存储库中的 github 上。
当 CloudWatch Events 收到有关创建新 EC2 实例的通知时,检测该实例是否属于 CloudFormation 堆栈的最简单方法是什么?
我的第一个方法是在实例上调用 DescribeInstances
来查找 aws:cloudformation:stack-id
标签,但显然在添加该标签之前存在延迟,所以这变得很混乱。
我应该直接查询 CloudFormation API 吗?当我看到有关新 EC2 实例的 CloudWatch 事件时,如果我调用 CloudFormation API,是否有此 EC2 实例将显示为成员的排序保证?
要确定 CloudWatch Event 中返回的 EC2 实例 ID 是否对应于 运行 CloudFormation 堆栈中的实例,您可以调用 DescribeStackResources
API with the current stack ID as the StackName
request parameter。如果返回任何 StackResource
以及与事件返回的 EC2 实例 ID 匹配的 PhysicalResourceId
,那么您可以确定该事件对应于当前堆栈中的实例。
这是一个完整的工作示例:
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
wjordan's
解决方案:一旦在 Cloudwatch 事件流上检测到实例,就针对 cloudformation API 调用 DescribeStackResources(..)
并传递 {PhysicalResourceId: instanceId}
。它将 return 实例所属的堆栈 ID,如果它不属于任何 cloudformation 堆栈,则为空结果。
我在 Go 中实现了它,并将其放在 tleyden/awsutil 存储库中的 github 上。