如何使用 Lambda 按固定间隔停止和启动 Amazon EC2 实例?

上次更新日期:2021 年 2 月 15 日

我想通过自动停止和启动 EC2 实例来减少 Amazon Elastic Compute Cloud (Amazon EC2) 使用量。如何使用 AWS Lambda 和 Amazon CloudWatch Events 来实现这一点?

简短描述

注意:此示例设置是一个简单的解决方案。若要获得更强大的解决方案,请使用 AWS Instance Scheduler。有关更多信息,请参阅如何使用 AWS Instance Scheduler 停止和启动实例?

要使用 Lambda 按固定间隔停止和启动 Amazon EC2 实例,请执行以下操作:

1.    为 Lambda 函数创建自定义 AWS Identity and Access Management (IAM) 策略和执行角色。

2.    创建停止和启动 EC2 实例的 Lambda 函数。

3.    测试 Lambda 函数。

4.    创建按计划触发函数的 CloudWatch Events 规则
注意:您还可以创建触发 AWS 账户中发生的事件的规则

或者,您可以使用本文末尾提供的 AWS CloudFormation 模板来自动执行该流程。

解决方法

注意:如果在完成以下流程后,收到错误客户端启动时出错,请按照这篇启动时客户端出错问题排查文章中列出的步骤操作。

获取要停止和启动的 EC2 实例的 ID。然后,执行以下操作。

为 Lambda 函数创建 IAM 策略和执行角色

1.    使用 JSON 策略编辑器创建 IAM 策略。复制下列 JSON 策略文档,并粘贴到策略编辑器中:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Start*",
        "ec2:Stop*"
      ],
      "Resource": "*"
    }
  ]
}

2.    为 Lambda 创建 IAM 角色

重要提示:将权限策略附加到 Lambda 时,请确保选择您刚刚创建的 IAM 策略。

创建停止和启动 EC2 实例的 Lambda 函数

1.    在 AWS Lambda 控制台中,选择 Create function(创建函数)

2.    选择 Author from scratch(从头开始创作)

3.    在基本信息下,添加以下内容:
函数名称中,输入一个名称,并将其识别为用于停止 EC2 实例的函数。例如,“StopEC2Instances”。
对于 Runtime (运行时),请选择 Python 3.8
Permissions (权限) 下,展开 Choose or create an execution role (选择或创建执行角色)
执行角色下,选择使用现有角色
现有角色下,选择所创建的 IAM 角色。

4.    选择创建函数

5.    在 Function code (函数代码) 下,将以下代码复制并粘贴到代码编辑器 (lambda_function) 的编辑器窗格中。此代码将停止您识别的 EC2 实例。

示例函数代码 – 停止 EC2 实例

import boto3
region = 'us-west-1'
instances = ['i-12345cb6de4f78g9h', 'i-08ce9b2d7eccf6d26']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.stop_instances(InstanceIds=instances)
    print('stopped your instances: ' + str(instances))

重要提示:对于 region (区域),用实例所在的 AWS 区域替换“us-west-1”。对于 instances (实例),将示例 EC2 实例 ID 替换为要停止和启动的特定实例的 ID。

6.    在基本设置下,将超时设置为 10 秒。

注意:根据您的使用案例需要配置 Lambda 函数设置。例如,如果要停止和启动多个实例,可能需要不同的 Timeout (超时)Memory (内存) 值。

7.    选择 Deploy (部署)

8.    重复步骤 1-7 以创建另一个函数。以不同的方式执行以下操作,以使此函数启动 EC2 实例:
在步骤 3 中,输入一个 Function name (函数名称),请不要与之前的函数名称重复。例如,“StartEC2Instances”。
在步骤 5 中,将以下代码复制并粘贴到代码编辑器 (lambda_function) 的编辑器窗格中:

示例函数代码 – 启动 EC2 实例

import boto3
region = 'us-west-1'
instances = ['i-12345cb6de4f78g9h', 'i-08ce9b2d7eccf6d26']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.start_instances(InstanceIds=instances)
    print('started your instances: ' + str(instances))

重要提示:对于 region (区域)instances (实例),请使用与代码中用于停止 EC2 实例的值相同的值。

测试 Lambda 函数

1.    在 AWS Lambda 控制台中,选择 Functions(函数)

2.    选择您创建的函数之一。

3.    选择 Test (测试)

4.    在 Configure test event (配置测试事件) 对话框中,选择 Create new test event (创建新测试事件)

5.    输入 Event name (事件名称)。 然后,选择 Create (创建)

注意:不需要为测试事件更改 JSON 代码,因为函数不使用它。

6.    选择 Test(测试)以运行该函数。

7.    对创建的另一个函数重复步骤 1-6。

提示:您可以在测试前后检查 EC2 实例的状态,以确认函数是否按预期工作。

创建触发 Lambda 函数的 CloudWatch Events 规则

1.    打开 Amazon CloudWatch console (Amazon CloudWatch 控制台)

2.    在左侧导航窗格的事件下,选择规则

3.    选择创建规则

4.    在事件源下,选择计划

5.    执行以下任意一项操作:
固定频率为中,输入以分钟、小时或天为单位的时间间隔。
Cron 表达式中,输入一个指示 Lambda 何时停止实例的表达式。有关表达式语法的信息,请参阅规则的计划表达式
注意
:Cron 表达式使用 UTC 进行计算。确保针对您的首选时区调整表达式。

6.    在目标下,选择添加目标

7.    选择 Lambda 函数

8.    对于函数,选择停止您的 EC2 实例的函数。

9.    选择 Configure details

10.    在规则定义下,执行以下操作:
名称中,输入一个识别规则的名称,例如“StopEC2Instances”。
(可选)在描述中,描述您的规则。例如,“每晚 10 点停止 EC2 实例。”
对于 State (状态),选择 Enabled (已启用) 复选框。

11.    选择创建规则

12.    重复步骤 1-11 以创建启动 EC2 实例的规则。以不同的方式执行以下操作:
在步骤 5 中,在 Cron 表达式中,输入一个指示 Lambda 何时启动实例的表达式。
在步骤 8 中,对于 Function (函数),选择启动您的 EC2 实例的函数。
在步骤 10中,在 Rule definition (规则定义) 下,输入 Name (名称),例如“StartEC2Instances”。
(可选)输入 Description (描述),例如“每天早上 7 点启动 EC2 实例。”

(可选)使用 AWS CloudFormation 模板自动执行该流程

1.    将以下 AWS CloudFormation 模板另存为 YAML 文件。

2.    在 YAML 文件中,输入所需的变量。

3.    在 Visual Studio 中部署 AWS CloudFormation 模板在 AWS 命令行界面 (AWS CLI) 中部署

注意:如果在运行 AWS CLI 命令时收到错误,请确保您使用的是最新版本的 AWS CLI

有关如何使用模板的更多信息,请参阅使用 AWS CloudFormation 模板

使用 Lambda 按固定间隔停止和启动 EC2 实例的 AWS CloudFormation 模板

AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function with cfn-response.
Parameters: 
  instances: 
    Default: i-12345678
    Description: Instance ID's seperated by commers 
    Type: String
  Region: 
    Description: region only 1 region supported 
    Type: String
  StopScheduled: 
    Default: cron(0 18 ? * MON-FRI *)
    Description: enter an Schedule expression example cron(0 18 ? * MON-FRI *) see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html
    Type: String
  StartScheduled: 
    Default: cron(0 7 ? * MON-FRI *)
    Description: enter an Schedule expression example cron(0 7 ? * MON-FRI * ) see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html
    Type: String
Resources:
  StopEC2Instances:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.8
      Role: !GetAtt Role.Arn
      Handler: index.lambda_handler
      Timeout: 60
      Environment:
         Variables:
          instances: !Ref instances
          Region: !Ref Region
      Code:
        ZipFile: |
          import json
          import re
          import os
          import boto3
          
          def lambda_handler(event, context):
            # TODO implement
            instances_str = os.environ['instances']
            region = os.environ['Region']
            ec2 = boto3.client('ec2', region_name=region)
            instances= re.findall(r"i-[0-9a-z]{17}|i-[0-9a-z]{8}", instances_str)
            print('stopped your instances: ' + str(instances) + "in Region "+ region)
            ec2.stop_instances(InstanceIds=instances)
            
            return {
              'statusCode': 200,
              'body': json.dumps('stopped your instances: ' + str(instances))
            }
      Description: Function that stops instances
  permissionForEventsToInvokeStopEC2Instances:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt StopEC2Instances.Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      "SourceArn" : !GetAtt StopScheduledRule.Arn

  StartEC2Instances:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.8
      Role: !GetAtt Role.Arn
      Handler: index.lambda_handler
      Timeout: 60
      Environment:
         Variables:
          instances: !Ref instances
          Region: !Ref Region
      Code:
        ZipFile: |
          import json
          import re
          import os
          import boto3
          
          def lambda_handler(event, context):
            # TODO implement
            instances_str = os.environ['instances']
            region = os.environ['Region']
            ec2 = boto3.client('ec2', region_name=region)
            instances= re.findall(r"i-[0-9a-z]{17}|i-[0-9a-z]{8}", instances_str)
            print('started your instances: ' + str(instances)+ "in Region "+ region)
            ec2.start_instances(InstanceIds=instances)
            
            return {
              'statusCode': 200,
              'body': json.dumps('started your instances: ' + str(instances))
            }
      Description: Function that started instances
  permissionForEventsToInvokeStartEC2Instances:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt StartEC2Instances.Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      "SourceArn" : !GetAtt StartScheduledRule.Arn

  Role:
    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: Ec2permissions
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                - "ec2:StartInstances"
                - "ec2:StopInstances"
                Resource: '*'
                

  StopScheduledRule: 
    Type: AWS::Events::Rule
    Properties: 
      Description: "ScheduledRule"
      ScheduleExpression: !Ref StopScheduled
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt StopEC2Instances.Arn
          Id: "TargetFunctionV1"
  StartScheduledRule: 
    Type: AWS::Events::Rule
    Properties: 
      Description: "ScheduledRule"
      ScheduleExpression: !Ref StartScheduled
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt StartEC2Instances.Arn
          Id: "TargetFunctionV1"