Deploy de Django con Circus, Chaussette y Nginx
Aunque hay un pequeño mito al respecto, poner en producción una aplicación web hecha en Python no es tan complejo. Esa facilidad se debe a la estandarización de la pasarela WSGI, que define cómo se comunica (o se debería comunicar) una "app" (sea hecha con un framework o no) con un servidor web.
Si bien Nginx, el servidor web que está desplazando a Apache como el más popular tiene un módulo que implementa el estándar WSGI de manera nativa, la arquitectura más típica es utilizarlo como proxy reverso, conectado a un servidor WSGI especializado como Gunicorn que interactua con la aplicación web (posiblemente a través de multiples instancias o workers). Como queremos que nuestra app funcione permanentemente, el proceso WSGI y otros que sean necesarios (por ejemplo Redis) se demonizan de manera que sepan restaurarse automáticamente si mueren y sea posible monitorearlos: para eso suele usarse supervisor.
Si bien esta arquitectura está bastamente probada, hay una opción mejor.
El circo y el soquete
Circus y Chaussette son proyectos desarrollados por Tarek Ziadé y el equipo de Mozilla Sevices.
Una arquitectura de producción análoga a la descripta arriba, pero basada en Circus, se ve así:
Circus maneja procesos demonizados igual que Supervisor, pero además puede bindear sockets y manejarlos de la misma manera. Este desacople de la gestión de sockets del webserver WSGI permite muchas posibilidades, tanto en la gestión como en la escalabilidad de la arquitectura.
La capa WSGI en este esquema la aporta Chaussette, que tiene la particularidad que, en vez de abrir un socket nuevo, utiliza el que Circus abrió (y controla) para el worker. Además, aunque trae una implementación de WSGI built-in, puede usar muchos backends más eficientes o con características particulares como meinheld, gevent, gevent-socketio, entre muchos otros.
A diferencia de Supervisor que se basa en el protocolo XML-RPC para inspeccionar los procesos que controla, Circus utiliza un canal pub/sub basado en el mucho más moderno ZeroMQ (lo mismo que usa IPython Notebook) que permite un monitoreo realtime y una API de plugins mucho más simple. Otra diferencia, nada menor, es que Circus funciona con Python 2 o 3 (supervisor no es compatible con Python 3).
Y de yapa: Circus se puede usar como una biblioteca de muy alto nivel para la gestión no bloqueante de procesos. Se puede pensar con un wrapper de subprocess
y/o multiprocess
, que aporta información de monitoreo y estadísticas, control de flujo, una capa de señales (hooks) muy completa y más.
Desplegando Django
Para ejemplificar, voy utilizar un proyecto Django que estoy desarrollando (muy lentamente): nikolahub.
Circus se configura con un archivo con formato .ini
. El mio, que bauticé circus.ini
quedó así:
[circus] check_delay = 5 endpoint = tcp://127.0.0.1:5555 pubsub_endpoint = tcp://127.0.0.1:5556 stats_endpoint = tcp://127.0.0.1:5557 [socket:nikolahub] host = 127.0.0.1 port = 8080 [watcher:nikolahub] cmd = /virtualenvs/nikolahub/bin/chaussette --fd $(circus.sockets.nikolahub) nikolahub.wsgi.application use_sockets = True numprocesses = 3 [env:nikolahub] PYTHONPATH = /projects/nikolahub
La sección watcher
indica lanza el comando a controlar, en este caso levantando 3 workers de la aplicación -django. Notar que como tengo instalado Chaussette dentro del virtualenv, uso el path absoluto al ejecutable. El fragmento --fd $(circus.sockets.nikolahub)
se expande implícitamente asignando el pid que obtuvo el fork (el proceso hijo) de circus.
Si quisieramos usar otro servidor web, sólo hay que indicar cual con el parámetro --backend
Por ejemplo:
cmd = /virtualenvs/nikolahub/bin/chaussette --backend gevent --fd $(circus.sockets.nikolahub) nikolahub.wsgi.application
Podemos probar si todo funciona:
(nikolahub)tin@morochita:$ circusd circus.ini 2014-06-12 04:36:16 circus[1141] [INFO] Starting master on pid 1141 2014-06-12 04:36:16 circus[1141] [INFO] sockets started 2014-06-12 04:36:16 circus[1141] [INFO] Arbiter now waiting for commands 2014-06-12 04:36:16 circus[1141] [INFO] nikolahub started 2014-06-12 04:36:16 circus[1141] [INFO] circusd-stats started 2014-06-12 04:36:17 circus[1150] [INFO] Starting the stats streamer 2014-06-12 04:36:17 [1149] [INFO] Application is <django.core.handlers.wsgi.WSGIHandler object at 0xa06f60c> 2014-06-12 04:36:17 [1149] [INFO] Serving on fd://5 2014-06-12 04:36:17 [1149] [INFO] Using <class chaussette.backend._wsgiref.ChaussetteServer at 0x9f2d6ec> as a backend 2014-06-12 04:36:17 [1148] [INFO] Application is <django.core.handlers.wsgi.WSGIHandler object at 0x939b60c> 2014-06-12 04:36:17 [1148] [INFO] Serving on fd://5 2014-06-12 04:36:17 [1148] [INFO] Using <class chaussette.backend._wsgiref.ChaussetteServer at 0x92596ec> as a backend
Tendremos la aplicación servida en el puerto 8080 de localhost. Demonizarlo es sólo un flag:
Para implementar nginx como proxy reverso armé un archivo nginx.conf
:
server { listen 80; server_name nikolahub.nqnwebs.com; location /static/ { alias /projects/nikolahub/static/; } location /media/ { alias /projects/nikolahub/media/; } location / { proxy_pass http://localhost:8080/; proxy_pass_header Server; proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; } }
Luego agregamos el sitio:
$ ln -s /etc/nginx/sites-available/nikolahub nginx.conf $ ln -s /etc/nginx/sites-enable/nikolahub nginx.conf $ sudo service nginx restart
Esto pone a nginx como "frontend" de la aplicación, sirviendo los directorios con contenido estático y pasando el resto de las peticiones al puerto 8080 que administra Circus.
Ya tenemos nuestro sitio en producción.
El dueño del circo y los monitos
De ahora en más, podremos usar las herramientas que provee Circus.
circusctl
es el dueño del circo. Puede parar, reiniciar, cambiar la cantidad de workers, abrir una consola ipython para interactuar o inspeccionar y mucho mas. Se puede usar como subcomandos (circusctl <subcmd> <watcher>
) o usar la consola interactiva.
Por ejemplo, si quisiera ver cuantos procesos workers tengo y agregar uno más, podría hacer así:
(nikolahub)tin@morochita:$ circusctl numprocesses nikolahub 3 (nikolahub)tin@morochita:$ circusctl incr nikolahub ok (nikolahub)tin@morochita:$ circusctl numprocesses nikolahub 4
Lo mismo y más se puede hacer desde una consola IPython.
(nikolahub)tin@morochita:$ circusctl ipython Python 2.7.4 (default, Apr 19 2013, 18:32:33) Type "copyright", "credits" or "license" for more information. IPython 2.1.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: arbiter.numprocesses() Out[1]: 4
circus-top
es un monitor realtime, estilo top. Escucha las estadísticas
que produce circusd-stats
.
Todo esto puede verse y manejarse cómodamente a través de circus-web, un dashboard web que permite monitorear y administrar circus, con gráficos realtime y muy fácil de usar.
Desde las últimas versiones, circus-web
se refactorizó para basarla en Tornado (originalmente usaba bottle) y hay que instalarlo aparte.
Conclusiones
Circus es una herramienta que simplifica el stack de deployment de una aplicación web WSGI. La API de alto nivel, una arquitectura mucho más moderna y simple y el aval de ser desarrollada (y usada exhaustivamente) por Mozilla, son avales poderosos para probarla.
Como escribió el Marcos Luc "la función ya debería empezar (...) Bueno nena, buena suerte, cada vez la red te teme más..."
Comentarios
Comments powered by Disqus