Publicando (deploy) aplicativo web feito em flask usando gunicorn e nginx

Precisei publicar uma aplicativo web que fiz para meus estudantes e colaboradores poderem analizar alguns dados aqui na rede da universidade. Inicialmente iniciava o aplicativo apenas dessa forma na linha de comando

gunicorn --bind 0.0.0.0:5000 --workers 4 --timeout 0 wsgi:app

Essa forma é eficiente para fazer testes e ficar observando os erros, com o aplicativo aberto no navegador e de olho na saída do terminal. Na fase em que não precisamos mais olhar tantos erros (fase de produção no jargão dos desenvolvedores) é necessário que ele funcione como um serviço do servidor e seja apontado com o nginx para que fique acessível na rede, portanto vamos às etapas.

Construindo o serviço

Este é o projeto que estou colocando na rede github.com/orahcio/reducao, portanto a palavra reducao sempre aparecerá quando estiver me referenciando ao projeto. A referência que mais gostei para construção desse tutorial foi essa aqui do Blog do En.

Para criar o serviço vamos criar o arquivo que constrói as especificações do que queremos executar, aqui usei o nome reducao, a localização é portanto /etc/systemd/system/reducao.service

[Unit]
Description=Serviço pra executar o gunicorn do meu aplicativo
After=network.target
 
[Service]
User=orahcio
Group=www-data
Environment="PATH=/home/orahcio/.pyenv/versions/reducao/bin"
WorkingDirectory=/home/orahcio/reducao
ExecStart=/home/orahcio/.pyenv/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/sammy/myproject/myproject.sock myproject.wsgi:application
 
[Install]
WantedBy=multi-user.target

Utilizei algumas modificaçoes em relação ao tutorial já citado, propostas aqui neste tutorial. As linhas que precismos compreender melhor são as que estão no bloco [Service].

Primeiramente o usuário e o grupo devem estar de acordo com o usuário (User=) onde a pasta do projeto do aplicativo está armazenada e o grupo (Group=) deve ser o padrão no nginx, portanto deixe inalterado como www-data.

Tive um problema de permissão nas primneiras vezes que tentei acessar o aplicativo ao final de todos os passos, dessa forma, garanta que esse projeto esteja no grupo correto e sua pasta $HOME tenha permisões de leitura e escrita, para isso vamos fazer

sudo chmod 751 /home/orahcio

para que o diretório pessoal (que é o diretório raiz do projeto) tenha as permisões para que o nginx possa acessar e escrever dados no diretório do projeo. Este por sua vez deve pertencer ao grupo padrão do nginx, www-data, para isso vamos fazer

sudo chgrp www-data ~/reducao

para que o diretório do projeto esteja no grupo correto.

Um último detalhe é que os arquivos binários do meu projeto não estão na pasta do projeto, isso aconteceu porque usei o pyenv para gerenciar o ambiente virtual do meu projeto, para saber mais é só consultar o tutorial de como gerencial ambientes virtuais usando o pyenv aqui. Por isso as linhas referente ao meu ambiente virtual (Environment=) e a execução do gunicorn (ExecStart=) estão em diretórios diferentes do diretório do projeto (WorkingDirectory).

Finalmente vamos colocar o serviço em execução, basta fazermos

sudo systemctl start reducao
sudo systemctl enable reducao

depois é só checar o estado de execução com

sudo systemctl status reducao

a saída esperada é algo do tipo

•reducao.service - myproject.service - A Flask application run with Gunicorn.
         Loaded: loaded (/etc/systemd/system/reducao.service; enabled; vendor preset: enabled)
         Active: active (running) since Thu 2023-10-19 17:38:56 -03; 18h ago
       Main PID: 78143 (gunicorn)
          Tasks: 190 (limit: 38364)
         Memory: 683.1M
            CPU: 2min 13.216s
         CGroup: /system.slice/reducao.service
                 ├─78143 /home/orahcio/.pyenv/versions/3.11.6/envs/reducao/bin/python3.11 /home/orahcio/.pyenv/versions/reducao/bin/gunicorn --workers 3 -->
                 ├─78203 /home/orahcio/.pyenv/versions/3.11.6/envs/reducao/bin/python3.11 /home/orahcio/.pyenv/versions/reducao/bin/gunicorn --workers 3 -->
                 ├─78235 /home/orahcio/.pyenv/versions/3.11.6/envs/reducao/bin/python3.11 /home/orahcio/.pyenv/versions/reducao/bin/gunicorn --workers 3 -->
                 └─78236 /home/orahcio/.pyenv/versions/3.11.6/envs/reducao/bin/python3.11 /home/orahcio/.pyenv/versions/reducao/bin/gunicorn --workers 3 -->

Com o serviço em funcionamento precisamos deixar ele vizível ao navegador, observe que um arquivo reducao.sock foi criado no diretório do projeto, esse arquivo é que será requisitado toda vez que o nginx precisar requisitar o serviço. Podemos agora configurar o nginx para isso.

Publicando o serviço para deixá-lo acessível na rede

Uma vez que temos o nginx instalado e habilitado como serviço vamos construir o arquivo de configuração do nosso aplicativo, o caminho para ele neste tutorial será /etc/nginx/sites-available/reducao.conf, o conteúdo dele deve conter

server {
        listen 8001;
 
        access_log /var/log/nginx/reducao.access.log;
        error_log /var/log/nginx/reducao.error.log;
 
        location / {
                include proxy_params;
                proxy_pass http://unix:/home/orahcio/reducao/reducao.sock;
        }
}

Veja que o bloco server possui a primeira linha a porta na qual o nginx irá escutar para acionar o serviço (listen 8001), escolhi a porta 8001 pois a padrão 80 não estava dando certo então resolvi mudar e acabou funcionando, aconcelho fazer a aplicação flask de forma a escutar essa porta. Os demais trechos do arquivo dizem respeito a arquivos de log e no subbloco location temos o arquivo reducao.sock o qual o nginx irá ficar requisitando quando você digitar o IP do servidor na rede. Se precisa de um tutorial para publicar aplicativo para expor ao público geral, esse não é o caso aqui.

Agora vamos ativar esse novo site no servidor nginx, primeiro vamos fazer um link simbólico no diretório dos sites habilitados, para isso faça

sudo ln -s /etc/nginx/sites-available/reducao.conf /etc/nginx/sites-enabled/reducao

agora podemos testar se as configurações do _nginx estão ok com o novo site

sudo nginx -t

se a saída foi essa

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

então podemos reiniciar o servidor nginx para que as modificações tenham efeito

sudo nginx -s reload

caso o serviço não tenha sido iniciado ainda é só fazer

sudo systemctl start nginx

Pronto agora é só ir para um computador na rede que possa enxergar o _IP_ do servidor que você acaba de configurar e digitar no endereço ipdamaquina:8001 e sua aplicação estará lá rodando.

Além dos arquivos de log discriminados em acess_log e error_logo é possível ver a saída padrão do terminal desse aplicativo usando o comando

sudo journalctl -u reducao