我想在分离模式下在docker容器内运行python cron作业。我的设置如下:
我的python脚本是test.py
#!/usr/bin/env python
import datetime
print "Cron job has run at %s" %datetime.datetime.now()
我的cron文件是my-crontab
* * * * * /test.py > /dev/console
我的Dockerfile是
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
这种方法有哪些潜在问题?还有其他方法,它们的优缺点是什么?
答案 0 :(得分:33)
尝试在docker容器中运行cron作业时遇到的几个问题是:
列表中存在特定于cron的问题并且是特定于docker的问题,但无论如何都必须解决这些问题以使cron正常工作。
为此,我对问题中提出的问题的当前解决方案如下:
创建一个docker卷,在cron下运行的所有脚本都将写入:
# 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"]
将在cron下运行的脚本是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)
为了将环境变量传递给我想在cron下运行的脚本,请关注Thomas'建议并为/etc/cron.d
中需要docker环境变量的每个脚本(或脚本组)添加一个crontab片段,并且必须设置占位符XXXXXXX
。
# 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
不是直接调用cron,而是在执行操作的python脚本中包装cron:1。从docker环境变量中读取环境变量,并在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)
运行cron作业的容器的Dockerfile
如下:
# 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"]
最后,创建容器并运行它们:
docker build -t test-logs .
docker run -d -v /t-logs --name t-logs test-logs
docker build -t test-cron .
docker run --detach=true --volumes-from t-logs --name t-cron test-cron
docker run -t -i --volumes-from t-logs ubuntu:latest /bin/bash
下运行的脚本的日志文件。日志文件位于/var/log
。答案 1 :(得分:15)
以下是对rosksw答案的补充。
无需在crontab文件中进行某些字符串替换,以便将环境变量传递给cron作业。
在运行限制器时将环境变量存储在文件中更简单,然后在每次执行cron时从该文件加载它们。我找到了提示here。
在dockerfile中:
CMD mkdir -p /data/log && env > /root/env.txt && crond -n
在crontab文件中:
* * * * * root env - `cat /root/env.txt` my-script.sh
答案 2 :(得分:8)
在/etc/cron.d/
中添加crontab片段而不是使用root crontab
可能更为可取。
这会:
注意这些文件的格式与crontab条目略有不同。这是来自Debian php包的示例:
# /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)
总的来说,根据经验,在容器中运行cron确实可以很好地工作(除了cron日志记录之外还有很多需要)。
答案 3 :(得分:5)
这是另一种解决方案。
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"]
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
答案 4 :(得分:3)
我们正在使用以下解决方案。它支持docker logs
功能和在PID 1上的容器中挂起cron进程的能力(如果使用上面提供的tail -f
变通办法 - 如果cron崩溃,docker将不遵循重启策略):
<强> 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
请不要忘记在crontab中添加令人毛骨悚然的新行
答案 5 :(得分:2)
您可以使用处理PID 0的基本映像在doing something closely related的同一容器中运行crond
,例如phusion/baseimage。
可能更干净的是将另一个与其链接的Container运行crond
。例如:
Dockerfile
FROM busybox
ADD crontab /var/spool/cron/crontabs/www-data
CMD crond -f
crontab
* * * * * echo $USER
然后运行:
$ docker build -t cron .
$ docker run --rm --link something cron
注意:在这种情况下,它将以www-data
运行作业。无法将crontab
文件作为卷挂载,因为它需要由root
拥有,只有root
的写入权限,否则crond
将无法运行。此外,您必须将crond
作为root
运行。
答案 6 :(得分:2)
不要混合crond和你的基本形象。更喜欢使用适合您语言的原生解决方案(如Anton所说的计划或crython),或者将其解耦。通过解耦它我的意思是,保持分离,所以你不必保持图像只是python和crond之间的融合。
你可以使用Tasker,一个具有cron(调度程序)支持的任务运行器来解决它,如果你想让事物解耦的话。
这是一个docker-compose.yml
文件,它将为您运行一些任务
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")'
只需运行docker-compose up
,看看它是否有效。这是Tasker repo的完整文档:
答案 7 :(得分:1)
另一种可能性是使用Crython。 Crython允许您在单个python脚本/进程中定期调度python函数。它甚至理解cron语法:
@crython.job(expr='0 0 0 * * 0 *')
def job():
print "Hello world"
使用crython避免了在docker容器中运行crond的各种麻烦 - 你的工作现在是一个单独的进程,在需要时唤醒,这更适合docker执行模型。但是它将调度安排在你的程序中是不利的,这并不总是令人满意的。不过,在某些用例中它可能很方便。
答案 8 :(得分:0)
这是我在docker中调试cron python脚本的清单:
cron
命令。 Cron不会自动启动。您可以使用RUN
或CMD
从Dockerfile运行它,或将其添加到容器的启动脚本中。如果您使用CMD
,则可以考虑使用cron -f
标志,它将cron置于前台,并且不会让容器死亡。但是,我更喜欢在日志文件上使用tail -f
。printenv > /etc/environment
运行此命令。如果在python脚本中使用环境变量,这是绝对必须的。默认情况下,Cron对环境变量一无所知。通过它可以从/etc/environment
中读取它们。* * * * * echo "Cron works" >>/home/code/test.log
* * * * * bash -c "/usr/local/bin/python3 /home/code/test.py >>/home/code/test.log 2>/home/code/test.log"
python测试文件应包含一些print
语句或其他显示脚本正在运行的语句。 2>/home/code/test.log
还将记录错误。否则,您将根本看不到错误,并且会继续猜测。
完成后,使用docker exec -it <container_name> bash
转到容器并检查:
crontab -l
到位了crontab配置tail -f /home/code/test.log
我花了数小时和数天的时间来解决所有这些问题。我希望这可以帮助某人避免这种情况。