AWS Partner Network (APN) Blog – Brasil

Tutorial: Agendando seus backups no Lambda

Por Angelo Carvalho, AWS Partner Solutions Architect


Em 2014, a AWS lançou o Lambda, um serviço de computação com o qual o cliente pode executar funções em resposta a eventos sem precisar se preocupar com o provisionamento de infraestrutura. O Lambda ajudou a viabilizar a arquitetura de aplicações “serverless”, ou seja, aplicações que utilizam somente serviços gerenciados da AWS (como DynamoDB, S3, SQS, etc…) em sua composição.

Inicialmente somente o node.js era suportado, depois java, e no último re:Invent, a AWS anunciou o suporte a Python e também ao agendamento de scripts. Você pode especificar um período fixo (a cada x minutos, horas ou dias) ou mesmo uma expressão cron (0 18 ? * MON-FRI *).

Para comemorar estas duas novidades, vamos demonstrar neste artigo como criar um script Python para fazer um backup completo (imagens) das suas instâncias EC2, e usar a função de agendamento do Lambda para garantir que os backups sejam executados com a frequência correta (geralmente no mínimo uma vez ao dia). Também criaremos um outro script para apagar as imagens antigas tiradas há mais de 7 dias. Se você precisar de um período de retenção maior, basta alterar o script.

Um ponto crítico com relação a este tipo de uso para o Lambda é que o tempo máximo de execução de uma função (timeout) é de 5 minutos. Se a sua função levar mais de 5 minutos para finalizar o seu trabalho, ela será automaticamente encerrada pelo Lambda. Nos meus testes, levou aproximadamente 55 segundos para fazer o backup de 41 instâncias. Se você tem muitas instâncias na sua conta e o seu script levar mais de 5 minutos, pode ser necessário quebrar o script em várias funções, como por exemplo uma para cada região, uma para cada cliente, uma para cada VPC, etc… Fique à vontade para modificar o script conforme a sua necessidade! Em todo caso, vamos configurar um alarme no CloudWatch para nos notificar por e-mail caso nosso backup seja encerrado de forma prematura.

Vamos à configuração:

1. Selecione qualquer região que disponibilize o serviço Lambda.
2. Selecione o Lambda no seu console AWS;
3. Se você ainda não criou nenhuma função Lambda, clique em “Get Started Now”;
4. Na tela “Select Blueprint”, clique no botão “Skip”;
5. Na tela “Configure function”, dê um nome para a sua função (“EC2_Backup”, por exemplo) e selecione “Python 2.7” no campo “Runtime”;
6. Copie e cole a função abaixo no campo “Lambda function code”:

import boto3
from datetime import datetime

def backup():
    date_format = "%d/%m/%Y"
    date_time_format = "%d/%m/%Y_%H_%M_%S"
    created_by_value = "lambda-backup-script"

    start_time = datetime.now()

    # inicializado o ec2 client
    ec2_client = boto3.client('ec2')

    regions = ec2_client.describe_regions()

    for region in regions['Regions']:
        print("Regiao: " + str(region['RegionName']))
        ec2_client = boto3.client('ec2', region_name=region['RegionName'])
        instances = ec2_client.describe_instances()
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                #ignorando instancias com estados invalidos...
                if instance['State']['Name'] == "running" \
                        or instance['State']['Name'] == "stopping" \
                            or instance['State']['Name'] == "stopped":
                    date_time = datetime.now().strftime(date_time_format)
                    date = datetime.now().strftime(date_format)
                    ami_name = "backup_by_lambda__" + instance['InstanceId'] + "__" +  date_time
                    ami_desc = ami_name
                    print "Fazendo backup da instancia: " + \
                        instance['InstanceId'] + " >> " + ami_desc
                    ami_id = ec2_client.create_image(InstanceId=instance['InstanceId'], \
                                                     Name=ami_name, Description=ami_desc, \
                                                     NoReboot=True)
                    ec2_client.create_tags(Resources=[ami_id['ImageId']], \
                                           Tags=[{'Key': 'creation_date','Value': date}, \
                                                 {'Key': 'created_by', \
                                                    'Value': created_by_value}])
    
    end_time = datetime.now()
    took_time = end_time - start_time
    print "Tempo total de execucao: " + str(took_time)    

def lambda_handler(event, context):
    print('Iniciando backup... ')
    backup()

7. Importante: Se você estiver utilizando este script para fazer backup de um banco de dados, é necessário um cuidado adicional para garantir a integridade do mesmo. Para garantir um backup consistente, modifique o valor do parâmetro NoReboot para False na função create_image. Isso forçará um reboot da instância no momento da geração da sua imagem (backup da instância). Se reiniciar a instância não for viável, existem alternativas, como as indicadas na sessão Creating Consistent or “Hot” Backups do seguinte documento: https://media.amazonwebservices.com/AWS_Backup_Recovery.pdf

8. No campo “Role”, selecione “* Basic execution role” e siga o wizard para a criação de uma nova role para o Lambda:

a. No campo IAM Role, selecione “Create a new IAM Role”;
b. Dê um nome para a sua role;
c. Clique em “View Policy Document” e depois em “Edit”;
d. Sobrescreva o conteúdo a seguir no texto da sua policy e clique em “Allow”:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateImage",
                "ec2:DeleteSnapshot",
                "ec2:DeregisterImage",
                "ec2:DescribeImages",
                "ec2:DescribeInstances",
                "ec2:DescribeSnapshots",
                "ec2:CreateTags",
                "ec2:DescribeRegions"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

9. Selecione 128 no campo “Memory”;
10. Coloque 5 min no campo “Timeout”;
11. Clique “Next”, e em seguida “Create function”;
12. Agora teste a sua função clicando no botão “Test”;
13. Use o template “Hello World” e clique em “Save and Test”;
14. Aparecerá na parte superior da tela uma mensagem com um link para o CloudWatch. Clique neste link para ver o log da execução da função no CloudWatch. Procure pela seguinte mensagem em português: “Tempo total de execução: …” Se você conseguir ver esta mensagem, parabéns! Você conseguiu executar a sua função de backup com sucesso!

Configurando o agendamento:

1. Agora vamos fazer o agendamento da função para que ela seja executada diariamente ou com a periodicidade que você desejar. Selecione a sua função no console do Lambda e vá até a aba “Event Sources”. Clique em “Add Event Source”;
2. No campo “Event Source Type”, selecione “Schedule Event”;
3. No campo “Schedule expression”, coloque a sua expressão cron. Sugestão: para fazer os seus backups todos os dias às duas da manhã (horário de São Paulo), configure a expressão com duas horas a mais, pois o agendamento é feito em hora UTC. Logo, em vez de 2, use 4. A expressão fica da seguinte forma: cron(0 4 * * ? *);
4. Clique em Submit para criar o agendamento;
5. É importante criar um alarme para que, caso a função retorne algum erro, você seja notificado. Mas antes disto, vamos criar um tópico para poder receber notificações:

a. Crie um tópico SNS e se subscreva nele. Você vai usar este tópico no próximo passo, quando você deverá configurar um alarme no CloudWatch que lhe avisará caso ocorra algum erro na execução da função;
b. Para criar o tópico SNS, selecione o serviço SNS no console, e clique em “Create Topic”, dê um nome a ele e clique no botão “Create Topic”;
c. Selecione o seu tópico na lista, clique nele, clique em “Actions” e escolha a opção “Subscribe to Topic”, selecione o protocolo “Email” e adicione o seu email, ou o email que precisa ser notificado em caso de erro;
d. Hora de configurar o alarme no CloudWatch: selecione o serviço CloudWatch no console. Clique em “Alarms” e em “Create Alarm”;
e. No primeiro passo (1. Select Metric step), escolha “Lambda Metrics”, e então escolha a métrica de nome “Errors” na função que você acabou de criar. No menu “Average”, troque para “Sum;
f. Clique em “Next” e vá para o segundo passo (2. Define Metric step), e coloque: “Whenever: Errors is >= 1”, e selecione o tópico SNS que você criou para receber as notificações no campo “Select a notification list”;
g. Se quiser testar o alarme, force um erro na sua função para receber um email avisando sobre o erro;

6. Agora repita os mesmos passos para agendar a função para deleção de backups antigos, mas com algumas poucas diferenças:

a. Não é necessário criar uma nova role, use a mesma que você criou para a primeira função;
b. Agende a segunda função para ser executada um pouco mais tarde que a primeira, por exemplo às 2:30 da manhã: cron(30 4 * * ? *);
c. Use o seguinte código para a função deleção, lembrando de trocar a variável “account_id” para o número da sua conta da AWS:

import boto3
from datetime import datetime
from datetime import timedelta

def limpeza():
    date_format = "%d/%m/%Y"
    date_time_format = "%d/%m/%Y_%H_%M_%S"
    created_by_value = "lambda-backup-script"
    retention_days = 7
    account_id = '000000000000'

    start_time = datetime.now()

    # EC2 find instances
    ec2_client = boto3.client('ec2')
    ec2_resource = boto3.resource('ec2')

    regions = ec2_client.describe_regions()

    for region in regions['Regions']:
        print("Regiao: " + str(region['RegionName']))
        ec2_client = boto3.client('ec2', region_name=region['RegionName'])
        ec2_resource = boto3.resource('ec2', region_name=region['RegionName'])
        instances = ec2_client.describe_instances()
    
        images = ec2_client.describe_images(Owners=['self'], \
                    Filters=[{'Name': 'tag:created_by','Values': [created_by_value]}])
    
        creation_date_tag = ''
        for image in images['Images']:
            for tag in image['Tags']:
                if tag['Key'] == 'creation_date':
                    creation_date_tag = tag['Value']
        
                    today = datetime.today()
                    creation_date = datetime.strptime(creation_date_tag, date_format)
                    delta = creation_date - today
                    if (delta*(-1)) > timedelta(days=retention_days):
                        print "Pesquisando AMI: " + image['ImageId']
                        image_to_deregister = ec2_resource.Image(image['ImageId'])
                        print "Desregistrando AMI: " + image['ImageId']
                        image_to_deregister.deregister() 
                        print "Pesquisando Snaps: " + image['ImageId']
                        for snapshot in ec2_resource.snapshots.filter(OwnerIds=[account_id], \
                                Filters=[{'Name': 'description', \
                                    'Values': ['*'+ image['ImageId'] + '*']}]):
                            print "Excluindo Snapshot: " + str(snapshot.id)     
                            snapshot.delete()                                        
    
    end_time = datetime.now()
    took_time = end_time - start_time
    print "Tempo total de execucao: " + str(took_time)

def lambda_handler(event, context):
    print('Iniciando exclusao dos backups antigos... ')
    limpeza()

Pronto! A partir de agora, você já tem um backup diário de todas as suas instâncias EC2. Como próximos passos, você pode criar variações destes scripts e deixá-los ainda melhores. Algumas Ideias:

• Filtre para fazer backup somente das instâncias de produção, se você preferir desta forma. Você pode usar TAGs nas instâncias para fazer este filtro;
• Quebre o seu script em várias funções, caso ele esteja demorando muito para ser executado e esteja se aproximando do tempo de execução máximo do Lambda, que é de 5 minutos;
• Modifique os scripts para fazer o agendamento do start/stop das instâncias do seu ambiente de desenvolvimento, para economizar na sua fatura mensal. Use TAGs nas instâncias para fazer o filtro;
• Melhore os scripts para garantir backups consistentes dos seus bancos de dados (como freeze do sistema de arquivos), usando as dicas deste whitepaper: https://media.amazonwebservices.com/AWS_Backup_Recovery.pdf