Skip to content

Instantly share code, notes, and snippets.

@ValterAndrei
Last active October 23, 2025 18:50
Show Gist options
  • Save ValterAndrei/9f3ce4a0ccce2840b7c451302f26127e to your computer and use it in GitHub Desktop.
Save ValterAndrei/9f3ce4a0ccce2840b7c451302f26127e to your computer and use it in GitHub Desktop.
Automating Instance Start/Stop - AWS, Lambda, EC2, RDS and Ruby

🚀 AWS Lambda: Automação de Instâncias EC2 e RDS

📋 Visão Geral

Automatize o gerenciamento de instâncias EC2 e RDS com AWS Lambda, incluindo notificações em tempo real via Slack para monitoramento simplificado do ambiente.

⚙️ Especificações Técnicas

  • Linguagem: Ruby 3.4
  • Serviços AWS:
    • EC2 (Elastic Compute Cloud)
    • RDS (Relational Database Service)
    • Lambda (Serverless Functions)
    • EventBridge (Agendamento)
    • CloudWatch (Logs e Monitoramento)
    • SNS (Simple Notification Service)

📚 Guia de Implementação

1️⃣ Configuração de Permissões IAM

Configure as políticas de acesso necessárias para que as funções Lambda possam gerenciar os recursos EC2 e RDS.

📖 Acessar Documentação Completa


2️⃣ Lambda para Iniciar Instâncias

Implemente a função Lambda responsável por inicializar as instâncias EC2 e RDS nos horários programados.

📖 Acessar Documentação Completa


3️⃣ Lambda para Parar Instâncias

Configure a função Lambda que desliga as instâncias EC2 e RDS automaticamente, otimizando custos operacionais.

📖 Acessar Documentação Completa


💡 Benefícios

  • ✅ Redução de custos com automação de recursos
  • ✅ Monitoramento proativo via Slack
  • ✅ Agendamento flexível com EventBridge
  • ✅ Logs centralizados no CloudWatch
  • ✅ Infraestrutura como código serverless
@ValterAndrei
Copy link
Author

ValterAndrei commented Oct 23, 2025

🔐 Configuração de Permissões IAM

📝 Passo a Passo

1️⃣ Criar Política IAM

Configure a política que define as permissões necessárias para gerenciar instâncias EC2 e RDS.

🎯 Passos

  1. Navegue até: IAM → Policies → Create policy → JSON
  2. Cole o JSON abaixo no editor
  3. Clique em Next
  4. Defina o nome da política: start-stop-instance-policy
  5. Finalize clicando em Create policy
📄 JSON da Política
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "rds:DescribeDBInstances",
        "rds:StopDBInstance",
        "rds:StartDBInstance"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

✅ Permissões Incluídas

  • EC2: Listar, iniciar e parar instâncias
  • RDS: Listar, iniciar e parar instâncias de banco de dados
  • CloudWatch Logs: Criar e registrar logs

2️⃣ Criar Role IAM para Lambda

Crie a role que será assumida pelas funções Lambda para executar as operações.

🎯 Passos

  1. Navegue até: IAM → Roles → Create role
  2. Selecione o tipo de entidade confiável: AWS service
  3. Escolha o caso de uso: Lambda
  4. Clique em Next
  5. Defina o nome da role: start-stop-instance-role
  6. Finalize clicando em Create role
📄 JSON de Confiança (Trust Policy)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

🔑 Informações da Role

  • Tipo: Service Role
  • Serviço Confiável: AWS Lambda
  • Propósito: Permitir que funções Lambda assumam esta role

3️⃣ Anexar Política à Role

Vincule a política criada anteriormente à role do Lambda.

🎯 Passos

  1. Navegue até: IAM → Roles → start-stop-instance-role
  2. Clique em Add permissions → Attach policies
  3. Use a barra de pesquisa para encontrar: start-stop-instance-policy
  4. Marque a checkbox ao lado da política
  5. Clique em Add permissions para confirmar

✅ Resultado

Sua role agora possui todas as permissões necessárias para:

  • Gerenciar instâncias EC2 e RDS
  • Registrar logs no CloudWatch
  • Ser assumida por funções Lambda

@ValterAndrei
Copy link
Author

ValterAndrei commented Oct 23, 2025

▶️ Lambda para Iniciar Instâncias

📝 Configuração da Função Lambda

1️⃣ Criar Função Lambda

Configure a função Lambda responsável por inicializar as instâncias EC2 e RDS automaticamente.

🎯 Passos

  1. Navegue até: Lambda → Create function
  2. Preencha os campos:
    • Function name: start_instances
    • Runtime: Ruby 3.4
  3. Em Permissions, selecione a role: start-stop-instance-role
  4. Clique em Create function

2️⃣ Estrutura de Arquivos

A função Lambda deve conter dois arquivos Ruby com as seguintes responsabilidades:

📄 lambda_function.rb - Handler Principal

Arquivo principal que gerencia o fluxo de inicialização das instâncias.

💻 Ver código completo
require_relative 'holiday'
require 'aws-sdk-ec2'
require 'aws-sdk-rds'


def lambda_handler(event:, context:)
  logger = Logger.new($stdout)
  logger.info(event)

  # Environment variables
  bastion_id      = ENV.fetch('BASTION_ID')
  dev_app_id      = ENV.fetch('DEV_APP_ID')
  rds_instance_id = ENV.fetch('RDS_INSTANCE_ID')
  region          = ENV.fetch('REGION')

  # Create AWS Clients
  ec2_client = Aws::EC2::Client.new(region:)
  rds_client = Aws::RDS::Client.new(region:)

  # Check if EC2 instances are in a state available to start
  ec2_response = ec2_client.describe_instances(instance_ids: [bastion_id, dev_app_id])
  ec2_instances = ec2_response.reservations.flat_map(&:instances)

  ec2_instances.each do |instance|
    ec2_instance_status = instance.state.name

    if ec2_instance_status != 'stopped'
      return {
        status: 422,
        message: "A instância EC2 #{instance.instance_id} não está em um estado disponível para ser ligada.",
        state: ec2_instance_status
      }
    end
  end

  # Check if RDS instance is in a state available to start
  rds_response = rds_client.describe_db_instances(db_instance_identifier: rds_instance_id)
  rds_instance = rds_response.db_instances.first
  rds_instance_status = rds_instance.db_instance_status

  if rds_instance_status != 'stopped'
    return {
      status: 422,
      message: "A instância RDS #{rds_instance_id} não está em um estado disponível para ser ligada.",
      state: rds_instance_status
    }
  end

  # Check holiday
  today = Date.today
  if brazilian_holiday?(event, today)
    holiday_name = Holiday.new(today).holiday_name
    send_slack_message_for_holiday(holiday_name)

    return {
      status: 422,
      message: "Inicialização automática interrompida devido ao feriado: #{holiday_name}."
    }
  end

  # Start instances
  ec2_client.start_instances(instance_ids: [bastion_id, dev_app_id])
  rds_client.start_db_instance(db_instance_identifier: rds_instance_id)

  # Send Slack message
  send_slack_message_for_success

  # Return success message from Lambda
  {
    status: 200,
    message: 'Instâncias ligadas com sucesso.'
  }
end

def brazilian_holiday?(event, date)
  event['check_holiday'] && Holiday.new(date).holiday?
end

def send_slack_message_for_holiday(holiday_name)
  message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: ":spiral_calendar_pad: #{holiday_name}"
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '_Inicialização automática interrompida devido ao feriado._'
        },
        accessory: {
          type: 'button',
          text: {
            type: 'plain_text',
            text: 'Ligar instâncias'
          },
          url: ENV.fetch('START_FUNCTION_URL')
        }
      }
    ]
  }

  uri = URI(ENV.fetch('SLACK_WEBHOOK_URL'))
  Net::HTTP.post(uri, message.to_json, 'Content-Type' => 'application/json')
end

def send_slack_message_for_success
  message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: ':large_green_circle: Instâncias ligadas'
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '_Clique no botão para *desligar* as instâncias._'
        },
        accessory: {
          type: 'button',
          text: {
            type: 'plain_text',
            text: 'Desligar instâncias'
          },
          url: ENV.fetch('STOP_FUNCTION_URL'),
          style: 'danger',
          confirm: {
            title: {
              type: 'plain_text',
              text: 'Desligar instâncias'
            },
            text: {
              type: 'mrkdwn',
              text: 'Você tem certeza que deseja desligar as instâncias?'
            },
            confirm: {
              type: 'plain_text',
              text: 'Sim, desligar'
            },
            deny: {
              type: 'plain_text',
              text: 'Cancelar'
            }
          }
        }
      }
    ]
  }

  uri = URI(ENV.fetch('SLACK_WEBHOOK_URL'))
  Net::HTTP.post(uri, message.to_json, 'Content-Type' => 'application/json')
end

🔍 Funcionalidades:

  • Valida estado das instâncias EC2 e RDS antes de iniciar
  • Verifica feriados brasileiros para evitar inicialização desnecessária
  • Envia notificações personalizadas para o Slack
  • Inclui botões interativos para controle manual

📄 holiday.rb - Gerenciador de Feriados

Classe responsável por identificar feriados nacionais brasileiros.

💻 Ver código completo
class Holiday

  attr_reader :date, :year

  def initialize(date)
    @date = date
    @year = date.year
  end

  def holiday?
    holidays.key?(date)
  end

  def holiday_name
    holidays[date]
  end

  private

  def holidays
    @holidays ||= {
      # fixed holidays
      # ---
      Date.new(year, 1, 1)   => 'Confraternização Universal',
      Date.new(year, 4, 21)  => 'Tiradentes',
      Date.new(year, 5, 1)   => 'Dia do Trabalho',
      Date.new(year, 7, 9)   => 'Revolução Constitucionalista',
      Date.new(year, 9, 7)   => 'Independência do Brasil',
      Date.new(year, 10, 12) => 'Nossa Senhora Aparecida',
      Date.new(year, 11, 2)  => 'Dia de Finados',
      Date.new(year, 11, 15) => 'Proclamação da República',
      Date.new(year, 11, 20) => 'Dia da Consciência Negra',
      Date.new(year, 12, 8)  => 'Nossa Senhora da Conceição',
      Date.new(year, 12, 25) => 'Natal',

      # floating holidays
      # ---
      carnaval       => 'Carnaval',
      good_friday    => 'Paixão de Cristo',
      corpus_christi => 'Corpus Christi'
    }
  end

  def easter_sunday
    a = year % 19
    b = year / 100
    c = year % 100
    d = b / 4
    e = b % 4
    f = (b + 8) / 25
    g = (b - f + 1) / 3
    h = ((19 * a) + b - d - g + 15) % 30
    i = c / 4
    k = c % 4
    l = (32 + (2 * e) + (2 * i) - h - k) % 7
    m = (a + (11 * h) + (22 * l)) / 451
    month = (h + l - (7 * m) + 114) / 31
    day = ((h + l - (7 * m) + 114) % 31) + 1

    Date.new(year, month, day)
  end

  def carnaval
    easter_sunday - 47
  end

  def good_friday
    easter_sunday - 2
  end

  def corpus_christi
    easter_sunday + 60
  end
end

🔍 Funcionalidades:

  • Identifica feriados fixos e móveis
  • Calcula Páscoa usando o algoritmo de Gauss
  • Determina feriados baseados na Páscoa (Carnaval, Sexta-feira Santa, Corpus Christi)

3️⃣ Variáveis de Ambiente

Configure as variáveis de ambiente necessárias para o funcionamento da Lambda.

🎯 Passos

  1. Na página da função Lambda, acesse: Configuration → Environment variables
  2. Clique em Edit e adicione as seguintes variáveis:
🔑 Lista de Variáveis
Variável Exemplo Descrição
BASTION_ID i-0a1b2c3d4e5f6g7h8 ID da instância EC2 Bastion
DEV_APP_ID i-9j8h7g6f5e4d3c2b1 ID da instância EC2 de Aplicação
RDS_INSTANCE_ID dev-db Identificador da instância RDS
REGION us-east-1 Região AWS das instâncias
SLACK_WEBHOOK_URL https://hooks.slack.com/services/... Webhook do Slack para notificações
START_FUNCTION_URL https://...lambda-url... URL pública da função de iniciar
STOP_FUNCTION_URL https://...lambda-url... URL pública da função de parar

4️⃣ Configurar Trigger com EventBridge

Agende a execução automática da função Lambda usando EventBridge.

🎯 Passos

  1. Na página da função Lambda, clique em Add trigger
  2. Selecione EventBridge (CloudWatch Events)
  3. Escolha Create a new rule
  4. Configure:
    • Rule name: start_instances_event
    • Rule type: Schedule expression
    • Schedule expression: cron(00 10 ? * MON-FRI *)
  5. Clique em Add

⏰ Expressão Cron

cron(00 10 ? * MON-FRI *)

📅 Agendamento:

  • Executa às 07:00h (GMT-3) / 10:00h (UTC)
  • Apenas em dias úteis (segunda a sexta-feira)
  • Ignora feriados automaticamente via verificação no código

✅ Resultado

Após a configuração completa, a função Lambda irá:

  • ✅ Iniciar instâncias EC2 e RDS automaticamente em horários programados
  • ✅ Verificar feriados brasileiros antes de executar
  • ✅ Validar o estado das instâncias antes de iniciar
  • ✅ Enviar notificações para o Slack com status da operação
  • ✅ Fornecer botões interativos para controle manual via Slack

@ValterAndrei
Copy link
Author

ValterAndrei commented Oct 23, 2025

⏹️ Lambda para Parar Instâncias

📝 Configuração da Função Lambda

1️⃣ Criar Função Lambda

Configure a função Lambda responsável por desligar as instâncias EC2 e RDS automaticamente.

🎯 Passos

  1. Navegue até: Lambda → Create function
  2. Preencha os campos:
    • Function name: stop_instances
    • Runtime: Ruby 3.4
  3. Em Permissions, selecione a role: start-stop-instance-role
  4. Clique em Create function

2️⃣ Estrutura de Arquivos

A função Lambda contém um único arquivo Ruby responsável por toda a lógica de desligamento.

📄 lambda_function.rb - Handler Principal

Arquivo principal que gerencia o fluxo de desligamento das instâncias.

💻 Ver código completo
require 'aws-sdk-ec2'
require 'aws-sdk-rds'

def lambda_handler(event:, context:)
  logger = Logger.new($stdout)
  logger.info(event)

  # Environment variables
  bastion_id      = ENV.fetch('BASTION_ID')
  dev_app_id      = ENV.fetch('DEV_APP_ID')
  rds_instance_id = ENV.fetch('RDS_INSTANCE_ID')
  region          = ENV.fetch('REGION')

  # Create AWS Clients
  ec2_client = Aws::EC2::Client.new(region:)
  rds_client = Aws::RDS::Client.new(region:)

  # Check if EC2 instances are in a state available to stop
  ec2_response = ec2_client.describe_instances(instance_ids: [bastion_id, dev_app_id])
  ec2_instances = ec2_response.reservations.flat_map(&:instances)

  ec2_instances.each do |instance|
    ec2_instance_status = instance.state.name

    if ec2_instance_status != 'running'
      return {
        status: 422,
        message: "A instância EC2 #{instance.instance_id} não está em um estado disponível para ser desligada.",
        state: ec2_instance_status
      }
    end
  end

  # Check if RDS instance is in a state available to stop
  rds_response = rds_client.describe_db_instances(db_instance_identifier: rds_instance_id)
  rds_instance = rds_response.db_instances.first
  rds_instance_status = rds_instance.db_instance_status

  if rds_instance_status != 'available'
    return {
      status: 422,
      message: "A instância RDS #{rds_instance_id} não está em um estado disponível para ser desligada.",
      state: rds_instance_status
    }
  end

  # Stop instances
  ec2_client.stop_instances(instance_ids: [bastion_id, dev_app_id])
  rds_client.stop_db_instance(db_instance_identifier: rds_instance_id)

  # Create Slack message
  send_slack_message_for_success

  # Return success message from Lambda
  {
    status: 200,
    message: 'Instâncias desligadas com sucesso.'
  }
end

def send_slack_message_for_success
  message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: ':black_circle: Instâncias desligadas'
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '_Clique no botão para *ligar* as instâncias._'
        },
        accessory: {
          type: 'button',
          text: {
            type: 'plain_text',
            text: 'Ligar instâncias'
          },
          url: ENV.fetch('START_FUNCTION_URL')
        }
      }
    ]
  }

  uri = URI(ENV.fetch('SLACK_WEBHOOK_URL'))
  Net::HTTP.post(uri, message.to_json, 'Content-Type' => 'application/json')
end

🔍 Funcionalidades:

  • Valida estado das instâncias EC2 e RDS antes de desligar
  • Garante que apenas instâncias em execução sejam desligadas
  • Envia notificações para o Slack com status da operação
  • Inclui botão interativo para ligar novamente as instâncias

3️⃣ Variáveis de Ambiente

Configure as variáveis de ambiente necessárias para o funcionamento da Lambda.

🎯 Passos

  1. Na página da função Lambda, acesse: Configuration → Environment variables
  2. Clique em Edit e adicione as seguintes variáveis:
🔑 Lista de Variáveis
Variável Exemplo Descrição
BASTION_ID i-0a1b2c3d4e5f6g7h8 ID da instância EC2 Bastion
DEV_APP_ID i-9j8h7g6f5e4d3c2b1 ID da instância EC2 de Aplicação
RDS_INSTANCE_ID dev-db Identificador da instância RDS
REGION us-east-1 Região AWS das instâncias
SLACK_WEBHOOK_URL https://hooks.slack.com/services/... Webhook do Slack para notificações
START_FUNCTION_URL https://...lambda-url... URL pública da função de iniciar

4️⃣ Configurar Trigger com EventBridge

Agende a execução automática da função Lambda usando EventBridge.

🎯 Passos

  1. Na página da função Lambda, clique em Add trigger
  2. Selecione EventBridge (CloudWatch Events)
  3. Escolha Create a new rule
  4. Configure:
    • Rule name: stop_instances_event
    • Rule type: Schedule expression
    • Schedule expression: cron(00 22 ? * MON-FRI *)
  5. Clique em Add

⏰ Expressão Cron

cron(00 22 ? * MON-FRI *)

📅 Agendamento:

  • Executa às 19:00h (GMT-3) / 22:00h (UTC)
  • Apenas em dias úteis (segunda a sexta-feira)
  • Desliga automaticamente ao final do expediente

✅ Resultado

Após a configuração completa, a função Lambda irá:

  • ✅ Desligar instâncias EC2 e RDS automaticamente ao final do expediente
  • ✅ Validar o estado das instâncias antes de desligar
  • ✅ Enviar notificações para o Slack com status da operação
  • ✅ Fornecer botão interativo para ligar novamente as instâncias via Slack
  • ✅ Otimizar custos ao desligar recursos fora do horário de trabalho

🎯 Automação Completa

Com as duas funções Lambda configuradas (start_instances e stop_instances), você agora possui um sistema completo de automação que:

  • 🌅 Liga as instâncias automaticamente pela manhã (07:00h)
  • 🌙 Desliga as instâncias automaticamente à noite (19:00h)
  • 🎉 Respeita feriados brasileiros (apenas na função de iniciar)
  • 💬 Notifica o time via Slack com controles interativos
  • 💰 Reduz custos operacionais significativamente

📊 Monitoramento

Para acompanhar a execução das funções Lambda:

  1. Acesse CloudWatch → Log groups
  2. Localize os grupos: /aws/lambda/start_instances e /aws/lambda/stop_instances
  3. Visualize logs de execução, erros e métricas de performance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment