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

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


当前回答

专注于在接收到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优雅地完成,但仍然在超时后终止它们。

其他回答

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

作为参考,图片在这里。

显然,可以在容器中(在根用户下)与其他进程一起运行cron,使用Dockerfile中的ENTRYPOINT语句和start.sh脚本,其中包括行进程cron start。更多信息请点击这里

#!/bin/bash

# copy environment variables for local use
env >> etc/environment

# start cron service
service cron start

# start other service
service other start
#...

我偶尔尝试寻找一个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。但它们“没有简单的方法来查看任务的输出”。

这一行帮助我运行了预先计划好的任务。

ADD mycron/root /etc/cron.d/root

RUN chmod 0644 /etc/cron.d/root

RUN crontab /etc/cron.d/root

RUN touch /var/log/cron.log

CMD ( cron -f -l 8 & ) && apache2-foreground # <-- run cron

我的项目运行在:FROM php:7.2-apache

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

@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死亡,容器将继续运行。