quarta-feira, 30 de maio de 2012

Como configurar o django-celery

Aqui vou explicar como instalar o django-celery, seus requisitos e como configurá-lo. Pois, para variar, foi necessário consultar uma infinidade de fontes para aproveitar uma linha ou duas de cada uma.  Sim, isso torra o ** é chato demais e perdemos muito tempo. =/
Comecemos.

O que é o django-celery?

O django-celery é um pacote para o django para fornecer ao django as funcionalidades do Celery. O qual se trata de uma ferramenta distribuída para gerenciamento de tarefas.
Retirado do site do Celery:
O Celery é uma ferramenta para gerenciamento de fila de tarefas assíncronas baseado em passagem de mensagens distribuídas. Seu foco é em operações em tempo real, mas também suporta agendamento de tarefas.
Suas unidades de execução, chamadas de "tarefas", são executadas concorrentemente em um ou mais servidores usando multiprocessamento, Eventlet, ou gevent. As tarefas podem ser executadas de forma assíncrona (no plano de fundo) ou de forma síncrona (esperar até que esteja pronta).

Celery é escrito em Python, mas seus protocolos podem ser implementados em qualquer linguagem. Ele também pode trabalhar com outras linguagens usando "webhooks". Sem contar que há o RCelery feito para Ruby e funciona como cliente PHP.
O gerenciador de mensagens padrão/recomendado é o RabbitMQ, mas fornece suporte para Redis, MongoDB, Beanstalk, Amazon SQS, CouchDB e bancos de dados (usa SQLAlchemy ou o Django ORM).
Em miúdos: é um anacron com ferramentas para trabalho distribuído e integrável a várias ferramentas.



Por que o django-celery?

Ao pesquisar pela net, encontrei algumas opções como o cuedjango-async e o django-tasks, que aparentam ser mais simples de instalar, configurar e utilizar. Porém, o cue somente funciona como um gerenciamento de filas de tarefas, sem agendamento; o django-tasks é feito focado para tarefas grandes e pesadas que ocorrem poucas vezes; o django-async é bem recente e ainda apresenta várias falhas, além de pouquíssima documentação. Portanto, se quisermos agendar tarefas de forma integrada com o django, por enquanto, não temos muitas opções além de usar o django-celery. Ao menos, eu não encontrei, se conhecer alguma opção melhor, por favor avise! ;)

Como instalá-lo?

Eu uso xubuntu (Ubuntu 10.04.3 LTS) e foi nele que configurei o django-celery. Para que ele funcione, você terá que instalar um gerenciador de mensagens (usei o padrão, o RabbitMQ), o django-kombu (um pacote para armazenar as mensagens do gerenciador em banco de dados, pode ser qualquer outro) e as dependências do pacote django-celery. Para isso:

sudo easy_install Celery
sudo easy_install django-kombu
sudo apt-get install rabbitmq-server

Feito isso, teremos que configurar o RabbitMQ. Simplesmente criar um usuário e senha e um host virtual nele. É só utilizar os seguintes comandos:

sudo rabbitmqctl add_vhost teste
sudo rabbitmqctl add_user usuario senha
sudo rabbitmqctl set_permissions -p teste usuario ".*" ".*" ".*"

Esses comandos vão, respectivamente, criar o host virtual teste, adicionar o usuário usuario com senha senha e dar todas as permissões as permissões para ele no host virtual teste.

Pronto! Temos tudo instalado e funcionando. Só falta configurar o django e criar as tarefas.
Caso vá usar isso em mais de um projeto, crie um host virtual para cada projeto por questões de organização.

Como configurar o django-celery?

Até agora só instalamos e configuramos os pacotes externos. E já que temos todos funcionando, podemos finalmente configurar o django para usar o django-celery.
Abra o settings.py de seu projeto django e acrescente este código ao final dele:
INSTALLED_APPS += (
    'djcelery',
    'kombu.transport.django',
)

#django celery: task scheduling package
BROKER_HOST = "localhost"
BROKER_PORT = 5672
BROKER_VHOST = "teste"
BROKER_USER = "usuario"
BROKER_PASSWORD = "senha"
BROKER_BACKEND = "djkombu.transport.DatabaseTransport"

CELERY_RESULT_BACKEND = "amqp"
CELERY_IMPORTS = ("meuprojeto.aplicativo.tasks", )
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

import djcelery
djcelery.setup_loader()
#django celery: task scheduling package

Não se esqueça de adaptar os dados de acesso ao RabbitQM (nome de usuário, senha e o nome do host virtual) e os imports do celery (CELERY_IMPORTS)!

Depois de tanto trabalho temos a integração quase funcionando. Só resta criar as tarefas.
No código acima, note o valor de CELERY_IMPORTS. Ele funciona similar ao INSTALLED_APPS do django, então é só apontar por ele quais são os arquivos no seu projeto que armazenam as tarefas.

Caso você esteja usando WSGI, acrescente este código ao seu arquivo de configurações .wsgi (no meu caso era config.wsgi):
os.environ["CELERY_LOADER"] = "django"


Sim. Agora é para tudo estar funcionando sem problemas.
Para testar é só rodar o comando:
python manage.py celeryd -E -B -lINFO

Caso ocorra algum erro, execute no modo de debug para ver o que está acontecendo e poder procurar ajuda mais fácil:
python manage.py celeryd -E -B -lDEBUG

Agora que está tudo funcionando, ainda temos um problema: iniciar o celery junto com o django. Infelizmente, o django ainda não tem nenhuma ferramenta para lançar um sinal ou nada do gênero quando inicia. Já existe um ticket sobre o assunto, mas pelo jeito vai demorar até ele ser resolvido, pois há diversas questões envolvidas. Enfim. Sem uma solução pronta no framework, vamos a caça.
Depois de um bom tanto de pesquisas. Elaborei um código em Python para iniciar o django-celery:
import os
import time
from subprocess import call

#recupera o caminho para o arquivo manage.py
AUX = os.path.abspath( os.path.join( os.path.dirname(os.path.abspath(__file__)), os.path.pardir ) )
MANAGE = os.path.join(AUX, 'manage.py')

#varre os processos em busca de alguma instancia do django-celery sendo executado pelo meu projeto
saida = os.popen("ps -ef | grep -i 'seuprojeto/manage.py celeryd' | grep -v grep | awk '{print $2}'").read()

#se a saida for vazia, executa o comando para iniciar o django-celery
if len( str(saida) ) == 0:
    time.sleep(5) #em caso de erro, a geracao de processos fica mais demorada
    call("python "+MANAGE+" celeryd -E -B -lINFO &", shell=True)

Esse script funcionou bem para mim. Note que você precisa mudar a palavra seuprojeto para o diretório correto onde está seu projeto. Um erro aí e garanto que você vai ter uma enxurrada de processos "iguais". Mesmo com o sleep que coloquei ali para retardar isso.
Pesquisei um bocado, pois achei que haveria meios de configurá-lo como um daemon puro, mas não dá. Infelizmente, para executar o django-celery, temos que chamá-lo via manage.py. Embora seja possível fazer isso com o celery.
Para resolver isso, adaptei e coloquei esse código de inicialização na aplicação async_tasks e criei um script em python em /etc/init.d/ chamado djcelery com o seguinte conteúdo:
python /home/jayme/projetos/projeto_django/async_tasks/start_djcelery.py
Depois dei permissão de execução para ele:
chmod +x /etc/init.d/djcelery


E pronto! Temos o django-celery rodando. Caso você reinicie o sistema, ele vai ser iniciado de novo como um daemon. ;)
Em caso de emergências, use este script para matar todas as instâncias do celery:
sudo kill  `ps -ef | grep -i 'projeto_django/manage.py celeryd' | grep -v grep | awk '{print $2}'`

Caso você tenha a intenção de usar o celery puro, no meio das pesquisas aprendi como configurá-lo. Montei um tópico só para isso no final deste artigo.

Como criar tarefas periódicas no django-celery

Bem, agora que já temos o dito cujo funcionando. Precisamos criar as tarefas para ele executar.
No começo do artigo, falamos de como configurar o settings.py e que a variável CELERY_IMPORTS armazena seus arquivos de tarefas: tasks.py. É dentro desses arquivos que você deve colocar as tarefas para o django-celery. Podem ter qualquer nome, mas por questões de manter um padrão, melhor deixar assim.
Aqui temos alguns exemplos extraídos da documentação oficial:
from celery.task import tasks, PeriodicTask
from celery.schedules import crontab
from datetime import timedelta


class ACadaTrintaSegundos(PeriodicTask):
     run_every = timedelta(seconds=30)

     def run(self, **kwargs):
         logger = self.get_logger(**kwargs)
         logger.info("Executa a cada 30 segundos")

class TodaManhaDeSegunda(PeriodicTask):
     run_every = crontab(hour=7, minute=30, day_of_week=1)

     def run(self, **kwargs):
         logger = self.get_logger(**kwargs)
         logger.info("Executa toda segunda as 7h30 da manha")

class TodaManha(PeriodicTask):
     run_every = crontab(hours=7, minute=30)

     def run(self, **kwargs):
         logger = self.get_logger(**kwargs)
         logger.info("Executa todo dia as 7h30 da manha")


#note a diferenca entre o funcionamento de crontab e de timedelta
class ACadaQuinzeMinutosHora(PeriodicTask):
     run_every = crontab(minute=15)

     def run(self, **kwargs):
         logger = self.get_logger(**kwargs)
         logger.info("Executa nos 15 minutos de cada hora (1h15, 2h15, etc)")
  
class ACadaQuinzeMinutos(PeriodicTask):
     run_every = timedelta(minutes=15)

     def run(self, **kwargs):
         logger = self.get_logger(**kwargs)
         logger.info("Executa a cada 15 minutos")

Note que para agendar uma tarefa para uma data/horário específico se usa o crontab, enquanto que para uma tarefa que se repete em dado intervalo de tempo se usa o timedelta.
Logo vamos colocar mais exemplos aqui. Ficaríamos muito gratos se puderem ajudar. ;)

Montadas as suas tasks, é só iniciar o django-celery e ver o sistema executando as coisas nos horários esperados (aproximadamente, ele ainda não é tão exato assim).

Como instalar o daemon do celery

Há um tutorial bem simplista aqui feito baseado na documentação oficial. Mas como o tutorial está simplista demais e a idéia dos meus posts é fazer as coisas de forma simplificada. Pois serve inclusive pra eu mesmo consultar depois quando preciso fazer de novo. Convenhamos, não dá pra guardar todas essas coisas e passos de cabeça nos mínimos detalhes como é necessário. Enfim!

Para começar, temos que baixar os scripts para criar os daemons. São dois arquivos necessários para os daemons: celerybeat (para tarefas periódicas) e celeryd (para tarfeas comuns). É só clicar nos nomes e baixá-los. Preferi salvá-los na pasta do meu aplicativo django async_tasks para poder adicionar ao repositório e manter junto ao projeto. Depois, pretendia fazer isso com links simbólicos mas não funciona muito bem, crie, como admin, dois scripts em /etc/init.d/ para chamar os seus arquivos de daemon:
echo `sh /home/jayme/projetos/projeto_django/async_tasks/celeryd $1` > /etc/init.d/celeryd
echo `sh /home/jayme/projetos/projeto_django/async_tasks/celerybeat $1` > /etc/init.d/celerybeat
chmod 700 /etc/init.d/celery*
Obs: os daemons, normalmente, só funcionam corretamente se executados com privilégios de admin, então nem vale a pena dar quaisquer permissões para usuários comuns acessá-los.

Agora precisamos criar os arquivos de configuração. No meu caso, os criei dentro da minha aplicação async_tasks como celerydconf e celerybeatconf.
celerydconf:
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
DIR="$(dirname "$DIR")"

# Name of nodes to start, here we have a single node
CELERYD_NODES="w1"
# or we could have three nodes:
#CELERYD_NODES="w1 w2 w3"

# Where to chdir at start.
CELERYD_CHDIR=DIR

# How to call "manage.py celeryd_multi"
CELERYD_MULTI="$CELERYD_CHDIR/manage.py celeryd_multi"

# How to call "manage.py celeryctl"
CELERYCTL="$CELERYD_CHDIR/manage.py celeryctl"

# Extra arguments to celeryd
CELERYD_OPTS="--time-limit=300 --concurrency=8"

# Name of the celery config module.
CELERY_CONFIG_MODULE="celeryconfig"

# %n will be replaced with the nodename.
CELERYD_LOG_FILE="/var/log/celery/%n.log"
CELERYD_PID_FILE="/var/run/celery/%n.pid"

# Workers should run as an unprivileged user.
CELERYD_USER="celery"
CELERYD_GROUP="celery"

# Name of the projects settings module.
export DJANGO_SETTINGS_MODULE="settings"

E o celerybeatconf:
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
DIR="$(dirname "$DIR")"

# Where the Django project is.
CELERYBEAT_CHDIR=DIR

# Name of the projects settings module.
export DJANGO_SETTINGS_MODULE="settings"

# Path to celerybeat
CELERYBEAT="$CELERYBEAT_CHDIR/manage.py celerybeat

# Extra arguments to celerybeat
CELERYBEAT_OPTS="--schedule=/var/run/celerybeat-schedule"

Depois, para que sejam usados pelo celery de fato, crie dois links simbólicos:
sudo ln -s /home/jayme/projetos/projeto_django/async_tasks/celerydconf /etc/default/celeryd
sudo ln -s /home/jayme/projetos/projeto_django/async_tasks/celerybeatconf /etc/default/celerybeat
Obs: por favor, lembre-se de adaptar o código para o seu projeto.

Agora é só iniciar o celery:
sudo /etc/init.d/celeryd start
sudo /etc/init.d/celerybeat start

E seu celery vai começar a trabalhar. ;)
Espero que isto seja útil!



Resolvi colocar as referências de TODOS os locais que consultei, mesmo que fosse só pra extrair uma vírgula. Aí está o monstrinho. =P

Algumas dúzias de Fontes: http://celeryproject.org/, http://celery.readthedocs.org/en/latest/getting-started/introduction.html, http://www.rabbitmq.com/man/rabbitmqctl.1.man.htmlhttp://stackoverflow.com/questions/6854133/django-celery-tutorial-not-returning-resultshttp://stackoverflow.com/questions/5361521/celery-task-schedule-celery-django-and-rabbitmqhttp://stackoverflow.com/questions/4763072/why-cant-it-find-my-celery-config-filehttp://stackoverflow.com/questions/510348/how-can-i-make-a-time-delay-in-pythonhttp://stackoverflow.com/questions/591667/linux-equivalent-of-the-dos-start-commandhttp://www.doughellmann.com/PyMOTW/subprocess/index.html#module-subprocesshttp://www.howtogeek.com/howto/ubuntu/kill-a-process-by-process-name-from-ubuntu-command-line/http://docs.python.org/library/subprocess.htmlhttp://stackoverflow.com/questions/89228/how-to-call-external-command-in-pythonhttp://stackoverflow.com/questions/9259844/running-startup-code-right-after-django-settings-also-for-commandshttp://stackoverflow.com/questions/2781383/where-to-put-django-startup-codehttp://stackoverflow.com/questions/4643065/why-does-celery-work-in-python-shell-but-not-in-my-django-views-import-problehttp://stackoverflow.com/questions/8404325/django-celery-works-in-development-fails-in-wsgi-production-how-to-debughttp://docs.celeryq.org/en/latest/cookbook/daemonizing.html#example-django-configurationhttp://stackoverflow.com/questions/8426058/bash-get-the-parent-directory-of-current-directoryhttp://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-inhttp://stackoverflow.com/questions/2860153/get-parent-directory-in-pythonhttp://docs.celeryq.org/en/latest/cookbook/daemonizing.html#generic-initd-celerybeat-django-examplehttp://stackoverflow.com/questions/8404325/django-celery-works-in-development-fails-in-wsgi-production-how-to-debug e http://rberaldo.com.br/executando-scripts-na-inicializacao-do-debianubuntu/ 





3 comentários: