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

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


当前回答

在专用容器中定义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"

其他回答

我偶尔尝试寻找一个docker友好的cron实现。最后一次尝试,我找到了一对。

我所说的对docker友好的意思是,“任务的输出可以在docker日志中看到,而不是诉诸于技巧。”

目前我认为最有希望的是超级克隆。它可以提供一个crontab文件,同时对docker友好。利用它:

docker-compose.yml:

services:
  supercronic:
    build: .
    command: supercronic crontab

Dockerfile:

FROM alpine:3.17
RUN set -x \
    && apk add --no-cache supercronic shadow \
    && useradd -m app
USER app
COPY crontab .

定时任务:

* * * * * date

有更多信息的要点。

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

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

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

专注于在接收到SIGTERM或SIGQUIT信号时优雅地停止cronjob(例如在运行docker stop时)。

这并不容易。默认情况下,cron进程只是被杀死,而不注意运行cronjob。我在详细阐述pablorsk的回答:

Dockerfile:

FROM ubuntu:latest

RUN apt-get update \
    && apt-get -y install cron procps \
    && rm -rf /var/lib/apt/lists/*

# Copy cronjobs file to the cron.d directory
COPY cronjobs /etc/cron.d/cronjobs

# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/cronjobs

# similarly prepare the default cronjob scripts
COPY run_cronjob.sh /root/run_cronjob.sh
RUN chmod +x /root/run_cronjob.sh
COPY run_cronjob_without_log.sh /root/run_cronjob_without_log.sh
RUN chmod +x /root/run_cronjob_without_log.sh

# Apply cron job
RUN crontab /etc/cron.d/cronjobs

# to gain access to environment variables, we need this additional entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# optionally, change received signal from SIGTERM TO SIGQUIT
#STOPSIGNAL SIGQUIT

# Run the command on container startup
ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh:

#!/bin/bash
# make global environment variables available within crond, too
printenv | grep -v "no_proxy" >> /etc/environment

# SIGQUIT/SIGTERM-handler
term_handler() {
  echo 'stopping cron'
  service cron stop
  echo 'stopped'
  echo 'waiting'
  x=$(($(ps u -C run_cronjob.sh | wc -l)-1))
  xold=0
  while [ "$x" -gt 0 ]
  do
    if [ "$x" != "$xold" ]; then
      echo "Waiting for $x running cronjob(s):"
      ps u -C run_cronjob.sh
      xold=$x
      sleep 1
    fi
    x=$(($(ps u -C run_cronjob.sh | wc -l)-1))
  done
  echo 'done waiting'
  exit 143; # 128 + 15 -- SIGTERM
}

# cron service with SIGTERM and SIGQUIT support
service cron start
trap "term_handler" QUIT TERM

# endless loop
while true
do
  tail -f /dev/null & wait ${!}
done

的计划

* * * * * ./run_cronjob.sh cron1
*/2 * * * * ./run_cronjob.sh cron2
*/3 * * * * ./run_cronjob.sh cron3

假设您将所有cronjob包装在run_cronjob.sh脚本中。这样,您就可以执行任意代码,关机将优雅地等待。

Run_cronjobs.sh(保持cronjob定义干净的可选帮助脚本)

#!/bin/bash

DIR_INCL="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR_INCL" ]]; then DIR_INCL="$PWD"; fi
cd "$DIR_INCL"

# redirect all cronjob output to docker
./run_cronjob_without_log.sh "$@" > /proc/1/fd/1 2>/proc/1/fd/2

run_cronjob_without_log.sh

your_actual_cronjob_src()

顺便说一下,当接收到SIGKILL时,容器仍然会立即关闭。这样你就可以使用docker-compose stop -t 60 cron-container这样的命令来等待60秒,让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

这个问题有很多答案,但有些很复杂,另一些有一些缺点。我试着解释问题并提出解决方案。

cron-entrypoint.sh:

#!/bin/bash

# copy machine environment variables to cron environment
printenv | cat - /etc/crontab > temp && mv temp /etc/crontab

## validate cron file
crontab /etc/crontab

# cron service with SIGTERM support
service cron start
trap "service cron stop; exit" SIGINT SIGTERM

# just dump your logs to std output
tail -f  \
    /app/storage/logs/laravel.log \
    /var/log/cron.log \
    & wait $!

解决问题

环境变量在cron环境中不可用(如env vars或kubernetes secrets) 当crontab文件无效时停止 当机器接收到SIGTERM信号时,优雅地停止cron作业

作为上下文,我在Kubernetes上使用Laravel应用程序使用之前的脚本。

我根据其他答案创建了一个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

作为参考,图片在这里。