
Compartilhar:
Zach é um ex-educador de desenvolvedores da Vonage
Implementar o projeto do gerenciador de filas em Python no Docker para facilitar a implantação
No mês passado, mostrei a vocês como criar um aplicativo de gerenciamento de filas baseado em SMS com Python e Flask. Esse aplicativo foi ótimo para mostrar um exemplo básico de uso da SMS API da Nexmo, mas, na verdade, servia apenas para prototipagem e desenvolvimento local. Nesta postagem, vou explicar algumas etapas que você pode seguir para deixar esse aplicativo mais pronto para produção, com o resultado final sendo um versão do aplicativo em Docker que você pode implantar diretamente no Heroku.
Ao longo do caminho, vamos falar um pouco sobre:
Melhores práticas para o gerenciamento de segredos
Contêineres e por que são úteis
Trabalhar com um servidor de aplicativos capaz de lidar com o tráfego de produção
Pré-requisitos
Escolha um ponto de partida
Se você acompanhou meu primeiro post sobre a criação de um aplicativo de gerenciamento de filas, tudo o que você precisa para começar este é cumprir todos os pré-requisitos listados acima. Se você está começando agora, pode clonar o aplicativo já pronto da postagem anterior:
git clone https://github.com/nexmo-community/sms-queue-notify.gitVocê também pode começar com a versão finalizada e adaptada para o Docker do projeto:
git clone https://github.com/nexmo-community/docker-queue-manager.gitMuitas das etapas de configuração e dos comandos de compilação descritos neste post ainda precisarão ser executadas, mas você não precisará fazer nenhuma alteração no código.
Seja qual for o ponto de partida, certifique-se de que você está no diretório do seu projeto:
cd sms-queue-notifyou
cd docker-queue-manager Gerenciar segredos
Anteriormente, pedi que você colocasse sua chave Nexmo, seu segredo e seu número de telefone diretamente no topo do main.py. Embora isso tenha funcionado para demonstrar um aplicativo simples, sem muitos componentes móveis, em geral é recomendável garantir que suas credenciais fiquem separadas do seu código. Portanto, a primeira modificação que faremos main.py é ler nossos segredos diretamente das variáveis de ambiente. Altere as seguintes linhas:
NEXMO_KEY = <Your Nexmo Key>
NEXMO_SECRET = <Your Nexmo Secret>
NEXMO_NUMBER = <Your Nexmo Number>para:
NEXMO_KEY = os.environ['NEXMO_KEY']
NEXMO_SECRET = os.environ['NEXMO_SECRET']
NEXMO_NUMBER = os.environ['NEXMO_NUMBER']Em seguida, certifique-se de não publicar acidentalmente seus segredos em nenhum lugar público. Se ainda não tiver um, crie um .gitignore arquivo e certifique-se de que ele esteja .env listado. Já que está nisso, crie um .dockerignore arquivo com o seguinte conteúdo:
.env
.gitAgora você pode criar um novo arquivo chamado .env para armazenar suas informações confidenciais. O conteúdo do arquivo deve ser:
NEXMO_KEY=<Your Nexmo Key>
NEXMO_SECRET=<Your Nexmo Secret>
NEXMO_NUMBER=<Your Nexmo Number>Observe que a formatação é importante aqui. Não há espaços ao redor dos sinais de igual e, ao contrário do que acontecia quando esses valores estavam entre main.py, eles não devem ser colocados entre aspas.
Se você quiser testar seu aplicativo agora, pode executar o seguinte no terminal para definir as variáveis de ambiente com base no .env arquivo:
set -o allexport
source .env
set +o allexportUsaremos o Docker para testar as alterações em nosso aplicativo, que pode ler diretamente do .env arquivo diretamente.
Alterações na configuração
No final de main.py, você verá o seguinte:
if __name__ == '__main__':
app.run(debug=True, threaded=True)Essa configuração, com debug=True, é ótima para testar o aplicativo, pois permite que sejam feitas alterações sem a necessidade de reiniciar o servidor a cada vez. Esse modo de depuração destina-se apenas a fins de desenvolvimento e não deve ser usado em produção.
A outra parte da configuração, threaded=True, diz respeito aos eventos enviados pelo servidor, que exigem o uso de threads para funcionar corretamente. Como você verá em breve, vamos lidar com as solicitações por meio de um servidor de aplicativos separado, portanto, podemos remover essa parte da configuração também. O arquivo atualizado main.py deve ficar assim:
if __name__ == '__main__':
app.run() Criar um Dockerfile
Para facilitar a implantação do nosso projeto, vamos empacotar tudo em um contêiner Docker. Os contêineres são uma maneira leve de garantir que seu aplicativo tenha todos os recursos necessários para rodar, incluindo o sistema operacional correto e as dependências. Agrupar nosso aplicativo em um contêiner permite implantá-lo em diversas plataformas sem precisar se preocupar com quais outros processos, configurações e softwares já existem.
O Docker cria imagens de contêiner usando um arquivo chamado Dockerfile, que lista, linha por linha, as etapas necessárias para criar o ambiente no qual seu aplicativo será executado. É uma receita que indica ao Docker como reproduzir a configuração necessária para que seu aplicativo funcione corretamente. Cada linha do Dockerfile cria uma camada e, quando você recompila uma imagem do Docker, apenas as camadas que sofreram alterações serão recompiladas. Isso significa que você deve começar com a configuração mais geral no início (sistema operacional e pacotes necessários) e avançar para requisitos mais específicos.
Para o nosso aplicativo, vamos começar com um novo arquivo chamado Dockerfile que contenha o seguinte:
FROM python:3.6-slim-buster
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txtIsso instrui o Docker a iniciar com uma imagem base que inclui uma versão leve do Debian Buster , além do Python versão 3.6. Trata-se de uma imagem base padrão disponível no Docker Hub, um registro público de imagens de contêineres.
As duas próximas linhas do Dockerfile garantem que as dependências necessárias para nossa aplicação (Flask, Flask-SQLAlchemy e o SDK do Nexmo) sejam instaladas. A menos que sejam feitas alterações no requirements.txt ou for utilizada uma imagem base diferente, essas etapas precisarão ser realizadas apenas uma vez. As compilações subsequentes (por exemplo, para alterações no código) poderão utilizar essas camadas já existentes.
Servidor de aplicativos de produção
O Flask possui um servidor web integrado para fins de teste, que foi o que usamos no post anterior. Esse servidor não se destina ao uso em produção — ele foi projetado principalmente para processar uma solicitação por vez e não tem capacidade de escalar para lidar com o tráfego esperado de uma aplicação em produção.
Na produção, é importante garantir que você tenha um servidor web dedicado e um servidor de aplicativos separado que lide com a comunicação com seu aplicativo em Python. Duas opções comuns para isso são Nginx e Gunicorn. Como planejamos fazer a implantação no Heroku, que já fornece um servidor web, precisaremos incluir apenas o Gunicorn (além do o gevent para lidar com threads). Adicione o seguinte ao seu Dockerfile:
RUN pip install gunicorn gevent Configuração do banco de dados
Se você se lembra do primeiro post, criamos nosso banco de dados usando alguns comandos em Python diretamente na linha de comando. Isso não funciona muito bem quando você está implantando seu aplicativo em um contêiner, já que não é recomendável realizar etapas manuais para configurar seu ambiente. Para ajudar a manter tudo automatizado, crie um novo create_db.py arquivo com o seguinte conteúdo:
from main import db
db.create_all()Foi fácil! Agora podemos concluir nosso Dockerfile com o seguinte:
COPY . /app
WORKDIR /app
CMD python create_db.py && gunicorn -k gevent -b 0.0.0.0:$PORT main:app
Essas últimas etapas copiam o conteúdo do diretório do seu projeto para uma pasta chamada /app no contêiner, que é então definida como o diretório de trabalho. A última linha informa ao contêiner quais comandos devem ser executados ao ser iniciado: primeiro, criar o banco de dados e, quando isso estiver concluído, iniciar um servidor gunicorn para rodar nosso aplicativo. A $PORT variável de ambiente é definida pelo Heroku quando o contêiner é executado.
Teste localmente com o Docker
Agora que o Dockerfile está pronto, é fácil verificar se tudo está funcionando localmente. Primeiro, certifique-se de que o Docker esteja em execução no seu computador. Em seguida, execute o comando a seguir no diretório do seu projeto para criar uma imagem do Docker, usando --tag para definir um nome fácil de consultar:
docker build --tag queue_app .Se a compilação for bem-sucedida, você já pode executar o contêiner:
docker run -d -p 5000:5000 --env-file .env -e PORT=5000 queue_appObserve que estamos carregando nossos segredos a partir do .env arquivo e definindo a PORT variável de ambiente nós mesmos.
Assim que o contêiner estiver em execução, você poderá abrir um navegador, acessar localhost:5000e ver seu aplicativo!
Ainda há mais uma etapa se você quiser testar seu aplicativo completamente. No post anterior, você configurou o ngrok para tornar seu aplicativo acessível pela web. Você precisará fazer isso novamente se quiser testar o envio de uma mensagem SMS para o aplicativo. Abra uma nova janela do terminal e execute o seguinte:
ngrok http 5000Em seguida, certifique-se de acessar o painel do Nexmo e copiar a URL de redirecionamento nas configurações do seu número no Inbound Webhook URL campo, desta forma: https://<your ngrok ID>.ngrok.io/webhooks/inbound-sms (veja post anterior para mais detalhes).
Agora você já deve conseguir interagir com seu aplicativo por meio de texto, assim como antes! Só que, se você parar o contêiner e reiniciá-lo, verá que o banco de dados estará completamente reinicializado.
Banco de dados Postgres
Na versão de desenvolvimento do aplicativo, usamos um banco de dados SQLite para armazenar informações sobre quem estava na fila. O SQLite criou o banco de dados como um arquivo no diretório do projeto, o que facilitou a configuração. Em uma configuração baseada em contêineres, isso não funciona, pois o sistema de arquivos do contêiner não persiste se ele precisar ser reiniciado. Isso também dificulta o escalonamento do aplicativo em vários contêineres, já que não há uma fonte de dados compartilhada.
Felizmente, usamos o Flask-SQLAlchemy para abstrair os detalhes do banco de dados do nosso código; assim, foi possível trocar nosso SQLite por um Postgres fornecido pelo Heroku é incrivelmente simples. O banco de dados Postgres fica fora do contêiner, portanto, ele persistirá mesmo quando o contêiner for reiniciado e poderá ser acessado por vários contêineres.
Quando o Heroku cria um banco de dados Postgres, a URL do banco de dados é armazenada na DATABASE_URL variável de ambiente. A única alteração que precisamos fazer em nosso código para mudar do SQLite para o banco de dados Postgres do Heroku é substituir esta linha em main.py:
db_path = "sqlite:///queue.db"com isto:
db_path = os.environ['DATABASE_URL']Então, precisamos atualizar a linha do nosso Dockerfile que diz:
RUN pip install gunicorn geventpara dizer:
RUN pip install gunicorn gevent psycopg2-binaryO pacote psycopg2 é um adaptador de banco de dados Postgres desenvolvido especificamente para Python.
A etapa final consiste em criar um banco de dados Postgres no Heroku, o que exige que você, primeiro, faça login na CLI do Heroku e crie um aplicativo no Heroku:
heroku login
heroku create <your application name>Em seguida, crie o banco de dados, certificando-se de incluir o nome do seu aplicativo:
heroku addons:create heroku-postgresql:hobby-dev -a <your application name> Implemente seu contêiner no Heroku
Com seu aplicativo e banco de dados inicializados no Heroku, faltam apenas mais algumas etapas para implantar seu aplicativo no Docker. Primeiro, você precisará definir suas credenciais do Nexmo como variáveis de configuração do Heroku (Heroku Config Vars), o que é feito no painel do Heroku, na seção “Configurações”:
Config Vars interface in Heroku
Em seguida, é recomendável recompilar seu contêiner do Docker para garantir que as alterações recentes sejam incorporadas:
docker build --tag queue_app .Em seguida, você precisará fazer login no registro de contêineres do Heroku:
heroku container:loginE, por fim, você fará o push e a publicação do seu contêiner no Heroku:
heroku container:push web -a <your application name>
heroku container:release web -a <your application name>É isso! Bem, quase. Execute seu aplicativo pelo painel do Heroku para verificar se está funcionando e, em seguida, acesse o painel do Nexmo e atualize o campo Inbound Webhook URL para que fique assim: https://<your application name>.herokuapp.com/webhooks/inbound-sms.
Você conseguiu! Com o poder da conteinerização, agora você tem um aplicativo pronto para produção, facilmente escalável e replicável.
Se você tiver algum problema ou dúvida, entre em contato conosco pelo nosso Slack da Comunidade. Obrigado pela leitura!