

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 教程：自动停止缺少所需标签的 Amazon EC2 实例
<a name="monitor-example"></a>

随着 AWS 资源池和 AWS 账户 您管理的资源池的增长，您可以使用标签来更轻松地对资源进行分类。标签通常用于关键使用案例，例如成本分配和安全性。为了有效地管理 AWS 资源，需要对您的资源进行一致的标记。通常，当资源在配置时会获得所有相应的标签。但是，稍后的流程可能会导致标签变更，从而偏离企业标签策略。通过监控标签变更，您可以发现标签偏差并立即做出响应。这样，对于那些依赖于对资源进行正确分类的流程会产生预期的结果，您就更有信心了。

以下示例演示如何监控 Amazon EC2 实例上的标签更改，以验证指定实例是否继续具有所需的标签。如果实例的标签发生变化并且该实例不再具有所需的标签，则调用 Lambda 函数来自动关闭该实例。您为什么要进行此操作？ 它可确保根据您的公司标签策略对所有资源进行标记，以实现有效的成本分配，或者能够信任[基于属性的访问权限控制（ABAC）](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_attribute-based-access-control.html) 的安全性。

**重要**  
我们强烈建议您在非生产账户中执行本教程，以免无意中关闭重要实例。  
本教程中的示例代码故意将这种情况的影响限制在实例列表中的实例上 IDs。您必须使用愿意关闭以进行测试的实例 IDs 来更新列表。这有助于确保您不会意外关闭您所在区域中的所有实例 AWS 账户。  
测试后，请确保根据贵公司的标记策略对所有实例进行标记。然后，您可以删除将函数限制为仅限列表中的实例 IDs 的代码。

此示例使用 JavaScript 和 16.x 版 Node.js。该示例使用示例 AWS 账户 ID 123456789012 和 AWS 区域 美国东部（弗吉尼亚北部）()。`us-east-1`将这些替换为您自己的测试账户 ID 和区域。

**注意**  
如果您的控制台默认使用其他区域，请确保在更改控制台时切换了本教程中使用的区域。本教程失败的一个常见原因：实例和函数位于两个不同的区域。

如果您使用的区域与 `us-east-1` 不同，请确保将以下代码示例中的所有引用更改为所选区域。

**Topics**
+ [步骤 1：创建 Lambda 函数](#monitor-example-step-1)
+ [步骤 2：设置所需的 IAM 权限](#monitor-example-step-2)
+ [步骤 3：对您的 Lambda 函数进行初步测试](#monitor-example-step-3)
+ [步骤 4：创建启动该函数的 EventBridge 规则](#monitor-example-step-4)
+ [步骤 5。测试完整的解决方案](#monitor-example-step-6)
+ [教程摘要](#summary)

## 步骤 1：创建 Lambda 函数
<a name="monitor-example-step-1"></a>

**创建 Lambda 函数**

1. 打开 [AWS Lambda 管理控制台](https://console.aws.amazon.com/lambda/home)。

1. 选择 **创建函数**，然后选择 **从头开始创作**。

1. 对于**函数名称**，请键入 **AutoEC2Termination**。

1. 对于**运行时系统**，选择 **Node.js 16.x**。

1. 将所有其他字段保留为默认值，然后选择**创建函数**。

1. 在 `AutoEC2Termination` 详情页面的**代码**选项卡上，打开 **index.js** 文件以查看其代码。
   + 如果已打开带有 **index.js** 的选项卡，则可以在该选项卡中选择编辑框来编辑代码。
   + 如果包含 **index.js** 的选项卡未打开，请在导航窗格中 **Auto T EC2 erminator 文件夹下单击相应**的 **index.js** 文件。然后选择 **Open**。

1. 在 **index.js** 选项卡中，将以下代码粘贴到编辑器框中，替换所有已有代码。

    将值 `RegionToMonitor` 替换为您想要在其中运行此函数的区域。

   ```
   // Set the following line to specify which Region's instances you want to monitor
   // Only instances in this Region are succesfully stopped on a match
   
   const RegionToMonitor = "us-east-1"
   
   // Specify the instance ARNs to check.
   // This limits the function for safety to avoid the tutorial shutting down all instances in account
   // The first ARN is a "dummy" that matches the test event you create in Step 3.
   // Replace the second ARN with one that matches a real instance that you want to monitor and that you can 
   // safely stop
   
   const InstanceList = [
       "i-0000000aaaaaaaaaa",
       "i-05db4466d02744f07"
   ];
   
   // The tag key name and value that marks a "valid" instance. Instances in the previous list that
   // do NOT have the following tag key and value are stopped by this function
   
   const ValidKeyName = "valid-key";
   const ValidKeyValue = "valid-value";
   
   // Load and configure the AWS SDK
   const AWS = require('aws-sdk');
   // Set the AWS Region
   AWS.config.update({region: RegionToMonitor});
   // Create EC2 service object.
   const ec2 = new AWS.EC2({apiVersion: '2016-11-15'});
   
   exports.handler = (event, context, callback) => {
   
     // Retrieve the details of the reported event.
     var detail = event.detail;
     var tags = detail["tags"];
     var service = detail["service"];
     var resourceType = detail["resource-type"];
     var resource = event.resources[0];
     var resourceSplit = resource.split("/");
     var instanceId = resourceSplit[resourceSplit.length - 1];
   
     // If this event is not for an EC2 resource, then do nothing.
     if (!(service === "ec2")) {
       console.log("Event not for correct service -- no action (", service, ")" );
       return;
     }
   
     // If this event is not about an instance, then do nothing.
     if (!(resourceType === "instance")) {
       console.log("Event not for correct resource type -- no action (", resourceType, ")" );
       return;
     }
   
     // CAUTION - Removing the following 'if' statement causes the function to run against 
     //           every EC2 instance in the specified Region in the calling AWS 账户. 
     //           If you do this and an instance is not tagged with the approved tag key 
     //           and value, this function stops that instance.
   
     // If this event is not for the ARN of an instance in our include list, then do nothing.
     if (InstanceList.indexOf(instanceId)<0) {
       console.log("Event not for one of the monitored instances -- no action (", resource, ")");
       return;
     }
   
     console.log("Tags changed on monitored EC2 instance (",instanceId,")");
   
     // Check attached tags for expected tag key and value pair
     if ( tags.hasOwnProperty(ValidKeyName) && tags[ValidKeyName] == "valid-value"){
       // Required tags ARE present
       console.log("The instance has the required tag key and value -- no action");
       callback(null, "no action");
       return;
     }
     
     // Required tags NOT present
     console.log("This instance is missing the required tag key or value -- attempting to stop the instance");
   
     var params = {
       InstanceIds: [instanceId], 
       DryRun: true
     };
   
     // call EC2 to stop the selected instances
     ec2.stopInstances(params, function(err, data) {
       if (err && err.code === 'DryRunOperation') {
         // dryrun succeeded, so proceed with "real" stop operation
         params.DryRun = false;
         ec2.stopInstances(params, function(err, data) {
           if (err) {
             console.log("Failed to stop instance");
             callback(err, "fail");
           } else if (data) {
             console.log("Successfully stopped instance", data.StoppingInstances);
             callback(null, "Success");
           }
         });
       } else {
         console.log("Dryrun attempt failed");
         callback(err);
       }
     });
   };
   ```

1. 选择**部署**以保存您的更改并激活新版本函数。

此 Lambda 函数会检查 Amazon EC2 实例的标签，如中的标签更改事件所报告的那样。 EventBridge在此示例中，如果事件中的实例缺少所需的标签键 `valid-key` 或该标签没有值 `valid-value`，则该函数会尝试停止该实例。您可以根据自己的特定用例更改此逻辑检查或标签要求。

使 Lambda 控制台浏览器窗口保持打开状态。

## 步骤 2：设置所需的 IAM 权限
<a name="monitor-example-step-2"></a>

您必须向该函数授予停止 EC2 实例的权限，函数才能成功运行。 AWS 提供的角色[https://console.aws.amazon.com/iamv2/home#/roles/details/lambda_basic_execution](https://console.aws.amazon.com/iamv2/home#/roles/details/lambda_basic_execution)没有该权限。在本教程中，您将修改附加到名为 `AutoEC2Termination-role-uniqueid` 的函数执行角色的默认 IAM 权限策略。本教程所需的最低额外权限为 `ec2:StopInstances`。

有关创建特定于 Amazon EC2 的 IAM 策略的更多信息，请参阅 *IAM 用户指南*中的 [Amazon EC2: 允许以编程方式和在控制台中启动或停止特定的 EC2 实例并修改特定的安全组](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_ec2_instance-securitygroup.html)。

**创建 IAM 权限策略并将其附加到 Lambda 函数的执行角色**

1. 在另一浏览器选项卡或窗口中，打开 IAM 控制台的[角色](https://console.aws.amazon.com/iamv2/home#/roles)页面。

1. 开始键入角色名称 **AutoEC2Termination** ，当角色名称出现在列表中时，选择角色名称。

1. 在角色的**摘要**页面上，选择**权限**选项卡，然后选择已附加的一个策略的名称。

1. 在策略的**摘要**页面上，选择 **编辑策略**。

1. 在**可视化编辑器**选项卡上，选择**添加额外权限**。

1. 对于**服务**，选择 **EC2**。

1. 在 “**操作**” 中，选择**StopInstances**。您可以在搜索栏中键入 **Stop** ，在它出现时选中 `StopInstances`。

1. 对于**资源**，选择**所有资源**，选择**检查策略**，然后选择**保存更改**。

   这将自动创建新版本的策略并将该版本设置为默认版本。

   您的最终策略应类似于以下示例。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Sid": "VisualEditor0",
               "Effect": "Allow",
               "Action": "ec2:StopInstances",
               "Resource": "*"
           },
           {
               "Sid": "VisualEditor1",
               "Effect": "Allow",
               "Action": "logs:CreateLogGroup",
               "Resource": "arn:aws:logs:us-east-1:123456789012:*"
           },
           {
               "Sid": "VisualEditor2",
               "Effect": "Allow",
               "Action": [
                   "logs:CreateLogStream",
                   "logs:PutLogEvents"
               ],
               "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/AutoEC2Termination:*"
           }
       ]
   }
   ```

------

## 步骤 3：对您的 Lambda 函数进行初步测试
<a name="monitor-example-step-3"></a>

在本步骤中，您将向函数提交测试事件。Lambda 测试功能通过提交手动提交的测试事件来运行。该函数处理测试事件，就像事件来自一样 EventBridge。您可以定义多个具有不同值的测试事件，以测试代码的各个不同部分。在此步骤中，您将提交一个测试事件，表明 Amazon EC2 实例的标签已更改，并且新标签不包含所需的标签键和值。

**测试 Lambda 函数**

1. 使用 Lambda 控制台返回窗口或选项卡，然后打开**自动EC2终止**函数的 “**测试**” 选项卡。

1. 选择 **创建新事件**。

1. 对于**事件名称**，输入 **SampleBadTagChangeEvent**。

1. 在**事件 JSON** 中，将文本替换为以下示例文本中显示的示例事件。您无需修改账户、地区或实例 ID，此测试事件即可正常运行。

   ```
   {
     "version": "0",
     "id": "bddcf1d6-0251-35a1-aab0-adc1fb47c11c",
     "detail-type": "Tag Change on Resource",
     "source": "aws.tag",
     "account": "123456789012",
     "time": "2018-09-18T20:41:38Z",
     "region": "us-east-1",
     "resources": [
       "arn:aws:ec2:us-east-1:123456789012:instance/i-0000000aaaaaaaaaa"
     ],
     "detail": {
       "changed-tag-keys": [
         "valid-key"
       ],
       "tags": {
         "valid-key": "NOT-valid-value"
       },
       "service": "ec2",
       "resource-type": "instance",
       "version": 3
     }
   }
   ```

1. 选择 **Save**（保存），然后选择 **Test**（测试）。

   测试似乎失败了，但没关系。

   您应该会在**响应**下的**执行结果**选项卡中看到以下错误。

   ```
   {
     "errorType": "InvalidInstanceID.NotFound",
     "errorMessage": "The instance ID 'i-0000000aaaaaaaaaa' does not exist",
     ...
   }
   ```

   之所以出现错误，是因为测试事件中指定的实例不存在。

   **函数日志**部分的**执行结果**选项卡上的信息表明，您的 Lambda 函数已尝试停止 EC2 实例。但是，它失败了，因为代码最初尝试执行停止实例的 [https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html#API_StartInstances_RequestParameters](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html#API_StartInstances_RequestParameters) 操作，这表明实例 ID 无效。

   ```
   START RequestId: 390c1f8d-0d9b-4b44-b087-8de64479ab44 Version: $LATEST
   2022-11-30T20:17:30.427Z    390c1f8d-0d9b-4b44-b087-8de64479ab44    INFO    Tags changed on monitored EC2 instance ( i-0000000aaaaaaaaaa )
   2022-11-30T20:17:30.427Z    390c1f8d-0d9b-4b44-b087-8de64479ab44    INFO    This instance is missing the required tag key or value -- attempting to stop the instance
   2022-11-30T20:17:31.206Z    390c1f8d-0d9b-4b44-b087-8de64479ab44    INFO    Dryrun attempt failed
   2022-11-30T20:17:31.207Z    390c1f8d-0d9b-4b44-b087-8de64479ab44    ERROR   Invoke Error     {"errorType":"InvalidInstanceID.NotFound","errorMessage":"The instance ID 'i-0000000aaaaaaaaaa' does not exist","code":"InvalidInstanceID.NotFound","message":"The instance ID 'i-0000000aaaaaaaaaa' does not exist","time":"2022-11-30T20:17:31.205Z","requestId":"a5192c3b-142d-4cec-bdbc-685a9b7c7abf","statusCode":400,"retryable":false,"retryDelay":36.87870631147607,"stack":["InvalidInstanceID.NotFound: The instance ID 'i-0000000aaaaaaaaaa' does not exist","    at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/services/ec2.js:50:35)","    at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)","    at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10)","    at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:686:14)","    at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)","    at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)","    at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10","    at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)","    at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:688:12)","    at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18)"]}
   END RequestId: 390c1f8d-0d9b-4b44-b087-8de64479ab44
   ```

1. 要证明代码在使用正确标签时不会尝试停止实例，您可以创建并提交另一个测试事件。

   选择**代码源**上方的**测试**选项卡。控制台显示您现有的**SampleBadTagChangeEvent**测试事件。

1. 选择 **创建新事件**。

1. 对于**事件名称**，键入 **SampleGoodTagChangeEvent**。

1. 在第 17 行中，删除 **NOT-**，将值更改为 **valid-value**。

1. 在**测试事件**窗口的顶部，选择**保存**，然后选择**测试**。

   输出显示以下内容，这表明该函数可以识别有效标签并且不会尝试关闭实例。

   ```
   START RequestId: 53631a49-2b54-42fe-bf61-85b9e91e86c4 Version: $LATEST
   2022-12-01T23:24:12.244Z    53631a49-2b54-42fe-bf61-85b9e91e86c4    INFO    Tags changed on monitored EC2 instance ( i-0000000aaaaaaaaaa )
   2022-12-01T23:24:12.244Z    53631a49-2b54-42fe-bf61-85b9e91e86c4    INFO    The instance has the required tag key and value -- no action
   END RequestId: 53631a49-2b54-42fe-bf61-85b9e91e86c4
   ```

   在浏览器中保持 Lambda 控制台处于打开状态。

## 步骤 4：创建启动该函数的 EventBridge 规则
<a name="monitor-example-step-4"></a>

现在，您可以创建与事件匹配并指向您的 Lambda 函数的 EventBridge 规则。

**创建 EventBridge 规则**

1. 在其他浏览器选项卡或窗口中，打开[EventBridge 控制台](https://console.aws.amazon.com/events/home#/rules/create)，进入 “**创建规则**” 页面。

1. 对于**名称**，输入 **ec2-instance-rule**，然后选择**下一步**。

1. 向下滚动到**创建方法**并选择**自定义模式（JSON 编辑器）**。

1. 在编辑框中，粘贴以下图案文本，然后选择**下一步**。

   ```
   {
     "source": [
       "aws.tag"
     ],
     "detail-type": [
       "Tag Change on Resource"
     ],
     "detail": {
       "service": [
         "ec2"
       ],
       "resource-type": [
         "instance"
       ]
     }
   }
   ```

   此规则匹配 Amazon EC2 实例的 `Tag Change on Resource` 事件，并在下一步中调用您指定为**目标**的任何内容。

1. 接下来，将 Lambda 函数添加为目标。在**目标 1**框中，在**选择目标**下，选择 **Lambda 函数**。

1. 在 “**函数**” 下，选择您之前创建的**自动EC2终止**函数，然后选择 “**下一步**”。

1. 请在 **配置标签**页面上，选择 **下一步**。然后，请在 **审核和创建**页面上，选择 **创建规则**。这还会自动授予调用 EventBridge 指定 Lambda 函数的权限。

## 步骤 5。测试完整的解决方案
<a name="monitor-example-step-6"></a>

您可以通过创建 EC2 实例并观察更改其标签时会发生什么来测试最终结果。

**使用真实实例测试监控解决方案**

1. 打开 [Amazon EC2 控制台](https://console.aws.amazon.com/ec2/v2/home#Instances:)来到 **实例**页面。

1. 创建 Amazon EC2 实例。在启动之前，请附加带有密钥 `valid-key` 和值 `valid-value` 的标签。有关创建和启动实例的信息，请参阅《Amazon EC2 用户指南》**中的[步骤 1：启动实例](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html#ec2-launch-instance)。在*启动实例*的过程中，在步骤 3 中，输入**名称**标签，还要选择**添加额外标签**，选择**添加标签**，然后输入**密钥** **valid-key** 和**值** **valid-value**。如果此实例仅用于本教程，并且您计划在完成后删除此实例，您可以**在没有密钥对的情况下继续**。完成**步骤 1** 后，返回本教程即可，您无需执行步骤 **2：连接到实例**。

1. **InstanceId**从控制台复制。

1. 从 Amazon EC2 控制台切换到 Lambda 控制台。选择您的**自动EC2终止**功能，选择**代码**选项卡，然后选择 **index.js** 选项卡来编辑您的代码。

1. 粘贴从 Amazon EC2 控制台复制的值，以更改 `InstanceList` 中的第二个条目。确保该 `RegionToMonitor` 值与包含您粘贴的实例的区域相匹配。

1. 选择**部署**以激活您的更改。现在，该函数已准备就绪，可以通过在指定区域对该实例进行标签更改来激活。

1. 从 Lambda 控制台切换到 Amazon EC2 控制台。

1. 通过删除**有效密钥**标签或更改该密钥的值来更改附加到实例的**标签**。
**注意**  
有关如何在正在运行的 Amazon EC2 实例上更改标签的信息，请参阅《Amazon EC2 用户指南》**中的[在单个资源上添加和删除标签](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#adding-or-deleting-tags)。

1. 等待几秒钟，然后刷新控制台。该实例应将其**实例状态**更改为**正在停止**，然后更改为**已停止**。

1. 运用函数，从 Amazon EC2 控制台切换到 Lambda 控制台，然后选择**监控**选项卡。

1. 选择 “**日志**” 选项卡，然后在 “**最近的调用**” 表中，选择该列中的最新条目。**LogStream**

   Amazon CloudWatch 控制台会打开您的 Lambda 函数的最后一次调用的 “**日志事件**” 页面。最后一条条目应类似于以下示例。

   ```
   2022-11-30T12:03:57.544-08:00    START RequestId: b5befd18-2c41-43c8-a320-3a4b2317cdac Version: $LATEST
   2022-11-30T12:03:57.548-08:00    2022-11-30T20:03:57.548Z b5befd18-2c41-43c8-a320-3a4b2317cdac INFO Tags changed on monitored EC2 instance ( arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0 )
   2022-11-30T12:03:57.548-08:00    2022-11-30T20:03:57.548Z b5befd18-2c41-43c8-a320-3a4b2317cdac INFO This instance is missing the required tag key or value -- attempting to stop the instance
   2022-11-30T12:03:58.488-08:00    2022-11-30T20:03:58.488Z b5befd18-2c41-43c8-a320-3a4b2317cdac INFO Successfully stopped instance [ { CurrentState: { Code: 64, Name: 'stopping' }, InstanceId: 'i-1234567890abcdef0', PreviousState: { Code: 16, Name: 'running' } } ]
   2022-11-30T12:03:58.546-08:00    END RequestId: b5befd18-2c41-43c8-a320-3a4b2317cdac
   ```

## 教程摘要
<a name="summary"></a>

本教程演示了如何创建 EventBridge 规则以与 Amazon EC2 实例的资源事件的标签更改相匹配。该规则指向一个 Lambda 函数，如果实例没有所需的标签，该函数会自动关闭该实例。

Amazon 对资源标签更改的 EventBridge 支持为在 AWS 许多资源中构建事件驱动的自动化开辟了可能性。 AWS 服务将此功能与之相结合， AWS Lambda 可为您提供构建无服务器解决方案的工具，这些解决方案可安全访问 AWS 资源、按需扩展且具有成本效益。

该 tag-change-on-resource EventBridge 活动的其他可能用例包括：
+ **如果有人从异常 IP 地址访问您的资源，会发出警告** – 使用标签，储存访问您的资源的每位访客的源 IP 地址。对标签的更改会生成一个 CloudWatch 事件。您可以使用该事件将源 IP 地址与有效 IP 地址列表进行比较，并在源 IP 地址无效时激活警告电子邮件。
+ **监控资源的基于标签的访问控制是否发生了变化 — 如果您使用基于**[属性（标签）的访问控制 (ABAC) 设置了对资源的访问权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_attribute-based-access-control.html)，则可以使用对标签进行任何更改所生成 EventBridge的事件来提示安全团队进行审计。