Como interrompo e inicio instâncias do Amazon EC2 em intervalos regulares usando o Lambda?

Última atualização: 15/02/2021

Quero interromper e iniciar minhas instâncias do EC2 automaticamente para reduzir o uso do Amazon Elastic Compute Cloud (Amazon EC2). Como uso o AWS Lambda e o Amazon CloudWatch Events para fazer isso?

Descrição breve

Observação: a solução apresentada neste exemplo é simples. Se você quiser uma solução mais robusta, use o AWS Instance Scheduler. Para obter mais informações, consulte Como interrompo e inicio instâncias usando o AWS Instance Scheduler?

Para interromper e iniciar instâncias do EC2 em intervalos regulares usando o Lambda, faça o seguinte:

1.    Crie uma política e uma função personalizadas do AWS Identity and Access Management (IAM) para a função do Lambda.

2.    Crie funções do Lambda que interrompam e iniciem suas instâncias do EC2.

3.    Teste as funções do Lambda.

4.    Crie regras do CloudWatch Events que acionem a função do Lambda conforme uma agenda.
Observação: também é possível criar regras que sejam acionadas quando um evento ocorrer em sua conta da AWS.

Você também pode usar o modelo do AWS CloudFormation que se encontra no fim deste artigo para automatizar o procedimento.

Resolução

Observação: se você receber o erro Client error on launch depois de concluir o procedimento, siga as etapas descritas no artigo Corrigir o erro Client error on launch.

Obtenha os IDs das instâncias do EC2 que você quer interromper e iniciar. Depois, siga as etapas abaixo.

Crie uma política e uma função de execução do IAM para a função do Lambda

1.    Crie uma política do IAM usando o editor de políticas JSON. Copie e cole o seguinte documento da política JSON no editor:

{
  "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.    Crie uma função do IAM para o Lambda.

Importante: ao associar uma política de permissão ao Lambda, lembre-se de escolher a política do IAM que você acabou de criar.

Crie funções do Lambda que interrompam e iniciem suas instâncias do EC2

1.    No console do AWS Lambda, escolha Create function (Criar função).

2.    Escolha Author from scratch (Criar do zero).

3.    Em Basic information (Informações básicas), adicione o seguinte:
Em Function name (Nome da função), insira um nome que a identifique como a função usada para interromper as instâncias do EC2. Por exemplo, "StopEC2Instances".
Em Runtime (Tempo de execução), escolha Python 3.8.
Em Permissions (Permissões), expanda Choose or create an execution role (Escolher ou criar função de execução).
Em Execution role (Função de execução), escolha Use an existing role (Usar função existente).
Em Existing role (Função existente), escolha a função do IAM criada.

4.    Escolha Create function (Criar função).

5.    Em Function code (Código da função), copie e cole o código abaixo no painel de edição do editor de código (lambda_function). Esse código interrompe as instâncias do EC2 identificadas por você.

Exemplo de código da função: interrupção de instâncias do 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))

Importante: em region, substitua "us-west-1" pela região da AWS das instâncias. Em instances, substitua os exemplos de IDs pelos das instâncias específicas que você quer interromper e iniciar.

6.    Em Basic settings (Configurações básicas), defina o Timeout (Tempo-limite) para 10 segundos.

Observação: modifique as configurações da função do Lambda conforme a necessidade do caso de uso. Por exemplo, se você quiser interromper e iniciar várias instâncias, precisará de valores diferentes no Timeout (Tempo-limite) e na Memory (Memória).

7.    Escolha Deploy (Implantar).

8.    Repita as etapas de 1 a 7 para criar outra função. Faça as seguintes alterações para que a função inicie as instâncias do EC2:
Na etapa 3, insira um Function name (Nome da função) diferente do que você usou antes. Por exemplo, “StartEC2Instances”.
Na etapa 5, copie e cole o código abaixo no painel de edição do editor de código (lambda_function):

Exemplo de código da função: início de instâncias do 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))

Importante: em region e instances, use os mesmos valores usados no código para interromper as instâncias do EC2.

Teste as funções do Lambda

1.    No console do AWS Lambda, escolha Functions (Funções).

2.    Escolha uma das funções criadas.

3.    Escolha Test (Testar).

4.    Na caixa de diálogo Configure test event (Configurar evento de teste), escolha Create new test event (Criar evento de teste).

5.    Insira o Event name (Nome do evento). Depois, escolha Create (Criar).

Observação: não é preciso alterar o código JSON para o evento de teste, pois a função não o usa.

6.    Escolha Test (Testar) para executar a função.

7.    Repita as etapas de 1 a 6 para a outra função criada.

Dica: é possível conferir o status das instâncias do EC2 antes e depois do teste para confirmar se as funções funcionaram como esperado.

Crie regras do CloudWatch Events que acionem a função do Lambda conforme uma agenda

1.    Abra o console do Amazon CloudWatch.

2.    No painel de navegação à esquerda, em Events (Eventos), escolha Rules (Regras).

3.    Escolha Create rule (Criar regra).

4.    Em Event Source (Fonte do evento), escolha Schedule (Agenda).

5.    Faça uma das seguintes alterações:
Em Fixed rate of (Intervalo regular de), insira um período em minutos, horas ou dias.
Em Cron expression (Expressão cron), insira uma expressão que informe ao Lambda quando ele deve interromper as instâncias. Para obter mais informações sobre a sintaxe de expressões, consulte Agendar expressões para regras.
Observação
: as expressões cron seguem o formato UTC. Lembre-se de ajustar a expressão de acordo com o fuso horário de sua preferência.

6.    Em Targets (Alvos), escolha Add target (Adicionar alvo).

7.    Escolha Lambda function (Função do Lambda).

8.    Em Function, escolha a função que interrompe as instâncias do EC2.

9.    Escolha Configure details (Configurar detalhes).

10.    Em Rule definition (Definição da regra), faça o seguinte:
Em Name (Nome), insira um nome que identifique a regra, como “StopEC2Instances”.
(Opcional) Em Description (Descrição), descreva a regra. Por exemplo, “Interrompe as instâncias do EC2 toda noite às 22h”.
Em State (Estado), marque a caixa de seleção Enabled (Habilitada).

11.    Escolha Create rule (Criar regra).

12.    Repita as etapas de 1 a 11 para criar uma regra que inicie as instâncias do EC2. Faça as seguintes alterações:
Na etapa 5, em Cron expression (Expressão cron), insira uma expressão que informe ao Lambda quando ele deve iniciar as instâncias.
Na etapa 8, em Function (Função), escolha a função que inicia as instâncias do EC2.
Na etapa 10, em Rule definition (Definição da regra), insira um Name (Nome), como “StartEC2Instances”.
(Opcional) Insira uma Description (Descrição), como “Inicia as instâncias do EC2 toda manhã às 7h”.

(Opcional) Use um modelo do AWS CloudFormation para automatizar o procedimento

1.    Salve o modelo do AWS CloudFormation a seguir como um arquivo YAML.

2.    No arquivo YAML, insira as variáveis necessárias.

3.    Implante o modelo do AWS CloudFormation no Visual Studio ou na AWS Command Line Interface (AWS CLI).

Observação: se você receber erros ao executar os comandos na AWS CLI, verifique se está usando a versão mais recente da interface.

Para obter mais informações sobre como usar o modelo, consulte Usar modelos do AWS CloudFormation.

Modelo do AWS CloudFormation para interromper e iniciar instâncias do EC2 em intervalos regulares usando o Lambda

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"