Cron y virtualenv

227

Estoy tratando de ejecutar un comando de administración de Django desde cron. Estoy usando virtualenv para mantener mi proyecto protegido.

He visto ejemplos aquí y en otros lugares que muestran comandos de administración en ejecución desde dentro de virtualenv como:

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

Sin embargo, aunque syslog muestra una entrada cuando la tarea debería haber comenzado, esta tarea nunca se ejecuta realmente (el archivo de registro para el script está vacío). Si ejecuto la línea manualmente desde el shell, funciona como se esperaba.

La única forma en que actualmente puedo obtener el comando para que se ejecute a través de cron, es dividir los comandos y ponerlos en un script de envoltura de bash tonto:

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

EDITAR:

Ars se le ocurrió una combinación funcional de comandos:

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

Al menos en mi caso, invocar el script de activación para virtualenv no hizo nada. Esto funciona, así sucesivamente con el espectáculo.

John-Scott
fuente
Una diferencia que veo es que el script ejecutará manage.py con / home / user / project como el directorio de trabajo actual. Su comando cron se ejecutará con su directorio de inicio como cwd. Tal vez el archivo de registro está ahí?
retira el
En realidad, la ruta de registro se define absolutamente, simplemente no se crea / agrega porque el script no se está ejecutando.
John-Scott
Una solución rápida y sucia para los problemas de cron es volcar su entorno (en el que su comando funciona inexplicablemente) envy exporttodos ellos en un contenedor de script bash al que llama desde el crontab.
jberryman

Respuestas:

250

Debería poder hacer esto utilizando el pythonen su entorno virtual:

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

EDITAR: si su proyecto django no está en PYTHONPATH, deberá cambiar al directorio correcto:

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

También puede intentar registrar el error desde cron:

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

Otra cosa para intentar es hacer el mismo cambio en su manage.pyscript en la parte superior:

#!/home/my/virtual/bin/python
ars
fuente
1
Eso tampoco funciona. Olvidé poner eso en mi lista de cosas que no funcionan. Sí, puedo ejecutar ese comando manualmente en el shell, pero no funciona desde cron.
John-Scott
¿Reemplazaste ~con el camino completo? (Probablemente lo hiciste, solo asegurándote ...)
ars
¡Ah, has encontrado un ejemplo de trabajo! He probado todas las combinaciones y la activación de virtualenv parece no tener ningún efecto. Configuré mi PYTHONPATH en .bashrc pero aparentemente esto no lo usa cron. Actualizaré mi pregunta para resaltar su respuesta.
John-Scott
Sí, había olvidado que cron se ejecuta en un entorno muy mínimo. La recomendación general es escribir scripts de bash para configurar el entorno que necesitará su trabajo. Puede intentar obtener el perfil de bash directamente en cron, pero esto puede conducir a errores sutiles dependiendo de lo que haya en su perfil (quizás si tiene un perfil separado y mínimo para tales necesidades, estaría bien).
ars
77
Una buena manera de probar es ejecutar / bin / sh, y luego intentar ejecutar su comando desde allí. Al menos tendrás la misma configuración de entorno que cron.
Dick
98

Ejecutar sourcedesde un archivo cron no funcionará, ya que cron lo utiliza /bin/shcomo su shell predeterminado, que no es compatible source. Debe configurar la variable de entorno SHELL para que sea /bin/bash:

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

Es difícil detectar por qué esto falla, ya /var/log/syslogque no registra los detalles del error. Lo mejor es usar un alias para rootear para que te envíen un correo electrónico con cualquier error cron. Simplemente agréguese /etc/aliasesy corra sendmail -bi.

Más información aquí: http://codeinthehole.com/archives/43-Running-django-cronjobs-within-a-virtualenv.html

el enlace de arriba se cambia a: https://codeinthehole.com/tips/running-django-cronjobs-within-a-virtualenv/

DavidWinterbottom
fuente
12
O '.' (comando de punto), que es compatible con / bin / sh. /path/to/virtualenv/bin/activate
Reed Sandberg el
55
David Winterbottom, si ese es tu nombre real, eres mi héroe. Nunca supe eso sobre sh vs bash y los archivos fuente. Has iluminado a mi pequeño amigo del mundo de scripting bash. Gracias.
joemurphy
Si tienes un postactivatearchivo, deberías hacerlosource /path/to/virtualenv/bin/activate && source /path/to/virtualenv/bin/postactivate
dspacejs
1
¡Gracias! Para mí, esto funciona en lugar de la respuesta aceptada por Gerald.
Martin Becker
1
¿Para qué sirve la raíz? ¿Alguien puede explicar
Adnanmuttaleb
19

No busques más:

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

Enfoque genérico:

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

Lo bueno de esto es que NO necesita cambiar la SHELLvariable para crontab de shabash

Albahaca Musa
fuente
13

La única forma correcta de ejecutar trabajos cron de python cuando se utiliza virtualenv es activar el entorno y luego ejecutar el python del entorno para ejecutar su código.

Una forma de hacerlo es usar virtualenv activate_thisen su script de Python, consulte: http://virtualenv.readthedocs.org/en/latest/userguide.html#using-virtualenv-without-bin-python

Otra solución es hacer eco del comando completo, incluida la activación del entorno y su canalización /bin/bash. Considere esto para su /etc/crontab:

***** root echo 'source /env/bin/activate; python /your/script' | /bin/bash
Ivanhoe
fuente
1
Tengo mucha curiosidad sobre si hay consenso de que esta es, de hecho, la única forma correcta.
Aaron Schumacher
1
Esta es probablemente la única forma correcta. Pero hay otras formas en que funciona.
Will
44
Esta no es "la única forma correcta". Ejecuté con éxito un script en un virtualenv simplemente señalando el cronjob al binario de python de virtualenv, como '/ home / user / folder / env / bin / python'. No es necesario activar el entorno en absoluto.
Canucklesandwich
Si usa PYTHONPATH personalizado en un entorno virtual, env / bin / python no funcionará para usted. Es por eso que es mejor usar env / bin / activar
varela
1
depende de cómo configure PYTHONPATH y si lo configura de una manera que requiera "activar" el venv, lo está haciendo mal
10

En lugar de perder el tiempo con shebangs específicos de virtualenv, simplemente anteponga PATHal crontab.

Desde un virtualenv activado, ejecute estos tres comandos y los scripts de Python deberían funcionar:

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

La primera línea del crontab debería verse así:

PATH=/home/me/virtualenv/bin:/usr/bin:/bin:  # [etc...]
joemaller
fuente
12
No es una buena solución. Todas las tareas de Python en el crontab se ejecutarían con el binario desde virtualenv. Hacer de ese binario una pitón pseudo-global va en contra del propósito mismo de virtualenv.
Victor Schröder
4

La mejor solución para mí fue a ambos

  • use el binario de python en el directorio bin / venv
  • configura la ruta de Python para incluir el directorio de módulos venv.

man pythonmenciona la modificación de la ruta en shell en $PYTHONPATHo en python consys.path

Otras respuestas mencionan ideas para hacer esto usando el shell. Desde python, agregar las siguientes líneas a mi script me permite ejecutarlo con éxito directamente desde cron.

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

Así es como se ve en una sesión interactiva:

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
>>>
aquí
fuente
4

Me gustaría agregar esto porque pasé algún tiempo resolviendo el problema y no encontré una respuesta aquí para la combinación del uso de variables en cron y virtualenv. Entonces quizás ayude a alguien.

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

No funcionó bien cuando se configuró como

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

Gracias @davidwinterbottom , @ reed-sandberg y @mkb por dar la dirección correcta. La respuesta aceptada realmente funciona bien hasta que su python necesite ejecutar un script que tenga que ejecutar otro binario de python desde el directorio venv / bin.

Dmitriy
fuente
0

Esta es una solución que me ha funcionado bien.

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

Estoy usando miniconda con Conda versión 4.7.12 en un Ubuntu 18.04.3 LTS.

Puedo colocar lo anterior dentro de un script y ejecutarlo a través de crontab también sin ningún problema.

Arun Thundyill Saseendran
fuente
0

script de 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  

En el comando anterior

  • * / 1 * * * * - Ejecuta cada minuto
  • cd / Workspace / testcron / - Ruta del script de python
  • / Espacio de trabajo / testcron / venvcron / bin / python3 : ruta Virtualenv
  • Workspace / testcron / testcronwithparam.py - Ruta del archivo
  • param - parámetro
Ramesh Ponnusamy
fuente