Neste post eu vou falar sobre como fazer o deploy de uma aplicação Django (mas fácil de replicar para qualquer outro framework Python como Flask, por exemplo) na AWS economizando muito dinheiro (dependendo do tráfego da sua aplicação ou quais serviços você pretende usar na AWS, pode ficar de graça…para sempre!*)

ESTE POST SERÁ UM POUCO LONGO, DESCULPE-ME POR ISSO

Em Julho, eu terminei o desenvolvimento do meu projeto educacional para crianças usando o Django e a minha primeira pergunta quando finalizei-o foi como fazer o deploy da aplicação na cloud da maneira mais barata o possível.

Minha primeira opção foi o Heroku, porque é bem simples de fazer o deploy e de implementar um processo de deploy contínuo de graça, mas se eu tiver que escalar a minha aplicação por qualquer motivo, o Heroku pode ficar bem mais caro, então eu decidi pensar melhor.

Nesse processo, eu achei um projeto, que foi o divisor de águas na minha procura. Este projeto é o Zappa, abaixo a descrição retirada do Readme do projeto no Github:

Zappa makes it super easy to build and deploy server-less, event-driven Python applications (including, but not limited to, WSGI web apps) on AWS Lambda + API Gateway. That means infinite scaling, zero downtime, zero maintenance - and at a fraction of the cost of your current deployments!

E de fato ele cumpre o prometido, é muito fácil fazer o deploy da aplicação na AWS, a parte mais difícil é configurar as roles/políticas do IAM dentro da AWS (Eu acho que é parte mais difícil para qualquer deploy na AWS :-) ). Então sem mais delongas, deixa eu mostrar para vocês como eu fiz para fazer o deploy da minha aplicação Django usando o Zappa.

PS: Não está no escopo desse artigo, explicar profundamente como os serviços da AWS funcionam, eu estou subentendendo que você já tem algum conhecimento sobre isso e eu não vou usar a console para configurar os serviços, eu vou usar o CLI da AWS, então se você quiser usá-lo também, clique aqui para saber como configurá-lo

PS 2: Configure o CLI da AWS para algum usuário que tenha acesso de administrador mas NUNCA, NUNCA, NUNCA MESMO, USE A CONTA RAIZ! E mantenha as chaves de acesso seguras por exemplo usando um gerenciador de senhas

PS 3: Todos os comandos e exemplos foram feitos usando uma máquina Linux

1. Criando o requerido Bucket S3 na AWS

O Zappa usa um bucket S3 para fazer o upload do pacote compatível com o Lambda gerado durante o processo de deploy, que explicarei mais para frente. Para criar o bucket usando o CLI:

aws s3api create-bucket --bucket nome_do_bucket --region regiao_de_sua_escolha --create-bucket-configuration LocationConstraint=region_of_your_choose

O parâmetro region e a configuração LocationConstraint são requeridas se você está criando um bucket fora da região us-east-1, se você escolher esta região pode remover ambos do comando.

Lembre-se que os nomes do bucket são únicos por toda AWS, então se voĉe receber um erro como BucketAlreadyExists, você tem que escolher um novo nome.

Se tudo funcionar como esperado, você irá receber este retorno do comando:

{
    "Location": "http://nome_do_seu_bucket.s3.amazonaws.com/"
}

PS 4: Se sua aplicação usa um banco de dados SQLite eu recomendo que você crie mais um bucket para ele e se sua aplicação tem arquivos estáticos e de mídia eu também recomendo que você crie um novo bucket para eles, nós veremos mais para frente como configurá-los

2. Configurando as políticas, a role, o grupo e o usuário no IAM

Para uma melhor segurança (e também uma melhor prática), nós precisamos configurar alguns objetos do IAM exclusivos para serem usados pelo Zappa para restringir qual recursos da AWS ele terá acesso.

2.1 Criando a role

Vamos começar criando a role que será passada para a função Lambda que será criada pelo Zappa durante o processo de deploy.

Para criar a role usando o CLI, nós temos que passar um arquivo JSON que vai representar quais serviços da AWS serão permitidos a chamar outros serviços da AWS no nome do usuário do Zappa (Assume Role Policy Document).

Crie o arquivo JSON em algum diretório no seu computador com este conteúdo:

Então é só rodar este comando usando o CLI para criar a role:

aws iam create-role --role-name my-role --assume-role-policy-document file:///tmp/zappa_assume_role.json

my-role pode ser qualquer nome que você queira. file:///tmp/zappa_assume_role.json precisa ser o caminho para o arquivo JSON que você criou acima.

Este será o retorno, se o comando for bem sucedido:

{
    "Role": {
        "Path": "/",
        "RoleName": "my-role",
        "RoleId": "AROA3IDJ3HNEWIQZA5IQZ",
        "Arn": "arn:aws:iam::773316098889:role/my-role",
        "CreateDate": "2020-08-29T19:24:37Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": [
                            "apigateway.amazonaws.com",
                            "lambda.amazonaws.com",
                            "events.amazonaws.com"
                        ]
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

Guarde o valor retornado pelo campo Arn em algum lugar, nós precisaremos dele mais para a frente.

2.2 Anexando a política na role

Agora é hora de anexar a política com a role que criamos anteriormente.

Uma política da AWS também pode ser um documento JSON que representa as permissões que serão concedidas para o uso dos serviços dentro da AWS, para a role que iremos anexá-la.

Para isso, novamente você precisa criar um arquivo JSON em qualquer diretório no seu computador com este conteúdo:

Novamente vamos ao CLI e executar o seguinte comando:

aws iam put-role-policy --role-name my-role --policy-name my-policy --policy-document file:///tmp/zappa_policy.json

my-role precisa ser o mesmo nome da role que criamos anteriormente. my-policy pode ser qualquer nome que você queira. file:///tmp/zappa_policy.json precisa ser o caminho aonde você criou o JSON acima.

Se o comando for bem sucedido, nada será retornado por ele

Mas se você quiser confirmar se de fato funcionou, você pode rodar este comando:

aws iam get-role-policy --role-name my-role --policy-name my-policy | head -3

O retorno deve ser este:

{
    "RoleName": "my-role",
    "PolicyName": "my-policy",
2.3 Criando o grupo

Nos últimos 2 passos, nós definimos a role e a política que serão passadas para a função Lambda que será criada pelo Zappa durante o deploy.

Agora, nós temos que definir o grupo, o usuário e as políticas que irão permitir ao Zappa criar os recursos dentro da AWS (Ex.: função Lambda, criar o API Gateway, gravar os pacotes no S3, etc).

Nós iremos começar criando o grupo, executando o seguinte comando do CLI:

aws iam create-group --group-name my-group

Este vai ser o retorno, se o comando for bem sucedido:

{
    "Group": {
        "Path": "/",
        "GroupName": "my-group",
        "GroupId": "AGPA3IDJ3HNEYAXDIXQ5K",
        "Arn": "arn:aws:iam::773316098889:group/my-group",
        "CreateDate": "2020-08-29T20:30:14Z"
    }
}
2.4 Anexando a política para as permissões gerais do Zappa no grupo

Nós iremos fazer algo similar o que fizemos no passo 2.2, mas ao invés de criar apenas um JSON, nós iremos criar dois JSONs, porque nós temos que anexar duas políticas para o grupo, uma para as permissões gerais e outra para as permissões específicas para o S3.

Vamos começar com a geral, crie o arquivo JSON em algum diretório no seu computador com o conteúdo abaixo, porém desta vez nós iremos fazer uma pequena modificação no JSON antes de usá-lo no comando do CLI.

Procure por full_arn_from_created_role dentro do conteúdo e substitua com o campo Arn da role que criamos anteriormente.

Depois disso vamos ao CLI e rodar o comando que irá anexar a política ao grupo:

aws iam put-group-policy --group-name my-group --policy-document file:///tmp/zappa_general_policy.json --policy-name my-general-policy

my-group precisa ser o mesmo nome do grupo que criamos anteriormente. my-general-policy pode ser qualquer nome, mas não pode ser o mesmo nome da política que criamos no passo 2.2 file:///tmp/zappa_general_policy.json precisa ser o caminho aonde o JSON acima foi criado.

Se o comando funcionar,nada será retornado

Mas se você quiser confirmar, você pode rodar este comando:

aws iam get-group-policy --group-name my-group --policy-name my-general-policy | head -3

O retorno deve ser:

{
    "GroupName": "my-group",
    "PolicyName": "my-general-policy",
2.5 Anexando a política para as permissões específicas do S3 no grupo

Agora, nós vamos anexar a política específica para o S3 no grupo, nós vamos repetir os mesmos passos do passo anterior. Mas o conteúdo do JSON dessa vez será diferente e novamente faremos uma pequena modificação no conteúdo antes de usá-lo no CLI.

Procure por full_arn_from_s3_bucket dentro do conteúdo e substitua pelo Arn do bucket do S3 criado no passo 1.

O ARN do bucket do S3 segue este padrão: arn:aws:s3:::nome_do_seu_bucket

Se você criou mais de um bucket no passo 1, você precisa adicionar o ARN deles também.

Depois vamos repetir os mesmos comandos do passo anterior:

aws iam put-group-policy --group-name my-group --policy-document file:///tmp/zappa_s3_policy.json --policy-name my-s3-policy

my-group precisa ser o nome do grupo que criamos anteriormente. my-s3-policy pode ser qualquer nome que você queira, desde que não seja o mesmo das políticas que criamos nos passos 2.2 e 2.4. file:///tmp/zappa_s3_policy.json precisa ser o caminho aonde foi criado o JSON acima.

Se o comando for bem sucedido, nada será retornado

Mas se você quiser confirmar a criação, você pode rodar este comando:

aws iam get-group-policy --group-name my-group --policy-name my-s3-policy | head -3

O retorno deve ser:

{
    "GroupName": "my-group",
    "PolicyName": "my-s3-policy",
2.6 Criando o usuário, adicionando ele ao grupo e criando as chaves de acesso

Por último, temos que criar o usuário que será usando exclusivamente pelo Zappa e adicionar ele no grupo criado anteriormente e gerar as chaves de acesso do usuário.

Vamos começar criando o usuário:

aws iam create-user --user-name my-user

O retorno deve ser:

{
    "User": {
        "UserName": "my-user",
        "Path": "/",
        "CreateDate": "2013-06-08T03:20:41.270Z",
        "UserId": "AIDAIOSFODNN7EXAMPLE",
        "Arn": "arn:aws:iam::123456789012:user/Bob"
    }
}

Agora vamos adicionar o usuário ao grupo:

aws iam add-user-to-group --user-name my-user --group-name my-group

Se o comando for bem sucedido, nada será retornado

Mas se você quiser confirmar se o usuário foi adicionado ao grupo:

aws iam get-group --group-name my-group

O retorno deve ser:

{
    "Users": [
        {
            "Path": "/",
            "UserName": "my-user",
            "UserId": "AIDA3IDJ3HNEVBCYBTCVB",
            "Arn": "arn:aws:iam::773316098889:user/my-user",
            "CreateDate": "2020-08-29T21:55:40Z"
        }
    ],
    "Group": {
        "Path": "/",
        "GroupName": "my-group",
        "GroupId": "AGPA3IDJ3HNEYAXDIXQ5K",
        "Arn": "arn:aws:iam::773316098889:group/my-group",
        "CreateDate": "2020-08-29T20:30:14Z"
    }
}

Agora, vamos gerar finalmente as chaves de acesso:

aws iam create-access-key --user-name my-user

O retorno deve ser:

{
    "AccessKey": {
        "UserName": "my-user",
        "Status": "Active",
        "CreateDate": "2015-03-09T18:39:23.411Z",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY",
        "AccessKeyId": "AKIAIOSFODNN7EXAMPLE"
    }
}

Salve o valor dos campos SecretAccessKey and AccessKeyId em um lugar seguro (é uma boa prática fazer isso), nós precisaremos deles mais para frente.

3. Considerações antes de instalar e configurar o Zappa

Tem duas coisas que o Zappa não irá te ajudar dentro da AWS: Como manusear os seus arquivos estáticos e de mídia e como se conectar no banco de dados.

Então, se você quiser usar a AWS para isso também, você terá que fazer isso dentro da sua aplicação Python.

No meu deploy, para manter as coisas simples, eu escolhi usar o SQLite como banco de dados e gravar o arquivo do banco em um bucket do S3 e eu também escolhi armazenar os arquivos estáticos e de mídia em outro bucket do S3 (por isso eu avisei vocês para criar alguns buckets extra no passo 1)

Outras opções estão disponíveis como por exemplo o Aurora ou o RDS como banco de dados (porém se você for usá-los, pode ser que você tenha que atualizar as políticas do IAM que criamos anteriormente)

Para configurar o Django para usar o S3 tanto para armazenar o banco de dados do SQLite, tanto para armazenar os arquivos estáticos, nós vamos precisar instalar outros dois pacotes Python que serão responsáveis por isso.

Lembre-se de ativar seu virtualenv, se você estiver usando-o ou use o pipenv

pip install django-s3-storage # Irá trabalhar com os arquivos estáticos
pip install django-s3-sqlite # Irá trabalhar com o banco de dados

Exemplo de como configurar settings.py do Django para gerenciar os arquivos estáticos e o banco de dados usando o S3:

4. Instalando e configurando o Zappa

Agora nós vamos instalar e configurar o Zappa, para isso iremos usar o pip

Lembre-se de ativar seu virtualenv, se você estiver usando-o ou use o pipenv

pip install zappa

Para configurá-lo, você precisa criar o arquivo zappa_settings.json dentro da raiz do repositório da sua aplicação Django (o mesmo lugar do manage.py), abaixo segue um exemplo:

dev é o nome do estágio que será criado dentro do API Gateway.

django_settings precisa ser nome_do_seu_projeto_django.settings

s3_bucket precisa ser o nome do bucket do S3 que criamos no passo 1.

Você pode ver o meu projeto no Github para mais informações.

5. Fazendo o deploy da aplicação

Finalmente, vamos ao que interessa :-)

Antes de rodar o comando para finalmente fazer o deploy da aplicação dentro da AWS, nós precisamos criar as variáveis de ambiente com as chaves de acesso do usuário que criamos no passo 2.6.

Lembre-se de ativar seu virtualenv antes, se você está usando-o ou se você está usando o pipenv rode pipenv shell antes

export AWS_ACCESS_KEY_ID=access_key_do_usuario_criado
export AWS_SECRET_ACCESS_KEY=secret_key_do_usuario_criado

Agora você pode rodar o comando para fazer o deploy da aplicação pela primeira vez:

zappa deploy dev

dev precisa ser o nome do estágio que definimos no arquivo zappa_settings.json

Se tudo deu certo, este deverá ser o retorno no seu terminal:

...
Deploying API Gateway...
Deployment complete!: https://wf13r2h75a.execute-api.us-west-2.amazonaws.com/dev

Você pode testar a URL acima para ver se sua aplicação está rodando direitinho, se ela não estiver você pode usar este comando para checar os logs e ver o que aconteceu:

zappa tail dev

E para fazer alguma atualização na sua aplicação, você não precisa rodar o comando de deploy novamente, basta rodar:

zappa update dev

Bem isso é tudo pessoal, abaixo seguem alguns links com informações adicionais:

Deploy de uma aplicação Flask Deploy de uma aplicação Flask

Deploy usando domínio próprio e certificado SSL

Deploy usando Aurora como banco de dados

Muito obrigado pela paciência! E tenha um bom dia!