Use Python Lambdas para executar remotamente comandos shell em instâncias do EC2
Recentemente, trabalhei em um arquivo Terraform para Kubernetes e deparei com os limites do Terraform ao tentar atualizar um arquivo com os endereços IP dos nós no cluster. Essa etapa foi difícil de resolver com o Terraform porque esses endereços são criados somente depois que você cria os recursos e não há como inseri-los no script de inicialização no modelo do Terraform com antecedência.
Ao pesquisar soluções, vi o próprio Mitchell Hashimoto dizer explicitamente que o Terraform não é para gerenciar o tempo de execução:
Então é isso. Você deve usar Consul ou algo como Ansible ao lidar com alterações em tempo de execução.
….mas então tive uma ideia terrível: como posso fazer um Lambda fazer isso? Depois de pesquisar e ruminar, encontrei uma maneira de alterar os nós já em execução usando a biblioteca Python da AWS, boto3, para enviar comandos shell. E porque eu amo Lambdas, gostaria de compartilhar minha pesquisa sobre isso com você hoje.
Para ser claro: você ainda deve usar ferramentas como Ansible ou Consul para realizar tarefas de tempo de execução, como bootstrapping. O que este projeto faz mostra a imensa flexibilidade do Python Lambdas e o poder que eles fornecem ao configurar a infraestrutura. Se você realmente quiser, pode usar Lambdas para quase tudo.
Visão geral
- Parte 1: Passo a passo do script Python
- Parte 2: Implantando o projeto com o Terraform
- Parte 3: Compreendendo as permissões necessárias para o SSM
- Parte 4: Testando o script no Lambda
- Cloud 9 IDE com credenciais de administrador da AWS — criaremos políticas de IAM, trabalharemos com o Systems Manager e usaremos o Python para interagir com a AWS via boto3. O Cloud 9 também tem o Terraform pré-instalado, facilitando este passo a passo.
- Familiaridade com Python e Boto3 — Estou operando com a suposição de que não preciso explicar os loops, listas ou bibliotecas neste passo a passo.
- Entendimento do nível iniciante do Terraform — fornecerei os modelos e você deve saber como executá-los.
O objetivo deste script é pesquisar nossas instâncias do EC2 e executar um shell script naquelas que possuem uma tag (que determinaremos no script) e estão no estado “running”.
Vamos examinar isso primeiro no IDE. Sinta-se à vontade para acessar python3o terminal Cloud 9 e acompanhar no shell interativo.
Bem no topo do script, definiremos o script a ser executado e a tag:
#Import boto3 library
import boto3
#Define the contents of your shell script
script = """
echo "Hello World!" > /home/ec2-user/helloworld.txt
pwd >> /home/ec2-user/helloworld.txt
"""
#Define the tag possessed by the EC2 instances that we want to execute the script on
tag='Test'
#Define ec2 and ssm clients
ec2_client = boto3.client("ec2", region_name='us-east-1')
ssm_client = boto3.client('ssm')
#Gather of instances with tag defined earlier
filtered_instances = ec2_client.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [tag]}])
#Reservations in the filtered_instances
reservations = filtered_instances['Reservations']
Note: If you don’t have any instances with the matching tag, ‘Instances’ may be empty.
As instâncias estão dentro da chave do dicionário chamada “Reservas”. Uma “reserva” está associada a um determinado tipo de instância e zona de disponibilidade e pode abranger uma ou mais instâncias. Ele é criado automaticamente quando as Instâncias são lançadas. Acedemos a essa lista de “Reservas” através da reservationsvariável que definimos.
Em terceiro lugar, vamos iterar reservationse extrair as instâncias 'em execução':
#Create a an empty list for instances to execute the shell script within
exec_list=[]
#Iterate through all the instances within the collected resaervations
#Append 'running' instances to exec list, ignoring 'stopped' and 'terminated' ones'
for reservation in reservations:
for instance in reservation['Instances']:
print(instance['InstanceId'], " is ", instance['State']['Name'])
if instance['State']['Name'] == 'running':
exec_list.append(instance['InstanceId'])
#divider between reservations
print("**************")
Para cada instância, se o estado da instância for 'running', o ID da instância será anexado à exec_listlista.
Em quarto lugar, vamos usar o boto3 ssm_clientdefinido anteriormente para executar o script de shell nas instâncias do EC2 no exec_list:
#Run shell script
response = ssm_client.send_command(
DocumentName ='AWS-RunShellScript',
Parameters = {'commands': [script]},
InstanceIds = exec_list
)
#See the command run on the target instance Ids
print(response['Command']['Parameters']['commands'])
Se eu fizer SSH na instância que já configurei com as permissões corretas, podemos ver o resultado:
Este é o resultado esperado do que colocamos na scriptvariável no topo deste script Python:
script = """
echo "Hello World!" > /home/ec2-user/helloworld.txt
pwd >> /home/ec2-user/helloworld.txt
"""
Parte 2: Implantando o projeto com o Terraform
Entrei em detalhes sobre como configurar um Lambda no Terraform em meu artigo Create a Python Lambda to Save On Your Cloud Bill with Terraform . Eu recomendo que você dê uma olhada se quiser se aprofundar na compreensão de como configurar o Lambda no Terraform. Presumo que você já tenha configurado um modelo ou dois antes para este post.
Vamos clonar meu repositório e implantá-lo usando nosso Cloud 9 CLI:
#Clone and Enter the Repo
git clone https://github.com/nickcmiller/lambda-shell-execution.git
cd lambda-shell-execution
#Initialize Terraform
terraform init
#Apply
terraform apply --auto-approve
Sinta-se à vontade para bisbilhotar se quiser ver
Caso você queira acessar a instância do EC2 via SSH, criei o script de conexão que você pode utilizar na forma de um Output:
O test_key_pair.pemestá no diretório raiz do arquivo Terraform (você pode vê-lo na parte inferior da captura de tela do diretório). Como sempre, não se esqueça chmod 400antes de usá-lo.
Agora vamos discutir como configurar permissões que permitem que o Lambda acesse instâncias.
Parte 3: Compreendendo as permissões necessárias para o SSM
Para permitir que nosso Lambda implante um script de shell no EC2, precisamos habilitar o SSM para registrar os agentes do EC2. Para fazer isso, criei uma função ec2-role-for-SSMque você verá se clicar em sua Testinstância.
Esta função utiliza uma das políticas IAM gerenciadas da Amazon ( AmazonEC2RoleforSSM ):
Esta política fornece acesso total a mensagens SSM e acesso de lista, leitura e gravação ao gerenciador de sistemas:
A Política de Papel, encontrada em Relacionamentos de Confiança , permite que ela seja assumida pelos serviços do EC2:
Se entrarmos na instância dos dados do usuário…
Veremos que estamos garantindo que a instância do EC2 tenha o agente instalado, iniciado e habilitado para futuras reinicializações:
Agora, vá até o console do Lambda e selecione nosso Lambda execute-shell-script :
Como você verá em Configuração, ele também recebeu permissões SSM:
Clique no nome da função e você verá que esta é uma política personalizada:
Suas permissões permitem acesso de lista e gravação ao EC2, bem como acesso de lista, leitura e gravação ao SSM :
De volta à função em relações de confiança , podemos ver que esta função é designada para o Lambda:
Se você não tiver cada uma dessas peças configuradas corretamente, provavelmente receberá um erro dizendo algo parecido com An error occurred (InvalidInstanceId) when calling the SendCommand operation: Instances [[i-0xxxxxxxxxxxxxx]] not in a valid state for account 2xxxxxxxxxx, o que não é um erro construtivo.
Se você ficar preso, também descobri que é útil acessar suas instâncias do EC2 e, depois de verificar se o SSM está instalado e em execução, verifico os logs com este comando sudo cat /var/log/amazon/ssm/amazon-ssm-agent.log. Se seu agente SSM estiver hibernando, certifique-se de que suas funções e políticas estejam configuradas corretamente.
Parte 4: Testando o script no Lambda
Agora vamos testar isso!
Para o meu evento de teste , deixei o JSON genérico, pois não estamos utilizando os dados do evento para este script:
Ao executá-lo, vejo que minha instância de Teste está rodando e o comando foi enviado via SSM:
Se fizermos SSH na instância de teste, veremos que foi bem-sucedido:
Essa é uma boa demonstração. Mas é apenas um arquivo de texto.
E se mudássemos a scriptvariável para algo assim?
script = """
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install nginx1 -y
sudo systemctl enable nginx
sudo systemctl start nginx
"""
Agora isso é legal!
Embrulhar
Quando combinamos boto3 e Lambdas, podemos fazer com que nossa infraestrutura AWS faça quase tudo o que quisermos com pouca sobrecarga. E como vimos, quando usamos código para implementar nossa arquitetura, torna-se trivial fazer alterações, mesmo em tempo de execução.
Espero que este post tenha inspirado você a pensar mais sobre como usar Lambdas de forma criativa em seu ambiente.
Como sempre, por favor, deixe qualquer dúvida ou opinião nos comentários.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)