En varias ocasiones ha sido abordado el tema de la publicación de aplicaciones Django utlizando diferentes tecnologías. Hoy les mostraré cómo hacerlo utilizando Nginx, Gunicorn, Gevent y el temido SystemD. Trataré no determe mucho en aspectos que ya han sido tocados en post anteriores para concentrarme en lo que considero novedoso.

Dependencias

Como en todo despligue lo primero que debemos hacer es instalar las dependencias de nuestra web app, ya sea en el sistema o en un entorno virtual. También debemos instalarnos Nginx, Gunicorn, Gevent y SystemD si no viene por defecto en tu CentOS o Debian, cosa que dudo mucho :).

yum -y install nginx python-gunicorn python-gevent # RHEL-like
apt-get install nginx gunicorn python-gevent # Debian-like

Gunicorn

Una vez instaladas las tecnologías que vamos a emplear en el despliegue procedemos a configurar cada una de ellas. Empezaremos por gunicorn así que lo primero será crearnos un archivo de configuración. Este debe ser un archivo python válido (/path/to/mywebapp/mywebapp.conf.py) y dentro colocamos las siguientes configuraciones.

from multiprocessing import cpu_count


# Server Socket
bind = "unix:/var/run/mywebapp.sock" # Este tipo de sockets es más rápido que 
# utilizando la interfaz local. Por lo que si el Nginx y el Gunicorn están en 
# el mismo servidor es el que les recomiendo.
backlog=2048

# Worker Processes
workers = cpu_count() * 2 + 1 # Valor recomendado por la doc oficial.
worker_class='gevent' # Le decimos que utilice gevent para un mejor rendimiento.
worker_connections=1000
max_requests=0
keepalive=5
timeout=30

# Security
limit_request_line=4096
limit_request_fields=100
limit_request_fields=8190

# Server Mechanics
pidfile='/var/run/mywebapp.pid'
user='mywebapp'
group='nginx'

# Logging
loglevel='error'
accesslog='/var/log/gunicorn/mywebapp.access.log'
errorlog='/var/log/gunicorn/mywebapp.error.log'

# Process Naming
proc_name = 'mywebapp'

Si se fijan en el archivo de configuración el usuario para la ejecución no es nginx porque se quiere un despliegue con mayor seguridad y conviene utilizar un usuario especifico para cada webapp. El Nginx solo necesita poder leer el socket unix, los ficheros estáticos de nuestra webapp y nada más. Si fuese un despliegue distribuido tanto el usuario como el grupo podrían ser específicos para cada webapp. Para más detalles consultar la documentación oficial de gunicorn. Por otra parte es válido decirles que deben asegurarse que la carpeta /var/log/gunicorn exista, si no exisiste la creamos porque el gunicorn no la crea automáticamente y eso puede abortar el inicio de nuestra webapp.

mkdir -p /var/log/gunicorn
# Para que gunicorn (que se ejecuta con el usuario mywebapp) pueda escribir logs.
chown -R mywebapp /var/log/gunicorn

SystemD

Ahora pasamos a crear un servicio con el diabólico SystemD para que el gunicorn ejecute nuestra aplicación automáticamente al inicio y de paso nos permita detenerla o reiniciarla según necesitemos. Primero creamos un script de inicio para nuestra webapp para nuestra comodidad en /path/to/mywebapp/mywebapp.sh con el siguiente contenido.

#!/bin/bash
cd /path/to/mywebapp && /usr/bin/gunicorn --config mywebapp.conf.py \
mywebapp.wsgi:application
exit $?

Nota: Si el despliegue se hace utilizando un entorno virtual se debe incluir en el script lo referente al mismo. Fíjense cómo lo hace Osiel en su artículo. Siguiendo el hilo del script de inicio anterior debería quedarnos más menos así (puede ser mejorable como todo):

#!/bin/bash
cd /path/to/mywebapp
VENV=/path/to/myvirtualenv
# Activando el entorno virtual
cd $VENV
source ../bin/activate
export DJANGO_SETTINGS_MODULE=mywebapp.settings
export PYTHONPATH=$VENV:$PYTHONPATH
/usr/bin/gunicorn --config mywebapp.conf.py \
mywebapp.wsgi:application
exit $?

Le damos permisos de ejecución con:

chmod +x /path/to/mywebapp/mywebapp.sh

Terminado lo anterior pasamos a crear el archivo que describirá el nuevo servicio de SystemD. Para ello creamos el fichero /lib/systemd/system/mywebapp.service y dentro agregamos lo siguiente:

[Unit]
Description=My Web App Daemon
After=network.target

[Service]
PIDFile=/var/run/mywebapp.pid
WorkingDirectory=/path/to/mywebapp
ExecStart=/path/to/mywebapp/mywebapp.sh
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Acto seguido ya podemos iniciar nuestra webapp utilizando el clásico service mywebapp start o con systemctl start mywebapp pero, no la podríamos ver porque está publicada usando un socket unix. Necesitamos configurar el Nginx para que lo lea y nos muestre la aplicación pero antes debemos habilitar el servicio para que sea capaz de iniciar de forma automática cada vez que inicie el sistema. Para ello ejecutamos systemctl enable mywebapp y terminamos con SystemD.

Nginx

Como somos buenos profesionales y nos importa tanto la seguridad lo menos que podemos hacer es publicar la webapp utilizando ssl por lo que, lo primero será generarnos un certificado propio sino no tenemos uno adecuado. Esto lo hacemos ejecutando:

openssl req -x509 -sha512 -nodes -days 365 -newkey rsa:8192 \
-keyout /etc/nginx/ssl/mywebapp.example.com.key \
-out /etc/nginx/ssl/mywebapp.example.com.crt

Llenamos los datos que nos pide el comando anterior y al terminar ya tenemos el certificado para nuestra webapp y estamos listos para pasar a configurar nuestro virtual host para Nginx. Si tu distro es RHEL-like creamos el fichero en /etc/nginx/conf.d/mywebapp.conf, si es Debian-like en /etc/nginx/sites-available/mywebapp.conf y dentro colocamos las siguientes configuraciones:

upstream mywebapp{
    server unix:/var/run/mywebapp.sock fail_timeout=0;
}

server {
    listen 443 ssl;
    server_name mywebapp.example.com;
    ssl_certificate /etc/nginx/ssl/mywebapp.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/mywebapp.example.com.key;
    root /path/to/mywebapp;
 
    access_log /var/log/nginx/mywebapp.access.log;
    error_log /var/log/nginx/mywebapp.error.log warn;
 
    location /static/ {
        alias /path/to/mywebapp/static/;
    }
 
    location / {
        proxy_pass http://mywebapp; # Nombre del upstream definido al inicio.
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Si estamos en una distro Debian-like, debemos crear un enlace simbólico en /etc/nginx/sites-enabled, esto lo logramos con el siguiente comando:

ln -s /etc/nginx/sites-available/mywebapp.conf /etc/nginx/sites-enabled/mywebapp.conf

En este punto nos damos cuenta que el virtual host estará escuchando conexiones por el 443 pero ¿qué pasa si alguien intenta conectarse por el puerto 80? Nada, que no podrá ver la aplicación. Es por eso que abrimos el fichero /etc/nginx/nginx.conf y dentro de la directiva http creamos un virtual host por defecto que lo único que contendrá será lo siguiente:

server_tokens off; # Ocultamos la versión de Nginx.
server {
    listen 80 default_server;
    return 301 https://$host$request_uri; # Redireccionamos al 443. 
}

De esta forma si publicamos todas las aplicaciones por ssl el que vaya a entrar por el 80 será redireccionado automáticamente.

Lo siguiente es que el Nginx cargue las configuraciones. Para esto debemos ejecutar:

service nginx reload # o si lo prefieren
service nginx restart

Y para terminar, si nuestro server es el búnker que todos soñamos para dormir en paz. Debemos decirle al firewall que admita conexiones entrantes por los puertos 80 y 443. Esto lo logramos ejecutando:

iptables -A INPUT -m state --state NEW -p tcp -m multiport --dports 80,443 -j ACCEPT

Y listo!!! Ya con esto hemos hecho un despliegue decente. Espero que les sea de utilidad y que consulten la documentación oficial de las herramientas y tecnologías usadas en este tutorial para que tengan un dominio más amplio de las mismas. No olviden comentar. Hasta la próxima!!!