我试图在调用shell脚本的docker容器内运行cronjob。

昨天我一直在网上搜索,堆栈溢出,但我真的找不到一个有效的解决方案。 我该怎么做呢?


当前回答

您可以将crontab复制到映像中,以便从映像中启动的容器运行作业。


重要提示:docker-cron问题3中提到:cron文件使用LF,而不是CRLF。


参见Julien Boulay在他的Ekito/ Docker -cron中的“Run a cron job with Docker”:

让我们创建一个名为“hello-cron”的新文件来描述我们的工作。

# must be ended with a new line "LF" (Unix) and not "CRLF" (Windows)
* * * * * echo "Hello world" >> /var/log/cron.log 2>&1
# An empty line is required at the end of this file for a valid cron file.

如果你想知道什么是2>&1,Ayman Hourieh解释道。

下面的Dockerfile描述了构建映像的所有步骤

FROM ubuntu:latest
MAINTAINER docker@ekito.fr

RUN apt-get update && apt-get -y install cron

# Copy hello-cron file to the cron.d directory
COPY hello-cron /etc/cron.d/hello-cron
 
# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/hello-cron

# Apply cron job
RUN crontab /etc/cron.d/hello-cron
 
# Create the log file to be able to run tail
RUN touch /var/log/cron.log
 
# Run the command on container startup
CMD cron && tail -f /var/log/cron.log

但是:如果cron死亡,容器将继续运行。

(见Gaafar的评论和我如何使apt-get安装噪音更小?: Apt-get -y install -qq——force-yes cron也可以工作)

正如Nathan Lloyd在评论中所指出的:

关于一个问题的简单说明: 如果您正在添加一个脚本文件并告诉cron运行它,请记住这样做 运行chmod 0744 /the_script 如果你忘记了,Cron会默默地失败。


或者,确保你的作业本身直接重定向到stdout/stderr,而不是一个日志文件,如hugoShaka的回答所述:

 * * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2

将最后一行Dockerfile替换为

CMD ["cron", "-f"]

但是:如果你想以非根用户的身份运行任务,它就不起作用了。

参见(关于cron -f,这是说cron“前景”)“docker ubuntu cron -f不能正常工作”


构建并运行它:

sudo docker build --rm -t ekito/cron-example .
sudo docker run -t -i ekito/cron-example

耐心等待2分钟,你的命令行应该显示:

Hello world
Hello world

Eric在评论中补充道:

请注意,如果tail是在映像构建期间创建的,那么它可能不会显示正确的文件。 如果是这种情况,您需要在容器运行时创建或触摸该文件,以便tail获得正确的文件。

参见“docker CMD末尾的tail -f输出不显示”。


更多信息请参见Jason Kulatunga的“在Docker中运行Cron”(2021年4月),他在下面评论道

参见Jason的图片AnalogJ/docker-cron基于:

Dockerfile安装cronie/crond,取决于发行版。 一个入口点初始化/etc/environment,然后调用 Cron -f -l 2

其他回答

下面是我的基于docker-compose的解决方案:

  cron:
    image: alpine:3.10
    command: crond -f -d 8
    depends_on:
      - servicename
    volumes:
      - './conf/cron:/etc/crontabs/root:z'
    restart: unless-stopped

带有cron条目的行在./conf/cron文件中。

注意:这将不会运行不在alpine映像中的命令。

此外,任务的输出显然不会出现在docker日志中。

只要添加到答案列表中,你也可以使用这张图片: https://hub.docker.com/repository/docker/cronit/simple-cron

并使用它作为启动cron作业的基础,像这样使用它:

FROM cronit/simple-cron # Inherit from the base image
#Set up all your dependencies 
COPY jobs.cron ./ # Copy your local config

对于多个作业和各种依赖项(如zsh和curl),这是一种很好的方法,同时还结合了其他答案的最佳实践。额外的好处:这并不需要你在myScript.sh上设置+x个执行权限,这在新环境中很容易错过。

cron.dockerfile

FROM ubuntu:latest

# Install dependencies
RUN apt-get update && apt-get -y install \
  cron \
  zsh \
  curl;

# Setup multiple jobs with zsh and redirect outputs to docker logs
RUN (echo "\
* * * * * zsh -c 'echo "Hello World"' 1> /proc/1/fd/1 2>/proc/1/fd/2 \n\
* * * * * zsh /myScript.sh 1> /proc/1/fd/1 2>/proc/1/fd/2 \n\
") | crontab

# Run cron in forground, so docker knows the task is running
CMD ["cron", "-f"]

将此与docker compose集成,如下所示:

docker-compose.yml

services:
  cron:
    build:
      context: .
      dockerfile: ./cron.dockerfile
    volumes:
      - ./myScript.sh:/myScript.sh

请记住,当您更改cron的内容时,您需要docker编写构建cron。但对myScript.sh的更改将在compose中挂载时立即反映出来。

在生产环境中,公认的答案可能是危险的。

在docker中,你应该每个容器只执行一个进程,因为如果你不这样做,fork和后台进程就不会被监控,可能会在你不知道的情况下停止。

当你使用CMD cron && tail -f /var/log/cron.log时,cron进程基本上会分叉,以便在后台执行cron,主进程退出,让你在前台执行tailf。后台cron进程可能会停止或失败,但你不会注意到,你的容器仍然会默默地运行,你的编排工具也不会重新启动它。

你可以通过直接将cron的命令输出重定向到分别位于/proc/1/fd/1和/proc/1/fd/2的docker stdout和stderr中来避免这样的事情

使用基本的shell重定向,你可能想做这样的事情:

* * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2

你的CMD将是:CMD ["cron", "-f"]

但是:如果你想以非根用户的身份运行任务,这就行不通了。

@VonC的建议很好,但我更喜欢在一行中完成所有cron作业配置。这将避免像cronjob位置这样的跨平台问题,并且您不需要单独的cron文件。

FROM ubuntu:latest

# Install cron
RUN apt-get -y install cron

# Create the log file to be able to run tail
RUN touch /var/log/cron.log

# Setup cron job
RUN (crontab -l ; echo "* * * * * echo "Hello world" >> /var/log/cron.log") | crontab

# Run the command on container startup
CMD cron && tail -f /var/log/cron.log

运行docker容器后,你可以通过以下方式确定cron服务是否在工作:

# To check if the job is scheduled
docker exec -ti <your-container-id> bash -c "crontab -l"
# To check if the cron service is running
docker exec -ti <your-container-id> bash -c "pgrep cron"

如果你喜欢用ENTRYPOINT代替CMD,那么你可以用

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

但是:如果cron死亡,容器将继续运行。