Desligamento Automático das Instâncias EC2

finops day
7 min readOct 23, 2023

--

Objetivo

Automatizar o início e a parada das instâncias EC2 em várias contas da AWS com base em horários específicos e tags aplicadas às instâncias.

Pré-Requisitos:

Passos:

1. Clone o Repositório do GitHub:

git clone https://github.com/finopsday/ec2-start-stop-cross-account.git

2. Clone o repositório que contém os scripts.

cd ec2-start-stop-cross-account

3. Deploy usando o CloudFormation:

  • Use os templates CloudFormation stack.yml e stackset.yml para criar as políticas e os papéis necessários na AWS.
  • Você pode fazer isso usando a interface da AWS ou a CLI da AWS. Para a CLI:
aws cloudformation create-stack --stack-name ec2-start-stop --template-body file://stack.yml
  • Definição das politicas do IAM no stack e stackset
Principal:
AWS: "arn:aws:iam::AccountID:root"

Observação:

  • No campo AWS: "arn:aws:iam::AccountID:root", AccountID deve ser substituído pelo ID numérico da conta master AWS à qual você deseja conceder permissões.
  • Certifique-se de que o ID da conta esteja correto para evitar problemas de permissão, garantindo que o recurso certo tenha as permissões necessárias.

4. Configure o Ambiente:

  • Importe o script script.sh para configurar o ambiente. Esse script preparará as dependências necessárias.
  • Em resumo, este script cria um diretório temporário, instala bibliotecas Python especificadas em um arquivo requirements.txt dentro desse diretório e, em seguida, cria um arquivo ZIP contendo essas bibliotecas e seus requisitos. Por fim, ele move o arquivo ZIP gerado para o diretório /tmp e remove o diretório temporário. Isso pode ser útil para preparar um pacote de bibliotecas Python para implantação em algum ambiente.
  • Explicação detalhada do script.
# Navega para o diretório /tmp e cria um diretório chamado "layer".
cd /tmp; mkdir layer; cd layer

# Cria um arquivo chamado requirements.txt no diretório "layer" e escreve as linhas "requests==2.28.1" e "pytz" nele.
touch requirements.txt
echo "requests==2.28.1\npytz" > requirements.txt

# Cria a estrutura de diretórios temp/python dentro do diretório "layer" e navega até esse diretório.
mkdir -p temp/python
cd temp/python

# Instala as bibliotecas listadas no arquivo "requirements.txt" no diretório atual usando pip3 e a opção -t (para especificar o diretório de instalação).
pip3 install -r ../../requirements.txt -t .

# Navega de volta para o diretório "layer".
cd ..

# Cria um arquivo ZIP chamado "requests.zip" que contém todos os arquivos e diretórios no diretório atual.
zip -r9 ../requests.zip .

# Move o arquivo "requests.zip" para o diretório /tmp.
mv ../requests.zip /tmp/

# Remove recursivamente (com o comando "rm -r") o diretório "layer" e seu conteúdo.
rm -r /tmp/layer/

5. Carregar a Layer:

  • Vá ao console AWS Lambda.
  • No painel de navegação, escolha “Layers” e depois “Create layer”.
  • Forneça um nome e uma descrição para a layer.
  • Carregue o arquivo zip requests.zip.
  • Escolha uma runtime compatível. (Nesse caso deve ser Python 3.8.)
  • Clique em “Create” para criar a layer.

6. Criação da Função Lambda no Console AWS:

  • Abra o console AWS Lambda.
  • Clique em “Create function”.
  • Selecione “Author from scratch”.
  • Dê um nome para a sua função e selecione o runtime Python (Python 3.8).
  • Selecione role que criamos utilizando o CloudFormation.
  • Clique em “Create function”.
  • Nas configurações aumente o tempo de timeout de acordo com o tamanho da sua infraestrutura.

7. Anexar a Layer à Função Lambda:

  • No console AWS Lambda, escolha a função que você deseja modificar (no nosso exemplo, vamos anexar na lambda que acabamos de criar).
  • No painel de design, procure por “Layers” .
  • Escolha “Add a layer”.
  • Escolha “Custom layers” e selecione a layer que você criou.
  • Clique em “Add”.
  • Depois, salve a função Lambda.

8. Upload do Código:

  • Na página da sua função, navegue até a seção “Function code”.
  • No campo “Code entry type”, selecione “Upload a .zip file” se você compactou seu código, ou “Upload a file from Amazon S3” se o código estiver armazenado no S3. Se o código for um único arquivo, você também pode optar por “Edit code inline”.
  • Faça o upload do seu código ou arquivo .zip
  • Função Lambda detalhada
import boto3  # Importa o módulo boto3 para interagir com os serviços AWS.

from datetime import datetime # Importa datetime para trabalhar com datas e horários.
from pytz import timezone # Importa timezone para lidar com fusos horários.

def lambda_handler(event, context): # Define a função principal que será executada quando a Lambda for chamada.

target_accounts = ['AccountID', 'AccountID'] # Lista de IDs das contas AWS que serão alvo da execução.

for target_account in target_accounts: # Inicia um loop para processar cada conta alvo.

role_to_assume_arn = f"arn:aws:iam::{target_account}:role/roleStartStop" # ARN da role que criamos.
sts_client = boto3.client('sts') # Cria um cliente para o serviço STS (Security Token Service).
assumed_role = sts_client.assume_role(RoleArn=role_to_assume_arn, RoleSessionName="AssumeRoleSession") # Assume o role na conta alvo.

# Cria uma sessão com as credenciais do role assumido.
assumed_session = boto3.Session(
aws_access_key_id=assumed_role['Credentials']['AccessKeyId'],
aws_secret_access_key=assumed_role['Credentials']['SecretAccessKey'],
aws_session_token=assumed_role['Credentials']['SessionToken']
)

ec2 = assumed_session.resource('ec2') # Usa a sessão para criar um recurso EC2.

# Define os filtros para buscar instâncias que possuem uma tag específica e estão em um estado específico.
filters = [
{'Name': 'tag:ec2-stop-start', 'Values': ['true']},
{'Name': 'instance-state-name', 'Values': ['running', 'stopped']}
]

instances = ec2.instances.filter(Filters=filters) # Busca instâncias que atendem aos critérios de filtro.

if len([instance for instance in instances]) > 0: # Se houver instâncias que atendem aos critérios.

# Imprime a quantidade de instâncias que serão gerenciadas.
print(f"Conta {target_account} possui {len([instance for instance in instances])} instâncias para serem gerenciadas!")

y = datetime.now(timezone('America/Sao_Paulo')) # Obtém a hora atual no fuso horário de São Paulo.
y = y.strftime("%H:%M") # Formata a hora atual.

print("##### Iniciando #####")
print(f"Horário: {y}")

# Define as funções para iniciar e parar instâncias.
def stop_instance(instance):
return ec2.Instance(instance.id).stop()

def start_instance(instance):
return ec2.Instance(instance.id).start()

# Decide se as instâncias devem ser iniciadas ou paradas com base na hora atual.
if y > "19:59" or y < "07:00":
print("Entre 07AM e 19:59PM")
for instance in instances:
tags = instance.tags
# Verifica se uma tag específica está presente antes de iniciar a instância.
if any(tag['Key'] == 'shutdown' and tag['Value'] == 'never' for tag in tags):
print(f"Skipping instance {instance.id} devido à tag shutdown: never")
continue
start_instance(instance) # Inicia a instância.
print(f"Starting instance {instance.id}")
elif y <= "06:00" or y > "20:00":
print("Entre 20PM e 06AM")
for instance in instances:
tags = instance.tags
# Verifica se uma tag específica está presente antes de parar a instância.
if any(tag['Key'] == 'shutdown' and tag['Value'] == 'never' for tag in tags):
print(f"Skipping instance {instance.id} devido à tag shutdown: never")
continue
stop_instance(instance) # Para a instância.
print(f"Stopping instance {instance.id}")
else:
# Imprime uma mensagem se não houver instâncias que atendam aos critérios.
print(f"Conta {target_account} Não Possui Instâncias com a TAG obrigatória ou com STATUS (running ou stopped)")

return "Assumed and performed actions in another account(s)" # Retorna uma mensagem indicando que as ações foram executadas.

9. Crie regras do EventBridge que executem suas funções do Lambda

Abra o console do EventBridge.

Selecione Criar regra.

Insira um nome para sua regra, como “StopEC2Instances”. (Opcional) Em Descrição, insira uma descrição da regra.

Em Tipo de regra, escolha Cronograma e, em seguida, escolha Continuar no Agendador do Amazon EventBridge.

Em Padrão de cronograma, escolha Cronograma recorrente.

Em Padrão de cronograma, procure Ocorrência e escolha Cronograma recorrente.

Em Tipo de cronograma, escolha um tipo de cronograma e conclua as seguintes etapas:

Em Cronograma baseado em taxas, insira um valor de taxa e escolha um intervalo de tempo em minutos, horas ou dias.

-ou-

Para o Cronograma baseado em cron, insira uma expressão que diga ao Lambda quando parar a sua instância. Para obter informações sobre a sintaxe da expressão, consulte Creating an Amazon EventBridge rule that runs on a schedule.

Observação: as expressões cron seguem o formato UTC. Certifique-se de ajustar a expressão de acordo com o seu fuso horário.

Em Selecionar destinos, escolha Função do Lambda na lista suspensa Destino.

Em Função, escolha a função de interrupção das instâncias.

Escolha Pular para a análise e criação e, em seguida, escolha Criar.

Repita as etapas de 1 a 10 para criar uma regra de inicialização das instâncias. Conclua as seguintes etapas:

Insira um nome para sua regra, como “StartEC2Instances”.

(Opcional) Em Descrição, insira uma descrição da regra. Por exemplo, “Inicia instâncias do EC2 todas as manhãs às 7h”.

Start e Stop de instâncias EC2 com Lambda e EventBridge

10. Verificação:

  • Verifique se a função Lambda está sendo executada conforme esperado, verificando os logs e o estado das instâncias EC2.

Notas Importantes:

  • Certifique-se de que você tem as permissões adequadas para executar todos os passos, como clonar o repositório, executar scripts e criar stacks no CloudFormation.
  • Adapte qualquer parte do script ou templates conforme necessário para atender às necessidades e políticas específicas de sua organização.
  • Lembre-se de que a função Lambda irá iniciar ou parar as instâncias EC2 com base nas tags específicas e no horário definido no script. Certifique-se de que isso está configurado conforme as necessidades da sua organização.

Conclusão:

Seguindo este procedimento, você deve ser capaz de configurar e executar o script para gerenciar automaticamente o início e a parada das instâncias EC2 em várias contas da AWS, ajudando a otimizar os custos.

Agradecimentos:

Um agradecimento especial a Antonio de Abreu e Rodrigo Ferradas pela valiosa contribuição e insights compartilhados. Seus conhecimentos têm sido essenciais para guiar a comunidade nas melhores práticas e estratégias.

Esperamos que isso ajude!

--

--