Hospedando Django + Celery + RabbitMQ en AWS -- Parte 1

Una de las necesidades más comunes en el desarrollo de aplicaciones son las tareas asíncronas como cuando necesitamos que se envíen correos electrónicos sin hacer esperar a nuestro usuario en "background", pero también necesitamos tareas periódicas que se repiten y se ejecutan automáticamente sin interacción del usuario como cuando se comparen fechas y se notifica o que a cierto horario de cierto día se ejecute una tarea en nuestra app, o el respaldo o purga de datos de forma automática.

Una forma de resolver esto es con nuestro backend y en este caso Django y Celery, Celery es una librería que nos permite crear tareas que se ejecuten de forma asíncrona.

En esta primera parte vamos a instalar celery y rabbitMQ (el cual es nuestro broker) en una instancia EC2 de Amazon Web Services con Ubuntu 14.

Paso 1: Instalación

Entramos a nuestra instancia de AWS con ssh, y una vez ahi instalamos las herramientas que necesitamos:

Primero actualizamos nuestro sistema.

$ sudo apt-get update
$ sudo apt-get upgrade

Ahora instalamos rabbitMQ

$ sudo apt-get install rabbitmq-server

Ahora instalamos virtualenv para crear un entorno virtual para nuestra app e instalar las dependencias necesarias para Django.

$ sudo apt-get install python-virtualenv

Inmediatamente creamos nuestro entorno virtual en la carpeta home del usuario ubuntu, en nuestro caso queremos usar python3 y le llamaremos env (~ ahi mismo donde entramos al conectarnos por ssh)

$ virtualenv env -p python3

y lo activamos con el siguiente comando:

$ source ./env/bin/activate

Y debería verse algo así (con tu propia ip claro):

$(env)ubuntu@ip-172-30-12-45:~$

Si nuestro entorno virtual ya está activado es hora de instalar celery con pip. De paso instalaremos Django para correr nuestro ejemplo

$ pip install celery=3.1.18
$ pip install django==1.11

Asegúrate de instalar las mismas versiones para obtener el mismo resultado ;)

Paso 2: Creamos un DjangoProject

Ya instalamos django ahora vamos a crear un projecto rápido solo para poder ver funcionar celery.

$ django-admin startproject ejemplo

se creará una carpeta llamada "ejemplo" la cual contiene nuestro projecto de Django, trabajaremos más con nuestro proyecto en la segunda parte de nuestro tutorial.

Paso 3: Demonios (daemons)

demonio

Y no precisamente de esos que te inducen a ver porno, más bien de los que nos levantan procesos en segundo plano en Linux ;)

RabbitMQ ya funciona en segundo plano desde que se instaló ahora Celery debe funcionar en segundo plano con su respectivo daemon.

Vamos a crear un archivo llamado celery.conf dentro de /etc/init/ con nano.

 $ sudo nano /etc/init/celery.conf

y vamos a escribir los siguiente:

description "Mi primer daemon para celery"

start on runlevel [2345]  
stop on runlevel [!2345]

respawn  
setuid ubuntu  
setgid ubuntu  
chdir /home/ubuntu/ejemplo

exec /home/ubuntu/env/bin/celery -A ejemplo worker  

Es importante notar que "ejemplo" es el nombre de tu aplicación de django

Ya escribimos nuestro daemon es hora de correrlo:

$ sudo service celery start

Recuerden que lo llamamos celery.conf por ello es que solo llamamos celery al servicio.

La forma más rapida de saber si nuestro demonio está funcionando es reiniciando el servicio:

$ sudo service celery restart

si nos aparece algo así:

celery stop/waiting  
celery start/running, process 15378  

Todo está bien, pero si nos aparece algo así:

stop: Unknown instance:  
celery start/running, process 15573  

Significa que algo está mál en nuestro archivo celery.conf o el nombre de nuestro proyecto de django no coincide.

cual es la diferencia? estamos pidiendo un reinicio lo primero que intenta es apagar porque supone que está iniciado y despues lo inicia, cuando nos aparece "stop: Unknown instance:" significa que no estaba corriendo y si esto aparece cada que reiniciamos, podemos deducir que nunca logra iniciar.

Para poder usar tareas periodicas automáticas tambien necesitamos crear un daemon para Celery Beat con el nombre de celery_beat.conf dentro de /etc/init/ de esta forma:

$ sudo nano /etc/init/celery_beat.conf

y escribimos:

description "Daemon para el beat de tareas periodicas"

start on runlevel [2345]  
stop on runlevel [!2345]

respawn  
setuid ubuntu  
setgid ubuntu  
chdir /home/ubuntu/ejemplo

exec /home/ubuntu/env/bin/celery -A ejemplo beat  

guardamos con ctrl + x

y comprobamos de igual manera con

$ sudo service celery_beat restart

2 veces para ver que si se detiene.

Estamos listos para configurar Celery y crear nuestra primer tarea periódica.

Paso 4: Periodic task

Ya creamos nuestro proyecto de Django, ahora vamos a crear un app donde vamos a codear nuestra tarea periódica. y configurar celery para que inicie junto con nuestra app.

Primero creamos nuestra app en Django, la llamaremos main

 $ ./manage.py startapp main

Agregamos el app al archivo settings en INSTALLED_APPS:

INSTALLED_APPS = [  
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'main',
]

Ahora vamos a crear un archivo en el mismo nivel que settings.py dentro de la carpeta ejemplo:

$ sudo nano ejemplo/ejemplo/celery.py
import os  
from celery import Celery  
from django.conf import settings

# seteamos el modulo de settings de django para celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ejemplo.settings")

app = Celery("ejemplo")

app.config_from_object('django.conf:settings')  
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Nota que el nombre del app es ejemplo, y que al final configuramos autodiscover_tasks lo cual nos va a permitir colocar tasks en cualquiera de nuestras apps de todo el proyecto y que las localize automaticamente.

Ahora lo unico que falta es colocar en el archivo __init__.py que se encuentra en el mismo nivel que settings.py la siguiente linea:

from .celery import app as celery_app  

Esto va a permitir que Celery funcione correctamente.

Es hora de crear la tarea:

Creamos un archivo nuevo dentro de nuestra app "main" llamado tasks.py de la siguiente manera:

$ sudo nano ejemplo/main

y escribimos:

from celery.task.schedules import crontab  
from celery.decorators import periodic_task  
x  
@periodic_task(run_every=(crontab()), name="test", ignore_result=True)
def print_algo():  
    print("Perro testeado!")

continuara...