O blog da AWS

Automatizando a atualização das AMIs no seu template de execução utilizado pelo autoscaling e integrando ao seu pipeline CI/CD

Por Vanessa Rodrigues Fernandes, Arquiteta de Soluções na AWS;

e Renata Rona Garib, Arquiteta de Soluções na AWS.

Automação e escalabilidade são termos praticamente complementares na era da tecnologia. Automação é o uso de tecnologia para executar tarefas com o mínimo de assistência humana possível. Escalabilidade é a capacidade que um sistema possui de crescer atendendo às demandas sem perder a qualidade dos serviços. Essas práticas combinadas permitem que as empresas forneçam novas funcionalidades aos clientes em uma velocidade mais alta.

Neste blog mostraremos uma forma de automatizar a geração de uma nova AMI usada pelo auto scaling de EC2, utilizando o pipeline de CICD, na busca de eliminar a interação humana a cada deploy. Lembrando que este blog é somente um exemplo o os códigos deverão ser adaptados para seus workloads, bem como suas variáveis de ambiente.

Funcionamento

O AWS Auto Scaling monitora os aplicativos, reduz e expande automaticamente a capacidade para manter um desempenho constante e previsível pelo menor custo possível. Para isto, o serviço utiliza  AMI (Imagens de máquina da Amazon), que são instâncias EC2 modelo que possuem a última versão da aplicação gerada por você.  Mas em caso de atualização dos serviços que estão hospedados nesta EC2, como gerar uma nova imagem e informar isto para o Template de Execucão do Autoscaling utilizar a última versão de forma automatizada?

Uma maneira de resolver esse problema é construir um pipeline de CI/CD junto com uma função AWS Lambda para atualizar o Launch Template usado pelo Autoscaling. Mais detalhes da solução e passo a passo pode ser visto no parágrafo Arquitetura.

Serviços

Nesta seção, discutiremos os vários serviços da AWS e de terceiros usados nesta solução.

  • AWS CodeCommit – É um serviço totalmente gerenciado de controle de código-fonte que hospeda repositórios seguros baseados em Git .
  • AWS CodeDeploy – É um serviço totalmente gerenciado de implantação que automatiza implantações de software em diversos serviços de computação como Amazon EC2, AWS Fargate, AWS Lambda e servidores locais.
  • AWS CodePipeline – É um serviço gerenciado de entrega contínua que ajuda a automatizar pipelines de liberação para oferecer atualizações rápidas e confiáveis de aplicativos e infraestrutura.
  • AWS Lambda – É um serviço de computação sem servidor que permite executar código sem provisionar ou gerenciar servidores, pagando apenas pelo tempo de computação que consome.
  • Amazon EC2 Autoscaling – É um serviço que ajuda a manter a disponibilidade das aplicações e permite adicionar ou remover instâncias do EC2 automaticamente de acordo com as condições que você definir.
  • Amazon Elastic Load Balancing (ELB) – É um serviço que distribui automaticamente o tráfego de aplicações de entrada entre vários destinos e dispositivos virtuais em uma ou mais Zonas de disponibilidade (AZs). O ELB oferece suporte a três tipos de balanceadores de carga, Application Load Balancer, Gateway Load Balancer e Network Load Balancer. É possível selecionar o balanceador de carga apropriado de acordo com as necessidades da aplicação.
  • Amazon Linux AMI -É uma imagem compatível do Linux, fornecida e mantida pela Amazon Web Services para uso no Amazon Elastic Compute Cloud (Amazon EC2)
  • AWS Identity and Access Management (IAM)– É um serviço de gerenciamento e segurança a identidades e acesso aos produtos e recursos da AWS.

Arquitetura

Para este artigo, estamos usando uma aplicação em PHP, com servidores Amazon Linux e Apache. Na figura abaixo é possível visualizar o pipeline do processo de atualização.

Figura 1: Pipeline do processo de atualização das AMIs usadas pelo AutoScaling

  1. Pull Request: O processo começa com o envio do código fonte para o AWS CodeComit no repositório onde está o código da aplicação.
  2. Start Pipeline: Somente após o commit do código, o AWS CodePipeline inicia o processo de deploy (como nossa aplicação não possui nenhuma compilação, não será necessário usar o AWS CodeBuild).
  3. Deploy Stage: É iniciado o processo de atualização das EC2, onde rodam vários scripts de parada de serviços das máquinas, verificação de pacotes, reinicialização de serviços, entre outros.
  4. Update E2: É o estágio onde o código fonte nas máquinas EC2 é atualizado e os serviços reestabelecidos.
  5. Automation Stage: É a fase onde a função Lambda é invocada.
  6. Create a new AMI from EC2: É a etapa onde a função lambda gera uma nova AMI a partir da EC2 recém atualizada.
  7. Update Lauch Template: É a fase final, onde a função Lamda atualiza o ID da nova AMI no Lauch Template utilizado pelo AutoScaling das EC2.

Detalhes da Implementação

Pré-requisitos

Para criarmos o pipeline de CI/CD, é necessário:

Implantando a Solução

Acesse o repositório público e copie para seu repositório.

git clone https://github.com/aws-samples/auto-create-ami-for-asg.git

IMPORTANTE: Nossa recomendação é que baixe o template, revise os recursos que serão criados, o nível de permissão necessária e faça testes antes de implantá-lo em produção.

Etapas

Etapa 1: Instale o agente do AWS Code Deploy na sua EC2

Para isto siga esta documentação.

Etapa 2: Adicionar e configurar os arquivos abaixo no seu repositório GIT

Neste tutorial estamos utilizando o AWS CodeCommit como repositório, mas o AWS CodePipeline permite adicinar também as seguintes origens:

  • Amazon ECR
  • Amazon S3
  • Bitbucket
  • GitHub (versão 1)
  • GitHub (versão 2)
  • GitHub Enterprise Server

Adicione o arquivo appspec.yml na sua pasta raiz pois será usado pelo AWS CodeDeploy, sugerimos criar também uma pasta scripts para adicionar scripts de instalação, parada e inicialização de serviços.

Para mais informações acesse Adicionar um arquivo de especificação de aplicativo a uma revisão do CodeDeploy

O ciclo de vida ficará assim:
ApplicationStop → BeforeInstall → AfterInstall → ApplicationStart

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html
file_exists_behavior: OVERWRITE
hooks:
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 30
      runas: root
  BeforeInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 30
      runas: root

Exemplo de conteúdo dos scripts:

stop_server.sh

#!/bin/bash
sudo service httpd stop

install_dependencies.sh

#!/bin/bash
sudo yum install httpd
sudo systemctl enable httpd.service
sudo chown -R $USER:$USER /var/www
sudo yum install php

start_server.sh

#!/bin/bash
sudo service httpd start

Etapa 2: Criar as funções IAM necessárias

2.1) Criar política para EC2:

Acesse IAM, em gerenciamento de acesso selecione Funções, clique em Criar Função. Em Tipo de entidade confiável selecione Serviços da AWS, e em Casos de uso selecione EC2 e clique em próximo.

Em Adicionar permissões clique em Criar política.

Selecione a aba JSON e cole o código abaixo:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": "arn:aws:s3:::codepipeline*/*"
        }
    ]
}

Em próximos o uso de tags é opcional e clique em Revisar. Insira um nome para sua política e clique em Criar Política.

Volte à tela de Adicionar Permissões e selecione a política recém criada (Obs: talvez seja necessário atualizar a página) e clique em próximo, insira um nome para sua função e clique em Criar função.

Clique na sua função recém criada:

Copie o ARN do perfil da instância, será necessário para a criação da Etapa 4 na criação da função Lambda.

2.2) Criar a função do IAM para a função lambda.

Acesse IAM, em gerenciamento de acesso selecione Funções, clique em Criar Função. Em Tipo de entidade confiável selecione Serviços da AWS, e em Casos de uso selecione Lambda e clique em próximo.

Em Adicionar permissões clique em Criar política.

Selecione a aba JSON e cole o código abaixo:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "autoscaling:DescribeAutoScalingGroups",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateImage",
                "ec2:CreateLaunchTemplateVersion"
            ],
            "Resource": [
                "arn:aws:ec2:*:<ID da sua Conta>:instance/*",
                "arn:aws:ec2:*:<ID da sua Conta>:launch-template/<ID do seu Modelo de Execução>",
                "arn:aws:ec2:*::image/*"
            ]
        }
    ]
}

Mude <ID DA SUA CONTA> pelo ID da sua conta AWS sem os <>.

Mude <ID DO SEU MODELO DE EXECUÇÃO> pelo ID do modelo de execução utilizado pelo seu grupo de Auto Scaling, sem os <>.

Em próximo, o uso de tags é opcional, clique em Revisar. Insira um nome para sua política e clique em Criar Política.

Volte a tela de Adicionar Permissões e selecione a política recém criada (Obs: talvez seja necessário atualizar a página) e clique em próximo, insira um nome para sua função e clique em Criar função.

2.3) Siga os mesmos passos da sessão anterior (2.2) porém com outra política para a função da Lambda, para enviar uma mensagem de sucesso ao seu Pipeline, adicione esta política na role anterior (2.2).

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:*"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Action": [
                "codepipeline:PutJobSuccessResult",
                "codepipeline:PutJobFailureResult"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Sua função (role) lambda do IAM ficará com 2 políticas:

2.4) Crie uma função IAM para o CodeDeploy conforme figura abaixo:

A política ASWCodeDeployRole é automaticamente selecionada, clique em Próximo e insira um nome para sua função.

Após criada, clique na sua política e copie seu ARN, ele será usado no passo a seguir.

Etapa 3: Criar Aplicativo do CodeDeploy.

3.1) Abra o CodeDeploy na console da AWS e selecione Aplicativos, clique em Criar Aplicativos:

Escolha um nome para seu aplicativo.

Em plataforma de computação escolha: “EC2/On-premises”.

Selecione o aplicativo criado.

Clique em Criar grupo de implantação.

Escolha um nome para o grupo de implantação.

Em função de serviço, selecione a função criada no passo 2.4.

Escolha o tipo de implantação: “No local”.

Marque Grupos de Auto Scaling e selecione o grupo de Auto Scaling que será implantado o novo código.

Selecione a configuração de implantação como “CodeDeployDefault.AllAtOnce”, para que o deploy da aplicação seja feito em todas as instâncias do grupo de Auto Scaling ao mesmo tempo.

Selecione o Load Balancer que balanceia o tráfego ao seu grupo de Auto Scaling.

Etapa 4: Criar a função lambda.

Acesse o menu AWS Lambda e clique em Criar função.

Selecione Criar do zero.

Insira um nome para a função.

Em tempo de execução selecione Python 3.9.

Em Arquitetura marque x86_64.

Em Alterar a função em execução padrão, selecione Usar uma função existente.

Escolha a função IAM criada anteriormente no passo 2.2.

Clique em Criar função.

Na aba Configuração, vá em Configuração Geral e clique em Editar.

Aumente o Tempo limite para 5 segundos, para que nossa função tenha tempo o suficiente para executar, então clique em Salvar.

Continue na aba Configuração, e clique em Variáveis de ambiente e depois em Editar.

Adicione todas as variáveis que vamos usar na nossa função:

Nome Valor
Auto_Scailing_Group_Name Nome do grupo de Auto Scaling que irá ser atualizado.
EC2_Role_ARN ARN do perfil da instância da Função IAM para as EC2 s que foi criado no Passo 1
EC2_Security_Group_ID ID do grupo de segurança usado pelas EC2 que compoem o grupo de Auto Scaling
Launch_Template_Name Nome do Modelo de Excecução usado pelo grupo de Auto Scaling
Private_Key_Name Nome da Private Key que será usada para logar nas instâncias

Tabela 1: Variáveis e valores que devem ser declarados na função lambda.

É importante que o nome das variáveis seja igual aos mostrados na tabela 1, os valores de cada uma devem ser de acordo com o ambiente onde esse tutorial será implantado.

Na aba Código insira o código abaixo, em seguida clique em Deploy.

import os
import boto3
import datetime

start_date = str(datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
autoScalingGroupName = os.environ['Auto_Scaling_Group_Name']
launchTemplateName = os.environ['Launch_Template_Name']
ec2RoleARN = os.environ['EC2_Role_ARN']
keyName = os.environ['Private_Key_Name']
ec2SecurityGroupID = os.environ['EC2_Security_Group_ID']

def lambda_handler(event, context):
    
    try:
        client = boto3.client('autoscaling')
        autoscaling = client.describe_auto_scaling_groups(
            AutoScalingGroupNames=[
            autoScalingGroupName
            ]
        )

        instanceID = autoscaling['AutoScalingGroups'][0]['Instances'][0]['InstanceId']
        client = boto3.client('ec2')
        name= "Lambda for " + autoScalingGroupName + " from "+ start_date
        description= "AMI for " + autoScalingGroupName + " created by lambda"

        image = client.create_image(
            Description=description,
            DryRun=False,
            InstanceId=instanceID,
            Name=name)

        launch_template= client.create_launch_template_version(
            DryRun=False,
            LaunchTemplateName = launchTemplateName,
            VersionDescription = name,
            LaunchTemplateData={
                'IamInstanceProfile': {
                    'Arn': ec2RoleARN
                },
                'ImageId': image['ImageId'],
                'KeyName': keyName,
                'SecurityGroupIds': [
                    ec2SecurityGroupID,
                ]
            }
        )
        
        job_id = event["CodePipeline.job"]["id"]
        pipeline = boto3.client('codepipeline')
        response = pipeline.put_job_success_result(jobId=job_id)
        
        return True
        
    except Exception as e:
        print(e)
        
    
        job_id = event["CodePipeline.job"]["id"]
        pipeline = boto3.client('codepipeline')
        response = pipeline.put_job_failure_result(
            jobId=job_id,
            failureDetails={
                'type': 'JobFailed',
                'message': e
            }
        )

            
        return False

Etapa 5: Altere ou adicione a política EC2 criada na etapa 2.1 à função já existente da sua instância.

Para visualizar ou alterar a política da instância, acesse EC2, Instâncias, selecione sua instância, clique em Ações > Segurança > Modificar função do IAM.

É possível alterar a função atual para a função criada no passo 2.1, ou incluir a política criada no passo 2.1 à função existente na sua Instância, adicionando-a através do IAM.

Etapa 6: Criar seu pipeline no AWS CodePipeline.

Acesse a console da AWS, selecione AWS CodePipeline e clique em Criar Pipeline.

Insira um nome para seu pipeline, em nova Função de serviço selecione Nova função de serviço, marque a opção Permitir que o AWS CodePipeline crie uma função de serviço, para que ela seja usada com esse novo pipeline e clique em Próximo.

Adicione a origem, o repositório e a ramificação. Para as demais opções, mantenha as selecionadas por padrão.

Em Adicionar etapa de compilação, caso necessite compilar sua aplicação usando o AWS CodeBuild ou Jenkins, é nessa etapa que os mesmos seriam adicionados, porém neste tutorial ignoraremos esta etapa de compilação clicando no botão conforme figura abaixo:

Em Adicionar etapa de implantação, selecione AWS CodeDeploy em Provedor de implantação.

Em região selecione a região onde seu pipeline será criado.

Em Nome do aplicativo selecione o aplicativo criado na etapa 3, bem como seu grupo de implantação, clique em próximo.

Em Revisão clique em Criar Pipeline. Imediatamente seu Pipeline será iniciado.

Vamos adicionar mais etapas no seu Pipeline, agora chamando a função Lambda que irá atualizar o ID do seu template de execução com a versão recém criada da sua AMI. Para isso clique em Editar.

Abaixo da última etapa clique em Adicionar Etapa, insira um nome e clique em Adicionar Etapa.

Na etapa criada, clique em Adicionar grupo de ações.

Insira um nome para sua ação, em provedor de ação selecione AWS Lambda, em artefato de entrada selecione SourceArctifact em nome da função selecione a função criada na etapa 4.

Seu pipeline ficará com as três etapas, clique em Salvar.

Após rodar o Pipeline teremos a nova AMI criada pela função Lambda.

Para validar corretamente se sua função foi executada conforme o esperado, acesse seu Modelo de Execução e verifique se a ultima versão disponível consta com a Função IAM da Lambda criada na Etapa 4.

A partir disso, em caso de aumento de capacidade, seu autoscaling group utilizará a nova AMI com o sistema mais recente.

Conclusão

Neste blogpost mostramos como automatizar a criação de uma nova AMI após um processo de atualização do código de alguma aplicação hospedada em EC2 e também como atualizar através de uma função Lambda o ID dessa nova AMI no Modelo de Execução utilizado pelo grupo do Autoscaling, a fim de manter sempre atualizada sua aplicação via pipeline de CI/CD com a implementação mais recente, diminuindo assim a necessidade de interação humana.


Sobre as autoras

Vanessa Rodrigues Fernandes é Arquiteta de soluções do setor público na AWS. Com formação técnica em Redes de Computadores pela Universidade Federal do Rio Grande do Sul, graduação em Segurança da Informação pela Universidade do Vale do Rio dos Sinos (RS) e Pós Graduação em Gestão Estratégica em TI pela PUCRS. Com mais de 18 anos de experiência na área de infraestrutura, redes, segurança da informação, trabalhou em diferentes nichos de mercado, ajudando empresas a suportar e construir soluções tecnológicas e estratégicas para seus negócios. É apaixonada por tecnologia e segurança da informação.

 
 
 

Renata Garib é Solutions Architect Intern no setor público da AWS. Ela trabalha apoiando clientes e os ajudando com sua jornada para a cloud.