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

时间:2016-05-26 10:32:54

标签: docker cron containers sh

我正在尝试在调用shell脚本的docker容器中运行cronjob。

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

修改

我创建了一个(commented) github repository,其中包含一个工作的docker cron容器,该容器以给定的时间间隔调用shell脚本。

24 个答案:

答案 0 :(得分:235)

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

参见" Run a cron job with Docker"来自Julien BoulayEkito/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.
  

以下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

(请参阅Gaafar' commentHow do I make apt-get install less noisy?
apt-get -y install -qq --force-yes cron也可以工作)

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

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

替换最后一个Dockerfile行
CMD ["cron", "-f"]

另请参阅(关于cron -f,也就是说cron"前景")" docker ubuntu cron -f is not working"

构建并运行它:

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

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

Hello world
Hello world

Eric添加in the comments

  

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

请参阅" Output of tail -f at the end of a docker CMD is not showing"。

答案 1 :(得分:83)

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

  

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

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

  

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

使用基本的shell重定向,您可能希望执行以下操作:

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

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

答案 2 :(得分:33)

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

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"]

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

* * * * * echo "hello stackoverflow" >> /test_file 2>&1
# remember to end this file with an empty new line

答案 3 :(得分:26)

@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,那么您可以用上面的CMD代替

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

答案 4 :(得分:13)

还有另一种方法,就是使用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")'

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

只需运行docker-compose up,看看它是否有效。这是Tasker repo的完整文档:

http://github.com/opsxcq/tasker

答案 5 :(得分:11)

VonC's回答非常彻底。另外我想添加一件对我有帮助的东西。如果您只想在不拖尾文件的情况下运行cron作业,那么您很想从cron命令中删除&& tail -f /var/log/cron.log

但是这会导致Docker容器在运行后立即退出,因为当cron命令完成时,Docker认为最后一个命令已经退出并因此杀死了容器。这可以通过cron -f在前台运行cron来避免。

答案 6 :(得分:6)

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

供参考,图像为is here

答案 7 :(得分:6)

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

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

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

DockerFile

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"]

entrypoint.sh

#!/bin/sh
service cron start & tail -f /var/log/cron-2.log

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

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

答案 8 :(得分:5)

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

//child.js
const Selenium = require('./lib/Selenium.js')
const {By, Key, until} = require('selenium-webdriver');
const log = require('electron-log');

process.send("Script running");

(async function() {
    console.log("Lancement de selenium")

    let driver = new Selenium("edge").build();

    try{
        await driver.get('http://www.google.com/');
        await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
        await driver.wait(until.titleIs('webdriver - Recherche Google'), 10000);
    }catch (e){
        log.error(e);
    }finally {
        if(driver != null)
            await driver.quit();
    }
})();

这里的优势是其他人已经为您完成了所有繁重的工作。超级方便简单。

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

enter image description here

答案 9 :(得分:4)

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

答案 10 :(得分:4)

虽然这旨在通过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

&#39; Classic&#39;,类似cron的配置也是可能的。

以下是docs,此处为image repository

答案 11 :(得分:4)

在另一台主机上部署容器时,请注意它不会自动启动任何进程。您需要确保'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

答案 12 :(得分:3)

在以上示例中,我创建了此组合:

在Nano中使用Crontab进行高山图像和编辑(我讨厌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

答案 13 :(得分:2)

与一次性作业并行设置cron

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

#!/bin/bash
timestamp=`date +%Y/%m/%d-%H:%M:%S`
echo "System path is $PATH at $timestamp"

保存并退出。

使用Entrypoint代替CMD

如果您在Docker容器化过程中需要处理多个作业,请使用入口点文件将其全部运行。

入口点文件是一个脚本文件,在发出docker run命令时生效。因此,我们要运行的所有步骤都可以放在此脚本文件中。

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

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

运行定期作业:run.sh

创建entrypoint.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

让我们了解文件中已设置的crontab

* * * * *:计划时间表;该作业必须每分钟运行一次。您可以根据自己的要求更新时间表。

/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
  1. 更新cron设置并指定-
SHELL=/bin/bash
BASH_ENV=/container.env

最后,您的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

最后但并非最不重要:创建一个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

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

答案 14 :(得分: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图像上没有的命令。

答案 15 :(得分:2)

这条线是帮助我执行预定任务的那条线。

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

->我的项目在内部运行:从php:7.2-apache

答案 16 :(得分:1)

我决定使用 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" ]

答案 17 :(得分:1)

如果您想运行 crontab 以外的其他服务,您可能需要 调用此脚本以激活您的脚本以及您的 入口点:

addCronTabEntry.sh

(crontab -l 2>/dev/null; echo "*/1 * * * * <yourscript>") | crontab -
service cron start

https://github.com/WolfgangFahl/pymediawikidocker

答案 18 :(得分:0)

当运行一些限制根访问的精简图像时,我必须将我的用户添加到sudoers并以sudo cron

运行

FROM node:8.6.0
RUN apt-get update && apt-get install -y cron sudo

COPY crontab /etc/cron.d/my-cron
RUN chmod 0644 /etc/cron.d/my-cron
RUN touch /var/log/cron.log

# Allow node user to start cron daemon with sudo
RUN echo 'node ALL=NOPASSWD: /usr/sbin/cron' >>/etc/sudoers

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

也许这有助于某人

答案 19 :(得分:0)

Cron作业存储在/ var / spool / cron / crontabs中(我知道的所有发行版中的常见位置)。顺便说一句,您可以使用类似的东西在bash中创建一个cron选项卡:

crontab -l > cronexample
echo "00 09 * * 1-5 echo hello" >> cronexample
crontab cronexample
rm cronexample

这将使用cron任务创建一个临时文件,然后使用crontab对其进行编程。最后一行删除临时文件。

答案 20 :(得分:0)

所以,我的问题是一样的。解决方法是更改​​docker-compose.yml中的命令部分。

发件人

命令:crontab / etc / crontab && tail -f / etc / crontab

收件人

命令:crontab / etc / crontab

命令:tail -f / etc / crontab

问题是命令之间的“ &&”。删除后,一切都很好。

答案 21 :(得分:0)

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

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

这实际上花了我几个小时才能弄清楚,因为在Docker容器中调试cron作业是一项繁琐的任务。希望它可以帮助其他无法使他们的代码正常工作的人!

答案 22 :(得分:-1)

尝试使用发条级宝石安排任务。请遵循此链接中提供的步骤。

http://fuzzyblog.io/blog/rails/2017/05/11/adding-cron-to-a-dockerized-rails-application-using-clockwork.html

您可以按以下方式在lib / clock.rb文件中调用rake任务。

every(1.day, 'Import large data from csv files', :at => '5:00') do |job|
  `rake 'portal:import_data_from_csv'`
end

在docker-compose文件中创建一个单独的容器,并在该容器中运行以下命令。

command: bundle exec clockwork lib/clock.rb

答案 23 :(得分:-1)

到目前为止,我发现的最强大的方法是运行一个独立的cron容器 - 安装docker客户端并绑定mounter docker sock,这样你就可以与主机上的docker服务器通信。

然后只为每个cron作业使用env vars,并使用入口点脚本生成/ etc / crontab

这是我使用这个原理创建的图像,并在过去3 - 4年中在生产中使用它。

https://www.vip-consult.solutions/post/better-docker-cron#content