Por Maicon Baum*

Objetivo

Imagine um cenário de backups diários de arquivos importantes para o S3, onde você precisa controlar se um backup foi ou não enviado, mas não tem a liberdade para instalar um agente de monitoração qualquer no servidor em questão, no entanto, por outro lado, não pode permitir que um backup com falha leve dias para ser descoberto. Fica chato entrar no console do S3 todos os dias e olhar se seu backup está correto, não é mesmo?

O objetivo deste post é ensinar um modo bem simples de envio de backup + notificação em caso de falha/sucesso utilizando Python (+ Boto3), AWS Lambda (Node.JS), SNS e S3.

Princípios

Esse tutorial parte do princípio que você já tenha o conhecimento de como:

  • Criar uma conta na AWS.
  • Instalar ou já ter instalado o Boto3 em um servidor Linux.

Caso você ainda não tenha, é necessário criar uma conta na AWS para podermos dar continuidade no tutorial. Dependendo do tamanho do seu backup, tudo que será ensinado neste post não terá custo na AWS no período de 1 ano (para novas contas). Para saber mais sobre o plano FREE da AWS, clique aqui. Cuide o que for utilizar para não ser cobrado.

Conceitos básicos

Amazon Simple Notification Service (SNS)

O SNS é um serviço de notificações por push rápido, flexível e totalmente gerenciado que permite enviar mensagens individuais ou distribuir mensagens para um grande número de destinatários. Utilizaremos o SNS para sermos notificados de um backup recebido com sucesso.

Amazon Simple Storage Service (S3)

O S3 é um armazenamento de objetos, altamente durável (99,999999999%) e com capacidade para escalar até 1 trilhão de objetos em todo o mundo. Sua capacidade de armazenamento é virtualmente ilimitada, sendo restrita apenas ao tamanho de um único objeto (até 5tb), mas sem restrição ńa quantidade de objetos armazenados. Utilizaremos o S3 para armazenar nossos backups.

AWS Lambda

O AWS Lambda permite que você execute códigos sem provisionar ou gerenciar servidores. Basta carregar o código e o Lambda toma conta de tudo o que for necessário para executar e escalar o seu código com alta disponibilidade. Utilizaremos o AWS Lambda para formatarmos o evento vindo do S3 quando um backup é recebido e publicarmos no SNS, notificando o sucesso no recebimento com informações sobre o arquivo.

Amazon Simple Email Service (SES)

O SES é uma plataforma de e-mail fácil de usar e com baixo custo, excelente para inúmeros cenários e extremamente versátil. Utilizaremos a SDK do SES para notificação em caso de falha no envio do backup.

Passo a passo

1 Configuração SNS

Para dar início, vamos primeiramente criar um tópico no SNS e configurar uma inscrição de e-mail no mesmo, para notificar o sucesso do backup.

No painel de serviços da AWS, selecione o SNS. Uma vez no console do SNS, clique em: Topics > Create new topic. Escolha um nome qualquer para o tópico, ex: S3Notify. Após criar o tópico, ele aparecerá na lista de tópicos, clique então sobre o ARN do tópico que você acabou de criar.

Uma vez na tela de detalhes sobre o tópico, clique em: Create subscription. No campo “Protocol”, selecione “Email” e no campo “Endpoint”, coloque o e-mail desejado para receber a notificação de sucesso do backup. Para vários e-mails, repita o processo a partir de “Create subscription”.

Após criar, verá que o Subscription ID ficou como PendingConfirmation. Um e-mail foi enviado para o endereço que você colocou, para confirmação de inscrição neste tópico. Acesse este e-mail e confirme sua inscrição.

Nos detalhes do seu tópico, observe o seu Topic ARN. Guarde-o em algum lugar pois será utilizado ao configurar a Lambda Function.

A configuração do SNS termina aqui, partiremos para a configuração do SES.

 

2 Configuração SES

A configuração do SES é muito simples, pois iremos apenas confirmar um e-mail de remetente e um e-mail para notificação em caso de falha.

No painel de serviços da AWS, selecione o SES. Uma vez no console do SES, clique em: Email Addresses > Verify a New Email Address. Digite o e-mail desejado para ser o remetente da notificação. Repita o processo para configurar o(s) destinatário(s).

A configuração do SES termina aqui, partiremos para a configuração do AWS Lambda.

 

3 Configuração AWS Lambda

Criaremos agora a Lambda Function responsável por formatar o evento vindo do S3 quando um backup é recebido e publicar esta informação no tópico SNS, onde nosso e-mail foi cadastrado no primeiro passo.

No painel de serviços da AWS, selecione Lambda. Uma vez no console Lambda, clique em: Create a Lambda function.

No menu “Select Blueprint”, escolha “Blank Function”;

No menu “Configure triggers”, apenas clique em Next, configuramos a trigger após criar o bucket;

No menu “Configure function”, configure de acordo:

Name: Um nome qualquer para a function, ex: S3Format

Description: Uma descrição para sua function, ex: Esta function é responsável por formatar o evento de um novo backup no bucket.

Runtime: Selecione Node.JS 4.3

Em “Lambda function code”, coloque a função abaixo, substituindo o TopicArn no final do código de acordo com seu Topic ARN armazenado no primeiro passo:

 

‘use strict’;

let aws = require(‘aws-sdk’);

let s3 = new aws.S3({ apiVersion: ‘2006-03-01’ });

exports.handler = function(event, context) {

   // Armazena as informações do conteúdo do objeto vindo do evento

   const eventn = event.Records[0].eventName; // Armazena o nome do evento

   const filesize = event.Records[0].s3.object.size; // Armazena o tamanho do objeto inserido

   const bucket = event.Records[0].s3.bucket.name; // Armazena o nome do bucket

   const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ‘ ‘)); // Armazena a key para o arquivo recebido

   var eventText = JSON.stringify(event, null, 2);

   var filesizemod = “-“; //Cria variável para definir a extensão do size do arquivo

   // Abaixo um IF para definir a extensão que será adicionada na variável criada acima

   if (typeof filesize == “number”) {

       if      (filesize>=1000000000) {filesizemod=(filesize/1000000000).toFixed(2)+’ GB’;}

       else if (filesize>=1000000)    {filesizemod=(filesize/1000000).toFixed(2)+’ MB’;}

       else if (filesize>=1000)       {filesizemod=(filesize/1000).toFixed(2)+’ KB’;}

       else                           {filesizemod=filesize+’ bytes’;}

   } else if (typeof filesize !== ‘undefined’ && filesize) {

       filesizemod = filesize;

   }

   // Cria o ASSUNTO do e-mail

   var subjecttext=”S3Notify: Um novo backup foi recebido!”;

   // Cria o corpo do e-mail, concatenando a mensagem personalizada com o nome do objeto e o tamanho dele

   var eventText2 = “Olá,” +

                   “\nO bucket ” + bucket + ” acaba de receber um backup.” +

                   “\n\nArquivo: ” + key +

                   “\nTamanho: ” + filesizemod +

                   “\n\n\n—-\n—-\n\n” +

                   “Para mais detalhes ou alguma dúvida, entre em contato conosco através do e-mail: suporte@etc.com\n”;

   // Istanciamos a classe SNS

   var sns = new aws.SNS();

   //Cria array com os dados formatados para serem enviados para o objeto do SNS criado anteriormente

   var params = {

       Message: eventText2,

       Subject: subjecttext,

       TopicArn: “Topic ARN do seu tópico no SNS que foi armazenado no final do primeiro passo”

   };

   // Publica o evento formatado no SNS, para que suas inscrições o recebam

   sns.publish(params, context.done);

};

Em “Role”, selecione “Create new role from template(s)” e dê um nome para a role (Ex: LambdaRole). Você precisará editar ela posteriormente para incluir permissões para publicar no SNS.

Em “Advanced Settings”, no campo “Timeout”, aumente para 10 segundos.

Siga até o fim da página e clique em Next > Create function.

Para que a Lambda Function possa publicar as informações vindas do evento, precisaremos dar permissão à role criada nesta Lambda. Para isto, vamos ao console do “IAM”, no painel esquerdo clicamos em “Roles” e selecionamos o nome da role criada na Lambda Function (neste exemplo: LambdaRole). Uma vez nas configurações da role, clique em “Inline Policies” > “Create Role Policy”. Selecione “Custom Policy”, coloque um nome qualquer para a policy e no campo abaixo, cole a seguinte policy substituindo abaixo de “Resource” pelo Topic ARN armazenado no primeiro passo.

{

   “Version”: “2012-10-17”,

   “Statement”: [

       {

           “Sid”: “0001”,

           “Effect”: “Allow”,

           “Action”: [

               “sns:Publish”

           ],

           “Resource”: [

               “Topic-ARN-aqui”

           ]

       }

   ]

}

A configuração do AWS Lambda termina aqui, partiremos para a configuração do S3.

 

4 Configuração do S3

Criaremos agora o bucket para receber o backup e vamos configurar o evento no mesmo.

No painel de serviços da AWS, selecione S3. Uma vez no console S3, clique em: Create bucket.

Em “Bucket name”, coloque o nome desejado. Em “Region”, modifique apenas se desejar outra região para o bucket. Clique em Next > Next > Next > Create bucket.

Após criar o bucket, selecione o mesmo no console do S3. Dentro do bucket, selecione a aba “Properties” e selecione “Events”. Na aba de eventos, selecione “Add notification”. Em “Events”, selecione “CompleteMultipartUpload”. Em “Prefix” e “Suffix”, caso não haja nenhuma particularidade de qual backup notificar, deixe em branco.

Em “Send to”, selecione “Lambda Function” e em “Lambda”, selecione a Lambda Function criada anteriormente.

A configuração do S3 termina aqui, partiremos para a configuração do Python.

 

5 Configuração Python

Nota: Estamos assumindo que este cenário acontece em um servidor local para envio dos backups, por isso utilizarmeos credenciais no código para envio. Caso o cenário aconteça em uma EC2, utilize ***IAM Role*** na instance.

Este é o último passo. Apenas vamos editar algumas informações no código para condizer com nosso cenário e vamos testar o envio.

Antes de iniciar o arquivo, criaremos uma IAM Policy e um IAM User para utilizar suas credenciais no envio do Backup. Para isso, navegue até o console do IAM na AWS.

No painel esquerdo, clique em “Policies” > “Create policy” > “Create Your Own Policy”. Em “Policy Name”, coloque o nome desejado (lembre-se de guardar este nome) e em “Policy Document”, cole a policy abaixo modificando apenas abaixo de “Resource” com o nome do seu bucket:

 

{

   “Version”: “2012-10-17”,

   “Statement”: [

       {

           “Sid”: “0001”,

           “Effect”: “Allow”,

           “Action”: [

               “s3:*”

           ],

           “Resource”: [

               “arn:aws:s3:::Nome_do_bucket*”

           ]

       },

       {

           “Sid”: “0002”,

           “Effect”: “Allow”,

           “Action”: [

               “ses:SendEmail”

           ],

           “Resource”: [

               “*”

           ]

       }

   ]

}

 

Policy criada. Criaremos o user agora.

No painel esquerdo, clique em Users > Add user.

Em “User name”, coloque o nome desejado (Ex: S3User), em “Access type” selecione “Programatic access”.

Clique em “Next: Permissions“, selecione “Attach existing Policies directly” e digite o nome da policy criada anteriormente. Selecione-a e clique em “Next: Review” > “Create user”. Clique em “Download .csv” para baixar as credenciais do usuário criado e após clique em “Close”.

Em um servidor local [Linux], inicie um arquivo .py com o editor de sua preferência:

vim s3backup.py

Cole o código abaixo, editando as informações em “Config” de acordo com o seu cenário:

import boto3

import sys

from botocore.exceptions import ClientError

# Config

ak = ‘Cole aqui a access key contida no CSV baixado ao criar o user’

sk = ‘Cole aqui a secret access key contida no CSV baixado ao criar o user’

region = ‘Coloque a region’

remetente = ‘Coloque o remetente cadastrado no SES’

destinatario = ‘Coloque o destinatário cadastrado no SES’

 

# SYS Config

bucket = sys.argv[1]

key = sys.argv[2]

 

# S3 Config

s3 = boto3.client(‘s3’, aws_access_key_id = ak, aws_secret_access_key = sk, region_name = region)

 

# SES Config

ses = boto3.client(‘ses’, aws_access_key_id = ak, aws_secret_access_key = sk, region_name = region)

 

def send(e):

response = ses.send_email( Source = remetente,

  Destination = {

  ‘ToAddresses’: [

  dest,

 ],

                                                  ‘CcAddresses’: [

                                                          destinatario,      

                                                                 ],

                                                  ‘BccAddresses’: [

                                                          remetente,      

                                                                 ]

        },

   Message = {

  ‘Subject’ : {

  ‘Data’ : ‘Falha no envio do backup!’,

  ‘Charset’ : ‘utf-8’

      },

   ‘Body’ : {

  ‘Text’ : {

  ‘Data’ : ‘Houve um erro ao efetuar o backup. Erro: ‘ + str(e),

   ‘Charset’ : ‘utf-8’

},

   ‘Html’ : {

   ‘Data’ : ‘Houve um erro ao efetuar o backup. Erro: ‘ + str(e),

   ‘Charset’ : ‘utf-8’,

}

}

},

  ReplyToAddresses = [

remetente,

]

)

return response

   

 

try:

 

filename = open(sys.argv[2])

 

except IOError as e:

 

send(e)

 

 

try:

       # Inicia o multipart upload

       mpu = s3.create_multipart_upload (      Bucket = bucket,

                                               Key = key,

                                               ACL = ‘private’

                                        )

 

       part1 = s3.upload_part (        Bucket = bucket,

                                       Key = key,

                                       PartNumber = 1,

                                       UploadId = mpu[‘UploadId’],

                                       Body = filename

                               )

 

       # Disponibiliza info sobre cada part restante

       part_info = {

           ‘Parts’: [

               {

                   ‘PartNumber’: 1,

                   ‘ETag’: part1[‘ETag’]

               }

           ]

       }

       # Finaliza o upload

       s3.complete_multipart_upload (  Bucket = bucket,

                                       Key = key,

                                       UploadId = mpu[‘UploadId’],

                                       MultipartUpload = part_info

                                    )

except ClientError as e:

send(e)

 

Pronto!

Agora iremos testar o upload de um arquivo através deste script, utilizando o comando:

python s3backup.py <nomeDoBucket> <nomeDoArquivo>

Ex:

python s3backup.py s3notify fileTeste.tar.gz

Considerando o exemplo acima e que o arquivo tenha 6.12Mb, o sucesso resultará num e-mail como este:

Post beyond 03

Considerando o exemplo acima, vamos enviar um arquivo que não existe (fileTeste – sem a extensão). O resultado deverá ser um e-mail de erro como este:

Post beyond 02

Considerações

  • Este cenário poderia ser simplificado, no entanto, a ideia aqui é utilizar vários serviços da AWS ao mesmo tempo.
  • Volume de dados maior que 5GB/mês são cobrados no plano FREE da AWS para o S3.
  • Compartilhe 😉

 

 

*Maicon Baum: Cloud Solution Architect,  AWS Certified Solutions Architect, AWS Certified SysOps Administrator, AWS Certified Developer.

 

 

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

CAPTCHA
Change the CAPTCHA codeSpeak the CAPTCHA code