如何使用 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": ,
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": ,
      "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 = 
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 = 
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
            region = os.environ
            ec2 = boto3.client('ec2', region_name=region)
            instances= re.findall(r"i-{17}|i-{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
            region = os.environ
            ec2 = boto3.client('ec2', region_name=region)
            instances= re.findall(r"i-{17}|i-{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"