web-dev-qa-db-fra.com

Exécution des tâches cron python dans docker

Je souhaite exécuter un travail cron python) à l'intérieur d'un conteneur de menu fixe en mode détaché. Ma configuration est la suivante:

Mon script python est test.py

  #!/usr/bin/env python
  import datetime
  print "Cron job has run at %s" %datetime.datetime.now()

Mon fichier cron est my-crontab

* * * * * /test.py > /dev/console

et mon Dockerfile est

FROM ubuntu:latest
RUN apt-get update && apt-get install -y software-properties-common python-software-properties && apt-get update

RUN apt-get install -y python cron
ADD my-crontab /
ADD test.py /
RUN chmod a+x test.py

RUN crontab /my-crontab
ENTRYPOINT cron -f

Quels sont les problèmes potentiels avec cette approche? Existe-t-il d'autres approches et quels sont leurs avantages et inconvénients?

47

Plusieurs problèmes que j'ai rencontrés lors de la tentative d'exécution d'un travail cron dans un conteneur Docker étaient les suivants:

  1. l'heure indiquée dans le conteneur de menu fixe est l'heure UTC et non l'heure locale;
  2. l'environnement docker n'est pas transmis à cron;
  3. comme l'a souligné Thomas, la journalisation cron laisse beaucoup à désirer et son accès via docker nécessite une solution basée sur le docker.

Il y a des problèmes spécifiques à cron et des problèmes spécifiques à docker dans la liste, mais dans tous les cas, ils doivent être résolus pour que cron fonctionne.

À cette fin, ma solution de travail actuelle au problème posé dans la question est la suivante:

Créez un volume de menu fixe dans lequel tous les scripts exécutés sous cron écriront:

# Dockerfile for test-logs

# BUILD-USING:        docker build -t test-logs .
# RUN-USING:          docker run  -d -v /t-logs --name t-logs test-logs
# INSPECT-USING:      docker run -t -i  --volumes-from t-logs ubuntu:latest /bin/bash

FROM stackbrew/busybox:latest

# Create logs volume
VOLUME /var/log

CMD  ["true"]

Le script qui s'exécutera sous cron est test.py:

#!/usr/bin/env python

# python script which needs an environment variable and runs as a cron job
import datetime
import os

test_environ = os.environ["TEST_ENV"]
print "Cron job has run at %s with environment variable '%s'" %(datetime.datetime.now(), test_environ)

Afin de transmettre la variable d'environnement au script que je veux exécuter sous cron, suivez la suggestion de Thomas et mettez un fragment de crontab pour chaque script (ou groupe de scripts) nécessitant une variable d'environnement docker dans /etc/cron.d avec un espace réservé XXXXXXX qui doit être défini.

# placed in /etc/cron.d 
# TEST_ENV is an docker environment variable that the script test.py need

TEST_ENV=XXXXXXX
#
* * * * * root python /test.py >> /var/log/test.log

Au lieu d'appeler directement cron, enveloppez-le dans un python) script qui fait les choses suivantes: 1. lit la variable d'environnement à partir de la variable d'environnement docker et la définit dans un fragment de crontab.

#!/usr/bin/env python

# run-cron.py
# sets environment variable crontab fragments and runs cron

import os
from subprocess import call
import fileinput

# read docker environment variables and set them in the appropriate crontab fragment
environment_variable = os.environ["TEST_ENV"]

for line in fileinput.input("/etc/cron.d/cron-python",inplace=1):
    print line.replace("XXXXXXX", environment_variable)

args = ["cron","-f", "-L 15"]
call(args)

La Dockerfile celle du conteneur dans lequel les tâches cron sont exécutées est la suivante:

# BUILD-USING:        docker build -t test-cron .
# RUN-USING docker run --detach=true --volumes-from t-logs --name t-cron test-cron

FROM debian:wheezy
#
# Set correct environment variables.
ENV HOME /root
ENV TEST_ENV test-value

RUN apt-get update && apt-get install -y software-properties-common python-software-properties && apt-get update

# Install Python Setuptools
RUN apt-get install -y python cron

RUN apt-get purge -y python-software-properties software-properties-common && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ADD cron-python /etc/cron.d/
ADD test.py /
ADD run-cron.py /

RUN chmod a+x test.py run-cron.py

# Set the time zone to the local time zone
RUN echo "America/New_York" > /etc/timezone && dpkg-reconfigure --frontend noninteractive tzdata

CMD ["/run-cron.py"]

Enfin, créez les conteneurs et exécutez-les:

  1. Créez le conteneur de volume de journal (tests-logs): docker build -t test-logs .
  2. Exécuter le volume du journal: docker run -d -v /t-logs --name t-logs test-logs
  3. Créez le conteneur cron: docker build -t test-cron .
  4. Exécutez le conteneur cron: docker run --detach=true --volumes-from t-logs --name t-cron test-cron
  5. Pour inspecter les fichiers journaux des scripts exécutés sous cron: docker run -t -i --volumes-from t-logs ubuntu:latest /bin/bash. Les fichiers de log sont en /var/log.
33

Voici un complément sur la réponse de Rosksw.

Il n'est pas nécessaire de remplacer une chaîne dans le fichier crontab pour transmettre des variables d'environnement aux tâches cron.

Il est plus simple de stocker les variables d’environnement dans un fichier lors de l’exécution du conteneur, puis de les charger à partir de ce fichier à chaque exécution cron. J'ai trouvé le conseil ici .

Dans le fichier de docker:

CMD mkdir -p /data/log && env > /root/env.txt && crond -n

Dans le fichier crontab:

* * * * * root env - `cat /root/env.txt` my-script.sh
15
Alban Mouton

Ajout de fragments de crontab dans /etc/cron.d/ au lieu d'utiliser crontab de root pourrait être préférable.

Cela:

  • Vous permet d'ajouter des tâches cron supplémentaires en les ajoutant à ce dossier.
  • Économisez quelques couches.
  • Émulez comment les distributions Debian le font pour leurs propres paquets.

Observez que le format de ces fichiers est un peu différent d'une entrée crontab. Voici un exemple du paquet php Debian:

# /etc/cron.d/php5: crontab fragment for php5
#  This purges session files older than X, where X is defined in seconds
#  as the largest value of session.gc_maxlifetime from all your php.ini
#  files, or 24 minutes if not defined.  See /usr/lib/php5/maxlifetime

# Look for and purge old sessions every 30 minutes
09,39 *     * * *     root   [ -x /usr/lib/php5/maxlifetime ] && [ -x /usr/lib/php5/sessionclean ] && [ -d /var/lib/php5 ] && /usr/lib/php5/sessionclean /var/lib/php5 $(/usr/lib/php5/maxlifetime)

Globalement, par expérience, exécuter cron dans un conteneur fonctionne très bien (à part la journalisation cron qui laisse beaucoup à désirer).

8
Thomas Orozco

Voici une solution alternative.

dans Dockerfile

ADD docker/cron/my-cron /etc/cron.d/my-cron
RUN chmod 0644 /etc/cron.d/my-cron

ADD docker/cron/entrypoint.sh /etc/entrypoint.sh

ENTRYPOINT ["/bin/sh", "/etc/entrypoint.sh"]

dans entrypoint.sh

 #!/usr/bin/env bash
  printenv | cat - /etc/cron.d/my-cron > ~/my-cron.tmp \
    && mv ~/my-cron.tmp /etc/cron.d/my-cron

cron -f
5
evtuhovdo

Nous utilisons la solution ci-dessous. Il supporte à la fois docker logs fonctionnalité et possibilité de suspendre le processus cron dans le conteneur sur le PID 1 (si vous utilisez tail -f solutions de contournement fournies ci-dessus - en cas de blocage de Cron, le menu fixe ne suivra pas la stratégie de redémarrage):

cron.sh:

#!/usr/bin/env bash

printenv | cat - /etc/cron.d/cron-jobs > ~/crontab.tmp \
    && mv ~/crontab.tmp /etc/cron.d/cron-jobs

chmod 644 /etc/cron.d/cron-jobs

tail -f /var/log/cron.log &

cron -f

Dockerfile:

RUN apt-get install --no-install-recommends -y -q cron 

ADD cron.sh /usr/bin/cron.sh
RUN chmod +x /usr/bin/cron.sh

ADD ./crontab /etc/cron.d/cron-jobs
RUN chmod 0644 /etc/cron.d/cron-jobs

RUN touch /var/log/cron.log

ENTRYPOINT ["/bin/sh", "/usr/bin/cron.sh"]

crontab:

* * * * * root <cmd> >> /var/log/cron.log 2>&1

Et s'il vous plaît n'oubliez pas d'ajouter la nouvelle ligne effrayant dans votre crontab

3
dogik

Ne mélangez pas crond et votre image de base. Préférez utiliser une solution native pour votre langue (programmez ou crython, comme le dit Anton), ou découplez-la. En découplant cela, je veux dire, gardez les choses séparées, de sorte que vous n’ayez pas à maintenir une image juste pour être la fusion entre python et crond.

Vous pouvez utiliser Tasker , un exécuteur de tâches doté de la prise en charge de cron (un planificateur), pour le résoudre, si vous souhaitez que les choses restent découplées.

Voici un docker-compose.yml fichier, qui exécutera certaines tâches pour vous

version: "2"

services:
    tasker:
        image: strm/tasker
        volumes:
            - "/var/run/docker.sock:/var/run/docker.sock"
        environment:
            configuration: |
                logging:
                    level:
                        ROOT: WARN
                        org.springframework.web: WARN
                        sh.strm: DEBUG
                schedule:
                    - every: minute
                      task: helloFromPython
                tasks:
                    docker:
                        - name: helloFromPython
                          image: python:3-slim
                          script:
                              - python -c 'print("Hello world from python")'

Il suffit de courir docker-compose up et le voir fonctionner. Voici le rapport Tasker avec la documentation complète:

http://github.com/opsxcq/tasker

2
OPSXCQ

Méthode de conteneur unique

Vous pouvez exécuter crond dans le même conteneur que faisant quelque chose de étroitement lié en utilisant une image de base qui gère bien le PID 0, comme phusion/baseimage .

Méthode du conteneur spécialisé

Peut-être plus propre serait d'avoir un autre conteneur lié à celui-ci qui ne fait que lancer crond. Par exemple:

Dockerfile

 FROM busybox
 ADD crontab /var/spool/cron/crontabs/www-data
 CMD crond -f

crontab

 * * * * * echo $USER

Puis lancez:

 $ docker build -t cron .
 $ docker run --rm --link something cron

Remarque: dans ce cas, le travail sera exécuté en tant que www-data. Impossible de monter simplement le fichier crontab en tant que volume, car il doit appartenir à root avec un accès en écriture uniquement pour root, sinon crond n'exécutera rien. De plus, vous devrez exécuter crond comme root.

2
Wernight

Une autre possibilité consiste à utiliser Crython . Crython vous permet de planifier régulièrement une fonction python à partir d'un seul script python /.). Elle comprend même la syntaxe cron:

@crython.job(expr='0 0 0 * * 0 *')
def job():
    print "Hello world"

L'utilisation de crython évite les divers problèmes liés à l'exécution de crond dans un conteneur de menu fixe: votre travail est désormais un processus unique qui se réveille à tout moment, ce qui convient mieux au modèle d'exécution du menu fixe. Mais cela présente l'inconvénient d'inscrire la planification dans votre programme, ce qui n'est pas toujours souhaitable. Néanmoins, cela pourrait être utile dans certains cas d'utilisation.

1
Anton I. Sipos