Uma Aplicação Simples Usando Flask e NGinx

Author

Mario Luiz Bernardinelli <mariolb _at_ gmail _dot_ com>

Version

0.1

Date

2019-05-08

Introdução

https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uswgi-and-nginx-on-ubuntu-18-04

O objetivo deste artigo é apresentar uma maneira de executar uma aplicação web escrita em Python. Para tanto, vamos usar o framework Flask, muito conhecido na comunidade Python, o servidor Web NGinx, muito conhecido pela sua eficiência e estabilidade e, para conectar estes dois elementos, vamos usar o uWSGI.

A aplicação apresentada aqui é extremamente simples, e, como o objetivo é disponibilizar uma aplicação Python através de um serviço web, questões como a utilidade desta aplicação não serão levadas em consideração. A ideia é fornecer um “esqueleto” para a aplicação.

Mas, antes de iniciar, vamos a algumas considerações:

  • O sistema operacional (Linux, sendo que utilizei o Debian para este artigo) deve estar configurado e operacional, já com o Python3 e o NGinx instalados.

  • Neste artigo, considero que o NGinx irá fornecer apenas o serviço para a nossa aplicação de teste.

Instalação

Vamos executar a configuração do NGinx para execução de aplicações Python em etapas. A cada etapa, executaremos algum tipo de teste para verificarmos seu funcionamento. Esta abordagem é muito interessante do ponto de vista prático, porque permite um melhor entendimento da responsabilidade de cada etapa e, talvez mais importante, provê conhecimento do ambiente e dos testes que podem ser executados caso algum problema ocorra num ambiente de produção. Além disso, há uma questão primordial: testar cada unidade facilita muito na correção de algum problema, porque não é necessário analisar o sistema como um todo, mas apenas a etapa eu questão. Isso ficará mais claro quando executarmos as etapas.

Pré-requisitos

Precisaremos de alguns pacotes adicionais para permitir a instalação do ambiente. Dependendo da distribuição e configuração do seu ambiente, os pacotes a seguir podem ou não serem necessários. Lembre-se que este artigo é baseado no Debian, então, todos os comandos serão os utilizados nesta distribuição:

sudo apt update
sudo apt install python3-pip python3-dev build-essential libssl-dev \
                 libffi-dev python3-setuptools python3-virtualenv \
                 uwsgi

Ambiente virtual

O próximo passo é criar um ambiente virtual para isolar a aplicação Flask dos outros arquivos e aplicações Python do sistema.

Utilizar ambientes virtuais de Python é especialmente interessante por permitir que todas as dependências da aplicação sejam instaladas apenas no ambiente em questão, e não no ambiente Python utilizado pelo resto do sistema operacional e aplicações. Além disso, este procedimento evita alguns problemas. Por exemplo, o uso de ambientes virtuais evita que, caso algum módulo ou aplicação seja instalado no Python utilizado pelo sistema operacional, gere algum problema com algum módulo utilizado pela aplicação. O inverso também é verdadeiro: pode-se instalar qualquer módulo ou aplicação Python num ambiente virtual do Python sem que isto provoque nenhum problema com as demais aplicações e módulos utilizados pelo restante do sistema operacional.

Antes de criamos o ambiente virtual, tenha em mente o seguinte: uma aplicação Flask não precisa necessariamente ser instalada na estrutura de diretórios padrão do servidor Web (a pasta htdocs para os iniciados em Apache). Na verdade, do meu ponto de vista, eu acho que as aplicações devem ficar fora da pasta padrão do servidor web e o motivo mais simples para isso é o seguinte: caso ocorra algum problema com servidor web ou com o conector com o Python (uWSGI), será mais difícil que o conteúdo dos scripts da aplicação sejam enviados para o cliente, ou seja, a obtenção do código-fonte fica um pouco mais difícil por parte do cliente. Porém, que fique muito claro: este procedimento não evita problemas de segurança da aplicação!

Vamos considerar então que a aplicação ficará hospedada na pasta /home/mario/app. Também vamos considerar que o usuário mario tem acesso completo à esta pasta.

Hora de criar as pastas para a aplicação e o ambiente virtual de Python:

cd /home/app
mkdir horacerta
cd horacerta
python3 -m venv venv

A pasta horacerta é a pasta-raiz da aplicação, enquanto que a sub-pasta venv é a pasta onde será instalado o ambiente virtual de Python.

No comando python3 -m venv venv, a opção -m venv carrega e executa o módulo de ambiente virtual e o último parâmetro (venv) é no nome que escolhi para a pasta onde serão armazenados os arquivos do ambiente virtual. Fique à vontade para alterar a pasta do ambiente virtual.

Observação: por questões didáticas, nossa aplicação executará uma tarefa bastante simples: cada vez que ela for acessada, serão apresentadas a data e hora atuais. Simples assim, já que a ideia é mostrar a integração do Python/Flask com NGinx. Já já veremos o código da aplicação.

Depois de instalado o ambiente virtual, é preciso ativá-lo:

source ./venv/bin/activate

Observe que o prompt do terminal em uso será alterado: ele será prefixado com o texto (venv). Exemplo:

(venv) mario@cyber horacerta$

O prompt indica que estamos usando o ambiente virtual. Agora podemos instalar os pacotes necessários para a aplicação:

pip install wheel flask uwsgi

Para nossa aplicação, estes pacotes são suficientes. O próximo passo será criar a aplicação e testá-la.

Criando a aplicação

Agora que nosso ambiente virtual está pronto, vamos escrever nossa aplicação mais do que simplista.

Crie o arquivo principal da aplicação (use o editor de sua preferência):

cd /home/app/horacerte
vim horacerta.py

Este arquivo dever ter o seguinte conteúdo:

import time

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    date = time.strptime("%Y-%m-%d %H:%M:%S")

    return "<h1 style='color:blue'>Hora atual:! ({})</h1>".format(date)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

Escolhi a porta 8080 por ser uma porta alta, isto é, que não precisa de privilégios administrativos para ser usada e para não conflitar com nenhum outro serviço em execução na máquina.

Como eu havia dito, a cada passo faremos um teste para validar etapa. Para tanto, vamos executar a aplicação à partir do ambiente virtual da aplicação:

(venv) mario@cyber horacerta$ python3 horacerta.py
 * Serving Flask app "horacerta" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)

Ao executar a aplicação, será lançado um servidor web do próprio Flask. Este servidor não deve ser usado em ambiente de produção, pois ele é muito simples e não dispõe de recursos de segurança, desempenho e estabilidade. Seu objetivo é permitir testes rápidos, como o que usamos aqui.

Abra o navegador web de sua preferência e acesse a URL http://localhost:8080. Deve ser carregada uma página similar a esta Figura Teste da aplicação.

_images/flask-nginx-01.png

Figura: Teste da aplicação

Para finalizar o teste, tecle CTRL-C para finalizar a aplicação.

Com isso já criamos a aplicação e nos certificamos que ela funciona.

Criação do ponto de entrada da aplicação no uWSGI

O próximo passo é criar um ponto de entrada no uWSGI. Este ponto de entrada diz ao serviço uWSGI como interagir com a aplicação. Criaremos nosso ponto de entrada no arquivo wsgi.py:

(venv) mario@cyber horacerta$ vim wsgi.py

Adicione o seguinte conteúdo a este arquivo:

from horacerta import app

if __name__ == "__main__":
    app.run()

Neste arquivo, fazemos basicamente o seguinte:

  • Importamos o objeto app módulo horacerta, que é o arquivo principal de nossa aplicação;

  • Criamos o ponto de entrada da aplicação.

Com isso, temos uma aplicação funcional e o seu ponto de entrada estabelecido. Vamos testar este conjunto usando o uWSGI. Podemos executar a aplicação simplesmente passando para o uWSGI o nome do arquivo do ponto de entrada (sem a extensão):

(venv) mario@vtawst011 horacerta$ uwsgi --socket 0.0.0.0:5000 \
                                        --protocol=http -w wsgi:app

Parâmetros utilizados:

  • –socket: indica o endereço e porta para acesso à aplicação;

  • –protocol: por padrão, o uWSGI utiliza um protocolo binário e rápido, porém, como queremos testar com um navegador, precisamos especificar o protocolo HTTP;

  • -w: especifica o nome do arquivo do ponto de entrada da aplicação (sem a extensão) e o nome do objeto da aplicação.

Abra o navegador e acesse http://localhost:5000. Você deverá ver a saída da aplicação, tal qual o teste anterior.

Finalize o uWSGI com CTRL-C.

Do ponto de vista da aplicação, já fizemos todos os testes: testamos a aplicação e também a integração com o uWSGI. Com isso, podemos finalizar o terminal do ambiente virtual:

deactivate

Agora podemos configurar o uWSGI

Configuração do uWSGI

Com as configurações que fizemos até agora, já é possível executar a aplicação através do uWSGI, porém, o uWSGI não é adequado para ser utilizado como servidor. Precisamos de algo que seja mais robusto ao longo do tempo: é aí que entra o NGinx.

Vamos criar um arquivo de configuração com os parâmetros relevantes para nossa aplicação. Crie o arquivo /home/app/horacerta/horacerta.ini com o seguinte conteúdo:

[uwsgi]
module = wsgi:app
master = true
processes = 5

socket = horacerta.sock
chmod-socket = 660
vacuum = true

die-on-term = true

logto = /var/log/uwsgi/%n.log

Agora vamos ver o que estes parâmetros significam:

Parâmetro

Descrição

module

Especifica o módulo a ser usado (nome do arquivo, sem extensão) e o nome do objeto da aplicação a ser chamado (executado).

master

Habilita o modo master de operação.

processes

Especifica o número de processos que o uWSGI deve executar para servir às requisições do serviço web.

socket

Especifica o socket a ser utilizado para a comunicação com o serviço web (NGinx). Sockets Unix são os preferidos, por serem muito rápidos e mais seguros (são acessíveis apenas à partir da própria máquina). Como não especificamos o caminho, o socket será criado na pasta da aplicação.

chmod-socket

Especifica as permissões de acesso ao socket de comunicação com o servidor web.

vacuum

Faz a limpeza do socket quando o processo finalizar.

die-on-term

Habilitar esta opção ajudar a garantir que o sistema init e o uWSGI tenham as mesmas suposições sobre o significado de cada sinal de processo. Habilite esta opção para alinhar os dois componentes do sistema, evitando comportamentos estranhos.

logto

Especifica o caminho e nome do arquivo de log da aplicação.

Observe que não especificamos o protocolo a ser utilizado, como fizemos no teste com o uWSGI na linha de comando e a resposta é simples: por padrão, o uWSGI utiliza um protocolo próprio, projetado para comunicação com servidores. O NGinx suporta este protocolo nativamente e, portanto, é melhor usar este protocolo nativo do que forçá-lo a usar o HTTP.

Criando uma unidade no SystemD

Agora chegou a ver de configurar nossa aplicação como um serviço gerenciado pelo SystemD. Criar uma unidade (ou serviço) no SystemD significa que a aplicação poderá ser iniciada automaticamente pelo sistema operacional, e também contará com o suporte de gerenciamento do próprio SystemD.

Crie um arquivo de serviço no SystemD dentro da pasta /etc/systemd/system. Este arquivo deve ter a extensão .service. Como exemplo, vamos criar o arquivo hiracerta.service com o seguinte conteúdo:

[Unit]
Description=Service HoraCerta
After=network.target

[Service]
User=mario
Group=www-data
WorkingDirectory=/home/app/horacerta
Environment="PATH=/home/app/horacerta/venv/bin"
ExecStart=/home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini

[Install]
WantedBy=multi-user.target

A seção Unit especifica os metadados e dependências do serviço. Basicamente, ela contém uma descrição do serviço e informa ao SystemD que o serviço deve ser iniciado após o objeto network ter sido executado.

Na seção Service são especificados os parâmetros relacionados à aplicação propriamente dita:

Parâmetro

Descrição

User

Usuário sob o qual o serviço deve ser executado

Group

Grupo sob o qual o serviço deve ser executado (use www-data para que o NGinx possa comunicar-se com os processos do uWSGI

WorkingDirectory

Diretório de trabalho da aplicação

Environment

Ajusta as variáveis de ambiente para execução da aplicação

ExecStart

Comando para executar o serviço. O SystemD exige que seja fornecido o caminho completo. Para o uWSGI, passamos o nome do arquivo de configuração do serviço da aplicação.

A última seção é a Install:

Parâmetro

Descrição

WantedBy

Informa ao SystemD a qual target este serviço (aplicação) está vinculado. Isto permite a execução do serviço quando o target for executado na inicialização do sistema operacional.

Solicite ao SystemD que recarregue suas configurações:

sudo systemctl daemon-reload

Inicie o serviço:

sudo systemctl start horacerta.service

Verifique se o serviço foi iniciado corretamente e está em execução:

sudo systemctl status horacerta.service

Se tudo estiver certo, a saída do comando deve ser similar à seguinte:

horacerta.service - uWSGI instance to serve HoraCerta
   Loaded: loaded (/etc/systemd/system/horacerta.service; disabled;
    vendor preset: enabled)
   Active: active (running) since Wed 2019-05-08 15:49:47 -03; 2s ago
 Main PID: 14546 (uwsgi)
    Tasks: 6 (limit: 4915)
   CGroup: /system.slice/horacerta.service
           ├─14546 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini
           ├─14547 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini
           ├─14548 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini
           ├─14549 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini
           ├─14550 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini
           └─14551 /home/app/horacerta/venv/bin/uwsgi --ini horacerta.ini

Se ocorrer algum erro, verifique todas as configurações antes de passar para a próxima etapa.

Configurando o NGinx com uWSGI

Nesta etapa, nosso servidor uWSGI já está operacional, apenas aguardando a chegada das requisições através do socket. Nosso próximo passo será configurar o NGinx para repassar as requisições através do socket de comunicação com o uWSGI.

Vamos iniciar com a criação de um arquivo de configuração do site: crie o arquivo /etc/nginx/sites-available/horacerta.conf com o seguinte conteúdo:

server {
    listen 80;
    server_name vectora mario.vectora;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/home/app/horacerta/horacerta.sock;
    }
}

Neste arquivo, instruímos o NGinx para esperar conexões na porta 80 e definimos o nome do servidor. Depois, definimos as configurações para o site raiz do sistema (/). Neste bloco de configuração, solicitamos o carregamento dos parâmetros comuns do uWSIG e definimos o socket de comunicação com o serviço uWSGI da nossa aplicação.

Uma vez criado o arquivo, devemos criar um link para ele na pasta /etc/nginx/sites-enabled:

cd /etc/nginx/sites-enabled
sudo ln -sf /etc/nginx/sites-available/horacerta.conf .

Solicite ao NGinx que verifique a sintaxe das configurações:

sudo nginx -t

Se surgir alguma mensagem de erro, verifique o arquivo recém-criado.

Quando não houver nenhuma mensagem de erro, reinicie o serviço NGinx:

sudo systemctl restart nginx

Configuração finalizada. A aplicação deve estar disponível para acesso. Abra o navegador e acesse: http://localhost.

Dicas de manutenção

Na ocorrência de problemas com a aplicação, tente verificar os itens a seguir.

Logs do NGinx:

sudo less /var/log/nginx/error.log
sudo less /var/log/nginx/access.log
sudo journalctl -u nginx

Logs do processo da aplicação:

sudo journalctl -u myproject

Segurança da aplicação

Não cobrimos esta questão neste artigo, mas é essencial que a aplicação seja bem desenvolvida e bem testada. Além disso, atualmente é imprescindível que a comunicação com a aplicação seja protegida por criptografia. Para tanto, deve ser adquirido um certificado digital SSL e o servidor NGinx deve ser configurado para utilizá-lo.

Lembre-se que, se você já tiver um domínio, o certificado SSL pode ser adquirido gratuitamente de Let’s Encrypt.

Mas isso é papo para um outro artigo, provavelmente no meu blog geral ou no de documentação Linux.