Cron e virtualenv

227

Estou tentando executar um comando de gerenciamento do Django a partir do cron. Estou usando o virtualenv para manter meu projeto em área restrita.

Eu vi exemplos aqui e em outros lugares que mostram a execução de comandos de gerenciamento de dentro do virtualenv, como:

0 3 * * * source /home/user/project/env/bin/activate && /home/user/project/manage.py command arg

No entanto, embora o syslog mostre uma entrada quando a tarefa deveria ter sido iniciada, ela nunca é executada (o arquivo de log do script está vazio). Se eu executar a linha manualmente a partir do shell, ela funcionará conforme o esperado.

Atualmente, a única maneira de obter o comando para executar via cron é dividir os comandos e colocá-los em um script de invólucro bash idiota:

#!/bin/sh
source /home/user/project/env/bin/activate
cd /home/user/project/
./manage.py command arg

EDITAR:

O ars surgiu com uma combinação de comandos de trabalho:

0 3 * * * cd /home/user/project && /home/user/project/env/bin/python /home/user/project/manage.py command arg

Pelo menos no meu caso, invocar o script de ativação para o virtualenv não fez nada. Isso funciona, assim por diante com o show.

John-Scott
fonte
Uma diferença que vejo é que o script executará manage.py com / home / user / project como o diretório de trabalho atual. Seu comando cron seria executado com o diretório inicial como o cwd. Talvez o arquivo de log esteja lá?
Rettops
Na verdade, o caminho do log é definido absolutamente, simplesmente não é criado / anexado porque o script não está em execução.
31410 John-Scott
Uma solução rápida e suja para os problemas do cron é despejar seu ambiente (no qual seu comando está inexplicavelmente funcionando) enve exporttodos eles em um wrapper de script bash que você chama no crontab.
jberryman

Respostas:

250

Você deve conseguir fazer isso usando o pythonem seu ambiente virtual:

/home/my/virtual/bin/python /home/my/project/manage.py command arg

EDIT: Se o seu projeto django não estiver no PYTHONPATH, será necessário mudar para o diretório certo:

cd /home/my/project && /home/my/virtual/bin/python ...

Você também pode tentar registrar a falha no cron:

cd /home/my/project && /home/my/virtual/bin/python /home/my/project/manage.py > /tmp/cronlog.txt 2>&1

Outra coisa a tentar é fazer a mesma alteração no seu manage.pyscript no topo:

#!/home/my/virtual/bin/python
ars
fonte
1
Isso também não funciona. Esqueci de colocar isso na minha lista de coisas que não funcionam. Sim, eu posso executar esse comando manualmente no shell, mas ele não funciona no cron.
John-Scott
Você substituiu ~pelo caminho completo? (Você provavelmente fez, apenas ter certeza ...)
ars
Ah, você veio com um exemplo de trabalho! Eu tentei todas as combinações e ativar o virtualenv parece não ter efeito algum. Eu defino meu PYTHONPATH em .bashrc, mas isso aparentemente não é usado pelo cron? Atualizará minha pergunta para destacar sua resposta.
John-Scott
Sim, eu tinha esquecido que o cron é executado em um ambiente muito mínimo. A recomendação geral é escrever scripts bash para configurar o ambiente que seu trabalho precisará. Você pode tentar obter o perfil do bash diretamente no cron, mas isso pode levar a erros sutis, dependendo do conteúdo do seu perfil (talvez se você tiver um perfil separado e mínimo para essas necessidades, tudo bem).
ars
7
Uma boa maneira de testar é executar / bin / sh e, em seguida, tente executar seu comando a partir daí. Pelo menos você terá a mesma configuração de ambiente que o cron.
Dick
98

A execução sourcede um cronfile não funcionará como o cron usa /bin/shcomo seu shell padrão, o que não suporta source. Você precisa definir a variável de ambiente SHELL para /bin/bash:

SHELL=/bin/bash
*/10 * * * * root source /path/to/virtualenv/bin/activate && /path/to/build/manage.py some_command > /dev/null

É difícil identificar por que isso falha, pois /var/log/syslognão registra os detalhes do erro. É melhor fazer o apelido de root para receber e-mails com quaisquer erros do cron. Basta adicionar-se /etc/aliasese executar sendmail -bi.

Mais informações aqui: http://codeinthehole.com/archives/43-Running-django-cronjobs-within-a-virtualenv.html

o link acima foi alterado para: https://codeinthehole.com/tips/running-django-cronjobs-within-a-virtualenv/

DavidWinterbottom
fonte
12
Ou '.' (comando dot), que é suportado por / bin / sh. /path/to/virtualenv/bin/activate
Reed Sandberg
5
DavidWinterbottom, se esse é seu nome verdadeiro, você é meu herói. Eu nunca soube disso sobre sh vs bash e arquivos de origem. Você iluminou meu pequeno cara do mundo dos scripts de festança. Obrigado.
Joemurphy
Se você tem um postactivatearquivo, você deveria fazer issosource /path/to/virtualenv/bin/activate && source /path/to/virtualenv/bin/postactivate
dspacejs 27/11
1
Obrigado! Para mim, isso funciona e não a resposta aceita por Gerald.
Martin Becker
1
para que serve a raiz? Alguém pode explicar
adnanmuttaleb
19

Não procure mais:

0 3 * * * /usr/bin/env bash -c 'cd /home/user/project && source /home/user/project/env/bin/activate && ./manage.py command arg' > /dev/null 2>&1

Abordagem genérica:

* * * * * /usr/bin/env bash -c 'YOUR_COMMAND_HERE' > /dev/null 2>&1

A beleza disso é que você NÃO precisa alterar a SHELLvariável do crontab de shparabash

Basil Musa
fonte
13

A única maneira correta de executar tarefas cron do python ao usar um virtualenv é ativar o ambiente e, em seguida, executar o python do ambiente para executar seu código.

Uma maneira de fazer isso é usar virtualenv's activate_thisem seu script python, consulte: http://virtualenv.readthedocs.org/en/latest/userguide.html#using-virtualenv-without-bin-python

Outra solução é repetir o comando completo, incluindo ativar o ambiente e canalizá-lo /bin/bash. Considere isto para o seu /etc/crontab:

***** root echo 'source /env/bin/activate; python /your/script' | /bin/bash
Ivanhoe
fonte
1
Estou muito curioso para saber se existe consenso de que essa é realmente a única maneira correta.
Aaron Schumacher
1
Esta é provavelmente a única maneira correta. Mas existem outras maneiras que funcionam.
Will
4
Esta não é "a única maneira correta". Eu executei com êxito um script em um virtualenv simplesmente apontando o cronjob para o binário python do virtualenv, como '/ home / user / folder / env / bin / python'. Não há necessidade de ativar o meio ambiente.
Canucklesandwich # 9/15
Se você usar o PYTHONPATH personalizado no ambiente virtual, env / bin / python não funcionará para você. É por isso que usando de env / bin / activate é melhor
varela
1
depende de como você define o PYTHONPATH e, se você o definir de uma maneira que exija "ativar" o venv, você estará fazendo errado.
10

Em vez de mexer com shebangs específicos de virtualvv, basta colocar PATHo crontab.

Em um virtualenv ativado, execute esses três comandos e os scripts python devem funcionar:

$ echo "PATH=$PATH" > myserver.cron
$ crontab -l >> myserver.cron
$ crontab myserver.cron

A primeira linha do crontab deve ficar assim:

PATH=/home/me/virtualenv/bin:/usr/bin:/bin:  # [etc...]
joemaller
fonte
12
Não é uma boa solução. Todas as tarefas python no crontab seriam executadas com o binário do virtualenv. Tornar esse binário um python pseudo-global vai contra o próprio objetivo do virtualenv.
Victor Schröder
4

A melhor solução para mim foi tanto

  • use o binário python no diretório venv bin /
  • defina o caminho python para incluir o diretório de módulos venv.

man pythonmenciona modificar o caminho no shell em $PYTHONPATHou em python comsys.path

Outras respostas mencionam idéias para fazer isso usando o shell. No python, adicionar as seguintes linhas ao meu script me permite executá-lo com êxito diretamente do cron.

import sys
sys.path.insert(0,'/path/to/venv/lib/python3.3/site-packages');

Veja como fica em uma sessão interativa -

Python 3.3.2+ (default, Feb 28 2014, 00:52:16) 
[GCC 4.8.1] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import sys

>>> sys.path
['', '/usr/lib/python3.3', '/usr/lib/python3.3/plat-x86_64-linux-gnu', '/usr/lib/python3.3/lib-dynload']

>>> import requests
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'requests'   

>>> sys.path.insert(0,'/path/to/venv/modules/');

>>> import requests
>>>
aqui
fonte
4

Eu gostaria de adicionar isso porque passei algum tempo resolvendo o problema e não encontrei uma resposta aqui para combinar o uso de variáveis ​​no cron e no virtualenv. Então, talvez isso ajude alguém.

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DIR_SMTH="cd /smth"
VENV=". venv/bin/activate"
CMD="some_python_bin do_something"
# m h  dom mon dow   command
0 * * * * $DIR_SMTH && $VENV && $CMD -k2 some_target >> /tmp/crontest.log 2>&1

Não funcionou bem quando foi configurado como

DIR_SMTH = "cd / smth &&. Venv / bin / ativar"

Obrigado @davidwinterbottom , @ reed-sandberg e @mkb por dar a direção certa. A resposta aceita realmente funciona bem até que seu python precise executar um script que precise executar outro binário python do diretório venv / bin.

Dmitriy
fonte
0

Esta é uma solução que funcionou bem para mim.

source /root/miniconda3/etc/profile.d/conda.sh && \
conda activate <your_env> && \
python <your_application> &

Estou usando o miniconda com Conda versão 4.7.12 em um Ubuntu 18.04.3 LTS.

Sou capaz de colocar o que precede dentro de um script e executá-lo via crontab também sem nenhum problema.

Arun Thundyill Saseendran
fonte
0

script python

from datetime import datetime                                                                                                                                                                
import boto   # check wheather its taking the virtualenv or not                                                                                                                                                                        
import sys                                                                                                                                                                                   
param1=sys.argv[1]     #Param                                                                                                                                                                                                                                                                                                                                                                    
myFile = open('appendtxt.txt', 'a')                                                                                                                                                      
myFile.write('\nAccessed on ' + param1+str(datetime.now())) 

Comando Cron

 */1 * * * *  cd /Workspace/testcron/ && /Workspace/testcron/venvcron/bin/python3  /Workspace/testcron/testcronwithparam.py param  

No comando acima

  • * / 1 * * * * - Executa cada minuto
  • cd / Workspace / testcron / - Caminho do script python
  • / Área de trabalho / testcron / venvcron / bin / python3 - caminho do Virtualenv
  • Área de trabalho / testcron / testcronwithparam.py - Caminho do arquivo
  • param - parâmetro
Ramesh Ponnusamy
fonte