Após o desenvolvimento e testes de uma aplicação, é necessário torná-la disponível para o cliente final configurando o servidor. Essa etapa é denominada deployment e é a parte mais legal (só que não) de todo o processo: inúmeros bugs podem surgir e você não faz ideia o por quê não funciona.
Para tornar menos problemático o processo de deploy, devops propõe muitas coisas que podem ajudar como entrega contínua, versionamento de código, integração contínua, metodologias ágeis etc. É uma área realmente bacana de estudar.
Infelizmente devido ao curto prazo de entrega desta aplicação, não consegui brincar um pouco com Docker neste projeto mas facilitaria e muito.
Em Java este processo se resume em gerar o .war e configurar o Apache. Caso queira saber mais:
http://pt.stackoverflow.com/questions/58729/o-que-é-deploy
Para quem nunca desenvolveu além de aplicações acadêmicas, a grande pergunta é por quê simplesmente não executar:
$ python manage.py runserver
$ python app.py
Este "servidor" serve somente para desenvolvimento e testes locais, não é adequado para lidar com inúmeras requisições de usuários e não possui nenhuma confiabilidade de segurança.
Overview
- python 3.5.1
- django 1.10.0
- gunicorn
- nginx
Quando alguém enviar alguma requisição http (GET, POST, UPDATE etc.), o nginx é o responsável por dizer o que fazer com ela. Nos arquivos do Django, irá ter um arquivo urls.py que diz ao nginx qual código deverá ser executado de acordo com a path e código http recebido.
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
Para seja possível o nginx lidar com o Django, é necessário que o gunicorn faça a ponte entre os dois.
Ambiente virtual
É ideal isolar os frameworks usados com o virtualenv para evitar conflitos com outros projetos, ainda mais quando há Python 2.7 e Python 3.5 no mesmo sistema.
Para saber mais leia:
https://pythonhelp.wordpress.com/2012/10/17/virtualenv-ambientes-virtuais-para-desenvolvimento/
Configuração do servidor
Todo processo descrito pode e deve ser automatizado para evitar erros e agilizar o processo. Antes de tudo, não havia feito a configuração do DNS e por se tratar de uma aplicação de site pessoal que exigia atualização somente de imagens, javascript e HTML não foi necessário me preocupar com zero deployment downtime.
Lembre-se de setar o debug para falso antes de liberar para produção, qualquer erro será exibido para o usuário final e pode facilitar o pentest. Após a instalação do nginx, suba para verificar a mensagem default do nginx.
Provavelmente o diretório do projeto é algo como:
.
├── __init__.py
├── settings.py
├── static
│ ├── css
│ │ ├── bootstrap.css
│ │ ├── combo.css
│ │ ├── font-awesome.min.css
│ │ └── raleway.css
│ ├── fonts
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ ├── FuturaHeavy.ttf
│ │ ├── Futura_ICG.ttf
│ │ └── FuturaLight.ttf
│ ├── html
│ │ ├── footer.html
│ │ └── mainmenu.html
│ ├── img
│ │ ├── estrela.png
│ │ ├── joao-whitaker.jpg
│ │ ├── logo-branco.jpg
│ │ ├── logo-preto.jpg
│ └── js
│ ├── analytics.js
│ ├── angular.min.js
│ ├── bootstrap.min.js
│ ├── connectionfacebook.js
│ ├── jquery-2.1.1.min.js
│ └── w3data.js
├── templates
│ ├── colabore.html
│ ├── index.html
├── urls.py
└── wsgi.py
É essencial inserir o HTML, CSS e JS no diretório static e separar do backend. Edite o arquivo settings.py inserindo a path de static, setando DEBUG=False e adicionando os seus domínios em ALLOWED_HOSTS.
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = (os.path.join(BASE_DIR, "sfiles"), )
Crie um diretório no servidor em /var/www/seu_projeto, todo seu projeto django deve estar neste diretório. Após configurar o diretório de arquivos estátios, execute:
$ python manage.py collectstatic --digitar yes para confirmar
Crie o arquivo de script do gunicorn chamando gunicorn_start.sh. Não esqueça de editar.
#!/bin/bash
NAME="seu-projeto" #Name of the application (*)
DJANGODIR=/var/www/seu_projeto/my-website # Django project directory (*)
SOCKFILE=/var/www/seu_projeto/run/gunicorn.sock # we will communicate using this unix socket (*)
USER=ubuntu # the user to run as (*)
GROUP=webdata # the group to run as (*)
NUM_WORKERS=1 # how many worker processes should Gunicorn spawn (*)
DJANGO_SETTINGS_MODULE=seu_projeto.settings # which settings file should Django use (*)
DJANGO_WSGI_MODULE=seu_projeto.wsgi # WSGI module name (*)
echo "Starting $NAME as `whoami`"
# Activate the virtual environment
cd $DJANGODIR
source /var/www/seu_projeto/venv/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR
# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec /var/www/seu_projeto/venv/bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user $USER \
--bind=unix:$SOCKFILE
Dê permissão de executável para o script com chmod a+x.
Para configurar o nginx, basta editar o arquivo em /etc/nginx/nginx.conf. A seguinte configuração deveria seguir o padrão do Apache e deixar o nginx.conf somente para configurações de níveis gerais. Leia o artigo de Vitor Lobo sobre confgurações do nginx:
Desvendando o Nginx
http://blog.ti.lemaf.ufla.br/2016/07/29/desvendando-o-nginx-parte-1/
nginx.conf
upstream test_server {
server unix:/var/www/seu_projeto/run/gunicorn.sock fail_timeout=10s;
}
# This is not neccessary - it's just commonly used
# it just redirects example.com -> www.example.com
# so it isn't treated as two separate websites
server {
listen 80;
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
}
server {
listen 80;
server_name www.example.com;
client_max_body_size 4G;
access_log /var/www/seu_projeto/logs/nginx-access.log;
error_log /var/www/seu_projeto/logs/nginx-error.log warn;
location /static/ {
autoindex on;
alias /var/www/seu_projeto/seu-projeto/static/;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://test_server;
break;
}
}
#For favicon
location /favicon.ico {
alias /var/www/seu_projeto/seu-projeto/static/img/favicon.ico;
}
#For robots.txt
location /robots.txt {
alias /var/www/seu_projeto/seu-projeto/static/robots.txt ;
}
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /var/www/seu_projeto/seu-projeto/static/;
}
}
No meu caso, tive muitos problemas com o conteúdo que estava dentro de /static como css e js. Não era redirecionado cada um para a respectiva pasta e tive que inserir manualmente a path inteira:
location /static/css/ {
include /etc/nginx/mime.types;
alias /var/www/seu_projeto/seu-projeto/static/css/;
}
location /static/js/ {
include /etc/nginx/mime.types;
alias /var/www/seu_projeto/seu-projeto/static/js/;
}
Agora basta subir novamente o servidor e executar o gunicorn.
$ pwd
/var/www/seu_projeto/
$ sudo service nginx start
$ ./gunicorn_start.sh
As únicas alterações do projeto eram em /static então o processo se resumia em git pull, cp -a /static para /var/www/seu_projeto e python manage.py collecstatic para inserir novas atualizações. Lembre-se de automatizar todo seu processo e melhorar os scripts descritos, há vários artigos gratuitos da ThoughtWorks sobre como melhorar o processo de deploy.
E claro, mantenha a calma se algo der errado.
Referências
Esse post teve como objetivo ser útil e rápido e por isso, utilizei as etapas essenciais do seguinte artigo. Os scripts são de autoria de seu autor.
http://tutos.readthedocs.io/en/latest/source/ndg.html
Kickstarting Flask on Ubuntu - Setup and Deployment
https://realpython.com/blog/python/kickstarting-flask-on-ubuntu-setup-and-deployment/
WSGI Servers
https://www.fullstackpython.com/wsgi-servers.html
Deploying nginx + django + python 3
http://tutos.readthedocs.io/en/latest/source/ndg.html