如何在docker容器中运行cron作业?

C H*_*yer 215 cron containers sh docker

我试图在一个调用shell脚本的docker容器中运行一个cronjob.

昨天我一直在网上搜索和堆栈溢出,但我真的找不到一个有效的解决方案.
我怎样才能做到这一点?

编辑:

我创建了一个(注释的)github存储库,其中包含一个工作的docker cron容器,它以给定的时间间隔调用shell脚本.

Von*_*onC 298

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

请参阅" 运行与码头工人cron作业从" 朱利安·布雷Ekito/docker-cron:

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

* * * * * 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.
Run Code Online (Sandbox Code Playgroud)

以下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
Run Code Online (Sandbox Code Playgroud)

(请参阅Gaafar评论以及如何使apt-get安装减少噪音?:
apt-get -y install -qq --force-yes cron也可以工作)


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

 * * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2
Run Code Online (Sandbox Code Playgroud)

用上面的替换最后一个Dockerfile行

CMD ["cron", "-f"]
Run Code Online (Sandbox Code Playgroud)

另见(关于cron -f,也就是说cron"前景")" docker ubuntu cron -f不工作 "


构建并运行它:

sudo docker build --rm -t ekito/cron-example .
sudo docker run -t -i ekito/cron-example
Run Code Online (Sandbox Code Playgroud)

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

Hello world
Hello world
Run Code Online (Sandbox Code Playgroud)

埃里克在评论中补充道:

请注意,tail如果在映像构建期间创建了正确的文件,则可能无法显示该文件.
如果是这种情况,您需要在容器运行时创建或触摸该文件,以便tail接收正确的文件.

请参阅" 未显示泊坞窗末尾的输出tail -fCMD ".

  • 截至2018年,这种方法不再有效; 有没有人能够让他们的cronjob与Ubuntu一起工作作为基本形象?我对开箱即用的cron附带的Alpine图像不感兴趣 (9认同)
  • 这个解决方案仍然有效吗?当我按照给出的指导方针,当我以root身份登录容器并输入`crontab -l`时,我得到**没有为root用户安装crontab**,同样,我的屏幕仍为空白.但是,当我检查'/etc/cron.d/'时,我看到crontab fiel存在(甚至更令人惊讶),当我检查`/ var/log/cron.log`时,我看到脚本正在运行(文件内容附加了"Hello World").我在我的Dockerfile中拉这个图像:`FROM phusion/baseimage:0.10.0`.关于行为差异的任何想法? (5认同)
  • 关于陷阱的快速说明:如果您要添加脚本文件并告诉 cron 运行它,请记住“RUN chmod 0744 /the_script”。如果你忘记了,Cron 会默默地失败。 (4认同)
  • 我写了一篇博客文章,将这个建议(以及我在 docker 中运行 cron 发现的其他问题)实现到多个发行版(ubuntu、alpine、centos)的工作 docker 镜像中:https://blog.thesparktree.com/cron-in-docker (4认同)
  • 你应该添加`-y`来安装cron以避免docker build exiting (2认同)
  • 我读过很多关于让 cron 在 Docker 容器中工作的指南,4 小时后这一篇对我有所帮助,因为你提到了 cron 文件中的空白链接:) 谢谢! (2认同)

hug*_*aka 119

采用的解决方案在生产环境中可能是危险的.

在docker中,你应该只为每个容器执行一个进程,因为如果你不这样做,分叉并进入后台的进程不会被docker监视,并且可能会在你不知道的情况下停止.

当您使用CMD cron && tail -f /var/log/cron.logcron进程基本上fork以便cron在后台执行时,主进程退出并让您tailf在前台执行.cron进程可能会停止或失败,您将不会注意到,您的容器仍将以静默方式运行,并且您的业务流程工具将不会重新启动它.

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

使用基本bash命令,您可能希望执行以下操作:

CMD ["cron", "-f"]

你的CMD将是: CMD cron && tail -f /var/log/cron.log

  • 尼斯:`cron -f`代表"cron前景".我在上面已经包含了你的答案,以获得更多的可见度.+1 (9认同)
  • @Arcsector 此方法避免将进程置于后台,这就是它不会静默失败的原因。在 docker 容器中有一个后台进程并不简单。如果您想要一个正在运行的后台进程,您可能需要使用一个 init 进程来监视您在容器中运行的多个进程。另一种方法是将进程启动到名为“sidecar”的主容器旁边的另一个容器中。最好的方法通常是避免容器中的多个进程。 (2认同)
  • 这是一个很好的解决方案,除了一个问题之外,对我们来说效果很好。当容器收到 SIGTERM 信号时,它似乎不会等待计划的进程完成并正常关闭,而是终止可能导致问题的进程。 (2认同)

Osc*_*lli 67

对于那些想要使用简单轻量图像的人:

FROM alpine:3.6

# copy crontabs for root user
COPY config/cronjobs /etc/crontabs/root

# start crond with log level 8 in foreground, output to stderr
CMD ["crond", "-f", "-d", "8"]
Run Code Online (Sandbox Code Playgroud)

其中cronjobs是包含cronjobs的文件,其形式如下:

* * * * * echo "hello stackoverflow" >> /test_file 2>&1
# remember to end this file with an empty new line
Run Code Online (Sandbox Code Playgroud)

  • 基于简单,轻便和标准的图像.这应该是公认的答案.还可以使用`>/proc/1/fd/1 2>/proc/1/fd/2`重定向直接从docker日志访问cronjobs输出. (9认同)
  • 对于不使用 alpine 的人:支持 `-d 8` 参数的 crond 不是标准的 cron,而是来自 busybox 的 crond 命令。例如,在 ubuntu 中,您可以将其作为 `busybox crond -f -d 8` 运行。对于旧版本,您必须使用`-L /dev/stdout/`。 (5认同)
  • 如果可以的话,我会给这个+100。这是迄今为止在 Docker 环境中运行 cron 作业的最佳方式。 (2认同)
  • 这可以完全通过 docker-compose.yml 和 `image:alpine` 来完成吗? (2认同)
  • `CMD ["crond"` 还是 `CMD ["cron"`? (2认同)

You*_*ess 28

@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
Run Code Online (Sandbox Code Playgroud)

运行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"
Run Code Online (Sandbox Code Playgroud)

如果您希望使用ENTRYPOINT而不是CMD,那么您可以用上面的CMD代替

ENTRYPOINT cron start && tail -f /var/log/cron.log
Run Code Online (Sandbox Code Playgroud)

  • `运行apt-get update &amp;&amp; apt-get -y install cron`否则它将无法找到软件包`cron`。 (2认同)
  • 谢谢Youness,你给了我做以下操作的想法,在我的情况下,每个cron都在不同的文件中指定:`RUN cat $ APP_HOME/crons/*| crontab`喜欢魅力:) (2认同)

OPS*_*XCQ 17

还有另一种方法,就是使用Tasker,一个具有cron(调度程序)支持的任务运行器.

为什么?有时要运行一个cron作业,你必须将你的基本图像(python,java,nodejs,ruby)和crond混合在一起.这意味着要维护另一个图像.Tasker通过将crond和你的容器分离来避免这种情况.您可以只关注要执行命令的映像,并配置Tasker以使用它.

这是一个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: hello
                    - every: minute
                      task: helloFromPython
                    - every: minute
                      task: helloFromNode
                tasks:
                    docker:
                        - name: hello
                          image: debian:jessie
                          script:
                              - echo Hello world from Tasker
                        - name: helloFromPython
                          image: python:3-slim
                          script:
                              - python -c 'print("Hello world from python")'
                        - name: helloFromNode
                          image: node:8
                          script:
                              - node -e 'console.log("Hello from node")'
Run Code Online (Sandbox Code Playgroud)

那里有3个任务,所有这些任务都会每分钟运行一次(every: minute),并且每个任务都将scriptimage部分中定义的图像内执行代码.

跑步docker-compose up,看看它有效.这是Tasker repo的完整文档:

http://github.com/opsxcq/tasker

  • 我没有得到使用 tasker 的好处。使用 java 和 sh*** 来运行 cron 作业对我来说似乎真的是一种矫枉过正。 (2认同)

小智 11

VonC的答案非常彻底.另外我想添加一件对我有帮助的东西.如果你只想在不拖尾文件的情况下运行cron作业,那么你很想&& tail -f /var/log/cron.log从cron命令中删除它.但是这会导致docker容器在运行后不久退出,因为当cron命令完成时,docker认为最后一个命令已经退出并因此杀死了容器.这可以通过在前台运行cron来避免cron -f.

我会把它写成评论,但我还没有足够的分数:)


Gaa*_*far 8

我根据其他答案创建了一个Docker映像,该映像可以像

docker run -v "/path/to/cron:/etc/cron.d/crontab" gaafar/cron

其中/path/to/cron:crontab文件的绝对路径,或者您可以将其用作Dockerfile中的基础:

FROM gaafar/cron

# COPY crontab file in the cron directory
COPY crontab /etc/cron.d/crontab

# Add your commands here
Run Code Online (Sandbox Code Playgroud)

供参考,图像在这里


And*_*löw 8

如果您使用 docker for windows,请记住,如果您打算将 crontab 文件从 windows 导入到 ubuntu 容器,则必须将行尾格式从 CRLF 更改为 LF(即从 dos 到 unix)。如果没有,您的 cron-job 将无法工作。这是一个工作示例:

FROM ubuntu:latest

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

# Add crontab file (from your windows host) to the cron directory
ADD cron/hello-cron /etc/cron.d/hello-cron

# Change line ending format to LF
RUN dos2unix /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/hello-cron.log

# Run the command on container startup
CMD cron && tail -f /var/log/hello-cron.log
Run Code Online (Sandbox Code Playgroud)

这实际上花了我几个小时才弄明白,因为在 docker 容器中调试 cron 作业是一项乏味的任务。希望它可以帮助那些无法让他们的代码工作的人!


Mon*_*dia 8

您可以采用的另一条路线是Ofelia,这是一个高度可配置的任务运行器映像,允许 4 种执行模式。

job-exec: this job is executed inside of a running container.
job-run: runs a command inside of a new container, using a specific image.
job-local: runs the command inside of the host running ofelia.
job-service-run: runs the command inside a new "run-once" service, for running inside a swarm
Run Code Online (Sandbox Code Playgroud)

这里的优势是其他人已经为你完成了所有繁重的工作。超级方便和容易。

它还有一个漂亮的官方吉祥物。

在此处输入图片说明


Gau*_*agi 8

不幸的是,上述答案都不适用于我,尽管所有答案都会导致解决方案并最终导致我的解决方案,但如果对某人有帮助,这里是片段。谢谢

这可以通过 bash 文件解决,由于 Docker 的分层架构,cron 服务不会通过 RUN/CMD/ENTRYPOINT 命令启动。

只需添加一个 bash 文件,它将启动 cron 和其他服务(如果需要)

文件

FROM gradle:6.5.1-jdk11 AS build
# apt
RUN apt-get update
RUN apt-get -y install cron
# Setup cron to run every minute to print (you can add/update your cron here)
RUN touch /var/log/cron-1.log
RUN (crontab -l ; echo "* * * * * echo testing cron.... >> /var/log/cron-1.log 2>&1") | crontab
# entrypoint.sh
RUN chmod +x entrypoint.sh
CMD ["bash","entrypoint.sh"]
Run Code Online (Sandbox Code Playgroud)

入口点.sh

#!/bin/sh
service cron start & tail -f /var/log/cron-2.log
Run Code Online (Sandbox Code Playgroud)

如果还需要任何其他服务与 cron 一起运行,则&在同一命令中添加该服务,例如:/opt/wildfly/bin/standalone.sh & service cron start & tail -f /var/log/cron-2.log

进入 docker 容器后,您可以看到testing cron....每分钟都会在文件中打印:/var/log/cron-1.log

  • 不应该做`tail -f /var/log/cron-1.log` 而不是`/var/log/cron-2.log`,因为`cron-1.log` 是STDOUT/STDERR 的地方被导演?(除非我遗漏了什么) (2认同)

fun*_*ure 7

尽管这旨在通过Docker的exec界面在容器中正在运行的进程旁边运行作业,但您可能对此很感兴趣。

我编写了一个守护程序,该守护程序可以观察容器并计划在容器中的元数据中定义的作业。例:

version: '2'

services:
  wordpress:
    image: wordpress
  mysql:
    image: mariadb
    volumes:
      - ./database_dumps:/dumps
    labels:
      deck-chores.dump.command: sh -c "mysqldump --all-databases > /dumps/dump-$$(date -Idate)"
      deck-chores.dump.interval: daily
Run Code Online (Sandbox Code Playgroud)

“经典”的,类似cron的配置也是可能的。

这是文档,这是图像存储库


Jak*_*son 6

在专用容器中定义 cronjob,该容器通过 docker exec 将命令运行到您的服务。

这是更高的内聚力,运行脚本将可以访问您为服务定义的环境变量。

#docker-compose.yml
version: "3.3"
services:
    myservice:
      environment:
        MSG: i'm being cronjobbed, every minute!
      image: alpine
      container_name: myservice
      command: tail -f /dev/null

    cronjobber:
     image: docker:edge
     volumes:
      - /var/run/docker.sock:/var/run/docker.sock
     container_name: cronjobber
     command: >
          sh -c "
          echo '* * * * * docker exec myservice printenv | grep MSG' > /etc/crontabs/root
          && crond -f"
Run Code Online (Sandbox Code Playgroud)

  • 应该有一个关于安装 docker 套接字的高安全影响的警告:https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html (2认同)

小智 6

我决定使用 busybox,因为它是最小的图像之一。

crond 在前台执行(-f),日志记录发送到 stderr(-d),我没有选择更改日志级别。crontab 文件复制到默认路径:/var/spool/cron/crontabs

FROM busybox:1.33.1

# Usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]
#
#   -f  Foreground
#   -b  Background (default)
#   -S  Log to syslog (default)
#   -l N    Set log level. Most verbose 0, default 8
#   -d N    Set log level, log to stderr
#   -L FILE Log to FILE
#   -c DIR  Cron dir. Default:/var/spool/cron/crontabs

COPY crontab /var/spool/cron/crontabs/root

CMD [ "crond", "-f", "-d" ]
Run Code Online (Sandbox Code Playgroud)

但任务的输出显然无法在docker logs.


x-y*_*uri 6

我偶尔尝试找到一个docker友好的cron实现。上次我尝试时,我找到了几个。

我所说的“友好”是指“无需借助技巧docker即可看到任务的输出”。docker logs

目前我认为最有希望的是supercronic。它可以被提供一个 crontab 文件,同时也是docker友好的。要使用它:

docker-compose.yml

services:
  supercronic:
    build: .
    command: supercronic crontab
Run Code Online (Sandbox Code Playgroud)

Dockerfile

FROM alpine:3.17
RUN set -x \
    && apk add --no-cache supercronic shadow \
    && useradd -m app
USER app
COPY crontab .
Run Code Online (Sandbox Code Playgroud)

crontab

* * * * * date
Run Code Online (Sandbox Code Playgroud)

包含更多信息的要点

另一个不错的方法是yacron,但它使用 YAML。

ofelia可以使用,但它们似乎专注于在单独的容器中运行任务。这可能不是一个缺点,但我不确定为什么我想这样做。

还有一些传统的cron实现:dcronfcroncronie。但它们“没有简单的方法来查看任务的输出”。


小智 5

当您在另一台主机上部署容器时,请注意它不会自动启动任何进程。您需要确保“cron”服务在您的容器内运行。在我们的例子中,我使用 Supervisord 和其他服务来启动 cron 服务。

[program:misc]
command=/etc/init.d/cron restart
user=root
autostart=true
autorestart=true
stderr_logfile=/var/log/misc-cron.err.log
stdout_logfile=/var/log/misc-cron.out.log
priority=998
Run Code Online (Sandbox Code Playgroud)


小智 5

从上面的例子中我创建了这个组合:

在 Nano 中使用 Crontab 进行 Alpine 图像和编辑(我讨厌 vi)

FROM alpine

RUN apk update
RUN apk add curl nano

ENV EDITOR=/usr/bin/nano 

# start crond with log level 8 in foreground, output to stderr
CMD ["crond", "-f", "-d", "8"]

# Shell Access
# docker exec -it <CONTAINERID> /bin/sh

# Example Cron Entry
# crontab -e
# * * * * * echo hello > /proc/1/fd/1 2>/proc/1/fd/2
# DATE/TIME WILL BE IN UTC
Run Code Online (Sandbox Code Playgroud)


him*_*ian 5

与一次性作业并行设置 cron

使用应该定期运行的作业创建一个脚本文件,比如 run.sh。

#!/bin/bash
timestamp=`date +%Y/%m/%d-%H:%M:%S`
echo "System path is $PATH at $timestamp"
Run Code Online (Sandbox Code Playgroud)

保存并退出。

使用入口点而不是 CMD

如果在 docker 容器化期间有多个作业要启动,请使用入口点文件来运行它们。

入口点文件是一个脚本文件,在发出 docker run 命令时会起作用。所以,我们想要运行的所有步骤都可以放在这个脚本文件中。

例如,我们有 2 个作业要运行:

运行一次作业:echo“Docker 容器已启动”

运行定期作业:run.sh

创建入口点.sh

#!/bin/bash

# Start the run once job.
echo "Docker container has been started"

# Setup a cron schedule
echo "* * * * * /run.sh >> /var/log/cron.log 2>&1
# This extra line makes it a valid cron" > scheduler.txt

crontab scheduler.txt
cron -f
Run Code Online (Sandbox Code Playgroud)

来了解一下文件中已经设置好的crontab

* * * * *:Cron 计划;作业必须每分钟运行一次。您可以根据您的要求更新计划。

/run.sh:要定期运行的脚本文件的路径

/var/log/cron.log:保存计划的 cron 作业输出的文件名。

2>&1:错误日志(如果有)也将被重定向到上面使用的相同输出文件。

注意:不要忘记添加一个额外的新行,因为它使其成为有效的 cron。 Scheduler.txt:完整的 cron 设置将被重定向到一个文件。

在 cron 中使用系统/用户特定的环境变量

我的实际 cron 作业期望将大部分参数作为传递给 docker run 命令的环境变量。但是,使用 bash,我无法使用属于系统或 docker 容器的任何环境变量。

然后,这是解决这个问题的方法:

  1. 在 entrypoint.sh 中添加以下行
declare -p | grep -Ev 'BASHOPTS|BASH_VERSINFO|EUID|PPID|SHELLOPTS|UID' > /container.env
Run Code Online (Sandbox Code Playgroud)
  1. 更新 cron 设置并指定-
SHELL=/bin/bash
BASH_ENV=/container.env
Run Code Online (Sandbox Code Playgroud)

最后,你entrypoint.sh应该看起来像

#!/bin/bash

# Start the run once job.
echo "Docker container has been started"

declare -p | grep -Ev 'BASHOPTS|BASH_VERSINFO|EUID|PPID|SHELLOPTS|UID' > /container.env

# Setup a cron schedule
echo "SHELL=/bin/bash
BASH_ENV=/container.env
* * * * * /run.sh >> /var/log/cron.log 2>&1
# This extra line makes it a valid cron" > scheduler.txt

crontab scheduler.txt
cron -f
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的:创建一个 Dockerfile

FROM ubuntu:16.04
MAINTAINER Himanshu Gupta

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

# Add files
ADD run.sh /run.sh
ADD entrypoint.sh /entrypoint.sh

RUN chmod +x /run.sh /entrypoint.sh

ENTRYPOINT /entrypoint.sh
Run Code Online (Sandbox Code Playgroud)

就是这样。构建并运行 Docker 镜像!


Tha*_*Guy 5

这是我的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
Run Code Online (Sandbox Code Playgroud)

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

注意:这不会运行不在alpine

此外,任务的输出显然不会出现在docker logs.