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