Как запустить задание cron внутри Docker-контейнера?


275

Я пытаюсь запустить cronjob внутри контейнера Docker, который вызывает сценарий оболочки.

Вчера я искал во всем Интернете и переполнение стека, но я не мог найти решение, которое работает.
Как я могу это сделать?

РЕДАКТИРОВАТЬ:

Я создал (прокомментированный) репозиторий github с рабочим контейнером cron Docker, который вызывает сценарий оболочки с заданным интервалом.

Ответы:


365

Вы можете скопировать свой crontab в изображение, чтобы контейнер, запущенный из указанного образа, запустил задание.

См. « Запусти cron с Docker » от Жюльена Булая в его 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.

Следующий 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 «сек комментарий и Как сделать apt-getустановку менее шумным? :
apt-get -y install -qq --force-yes cronможет работать тоже)

Как отметил Натан Ллойд в комментариях :

Краткое замечание по поводу ошибки:
если вы добавляете файл сценария и указываете cron запустить его, запомните, что Cron молчит, если вы забудете .
RUN chmod 0744 /the_script


ИЛИ, убедитесь , что ваша работа сама переадресовать непосредственно на стандартный вывод / STDERR вместо лог - файл, как описано в hugoShaka «s ответ :

 * * * * * 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

Эрик добавляет в комментариях :

Обратите внимание, что tailможет не отображаться правильный файл, если он создан во время сборки образа.
Если это так, вам нужно создать файл или коснуться его во время выполнения контейнера, чтобы хвост выбрал правильный файл.

См. « Вывод tail -fв конце докера CMDне отображается ».


1
Сначала мне нужно было установить cron, так как он не включен. Но добавив это в Dockerfile, он работает. Спасибо! RUN apt-get update && apt-get install cron
С Хейер

2
вам, вероятно, следует добавить -yустановку cron, чтобы избежать выхода из сборки Docker
gafi

1
@ Гаафар Правильно! Я включил ваш комментарий в ответ для большей наглядности и добавил еще один вариант.
VonC

6
Это решение все еще работает? Когда я следую приведенным рекомендациям, при входе в контейнер с правами root и type crontab -lя получаю No crontab, установленный для root , также мой экран остается пустым. Однако, когда я проверяю '/etc/cron.d/', я вижу, что поле crontab есть (и что еще более удивительно), когда я проверяю /var/log/cron.log, я вижу, что скрипт работает (к содержимому файла добавляется Hello World). Я потянув этот образ в моем Dockerfile: FROM phusion/baseimage:0.10.0. Есть идеи по поводу несоответствия в поведении?
Гомункул Ретулли

11
С 2018 года этот подход больше не работает; кто-нибудь смог заставить их cronjob работать с Ubuntu в качестве базового образа? Я не заинтересован в альпийском образе, который идет с cron, бегущим из коробки
пеликан

148

Принятое решение может быть опасным в производственной среде .

В Docker вы должны выполнять только один процесс для каждого контейнера, потому что если вы этого не сделаете, процесс, который разветвился и ушел в фоновый режим, не отслеживается и может остановиться, если вы этого не знаете.

Когда вы используете CMD cron && tail -f /var/log/cron.logпроцесс cron в основном форк для выполнения cronв фоновом режиме, основной процесс завершается и позволяет вам выполнять tailfна переднем плане. Фоновый процесс cron может остановиться или потерпеть неудачу, вы не заметите, ваш контейнер по-прежнему будет работать без вывода сообщений, а инструмент оркестровки не перезапустит его.

Этого можно избежать, перенаправив команды вывода cron непосредственно в ваш докер stdoutи stderrрасположенные соответственно в /proc/1/fd/1и /proc/1/fd/2.

Используя базовые перенаправления оболочки, вы можете сделать что-то вроде этого:

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

И ваш CMD будет: CMD ["cron", "-f"]


14
Хороший: cron -fдля "cron foreground". Я включил ваш ответ выше, для большей наглядности. +1
VonC

Допустим, моя программа ничего не выводит. Могу ли я использовать этот метод и быть уверенным, что мой процесс не остановится в фоновом режиме?
Arcsector

1
@Arcsector этот метод позволяет избежать размещения процесса в фоновом режиме, поэтому он не завершается с ошибкой. Фоновый процесс в контейнере Docker не прост. Если вы хотите иметь запущенный фоновый процесс, вы можете использовать процесс init для мониторинга нескольких процессов, которые вы запускаете в контейнере. Другой способ - запустить процесс в другой контейнер рядом с основным контейнером, который называется «коляска». Лучше всего часто избегать нескольких процессов в контейнере.
hugoShaka

Красиво и чисто! Люблю это :)
AmaelH

1
Это хорошее решение и хорошо работает для нас, кроме одной проблемы. Когда контейнер получает сигнал SIGTERM, он, похоже, не ожидает завершения запланированного процесса и корректно завершает работу, вместо этого он убивает процесс, что может вызвать проблемы.
Джеймс Халс

107

Для тех, кто хочет использовать простое и легкое изображение:

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

10
Простое, легкое и стандартное изображение на основе. Это должен быть принятый ответ. Также используйте > /proc/1/fd/1 2> /proc/1/fd/2перенаправление для доступа к выходу cronjobs непосредственно из журналов докера.
HenriTel

2
Для людей, не использующих alpine: crond, поддерживающий -d 8параметр, не является стандартным cron, это команда crond из busybox. Например, из Ubuntu, вы можете запустить это как busybox crond -f -d 8. Для более старых версий вы должны использовать -L /dev/stdout/.
Trendfischer

2
Я бы дал это +100, если бы мог. На сегодняшний день это лучший способ запуска заданий cron в среде Docker.
Джитсусама

1
Если вы не хотите создавать новый образ каждый раз, когда меняете задание cron (или если вам нужно несколько), вы можете просто запустить Alpine и использовать громкость для установки cron. Я проверил его, используя следующее: docker run -v ${PWD}/cronjobs:/etc/crontabs/root alpine:3.6 crond -f -d 8. @Groostav вы можете использовать аналогичные вещи в Docker Compose.
Duality_

1
CMD ["crond"или CMD ["cron"?
Кузнец

38

То, что предложил @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

1
Интересная альтернатива. +1
VonC

2
RUN apt-get update && apt-get -y install cronиначе он не сможет найти посылкуcron
alphabetasoup

2
Спасибо, Юнесс, вы дали мне идею сделать следующее, что сработало в моем случае, когда каждый крон указан в отдельном файле: RUN cat $APP_HOME/crons/* | crontab как талисман :)
marcostvz

добавление cronв сценарий точки входа кажется лучшим вариантом: ENTRYPOINT ["entrypoint.sh"]
bozdoz

20

Есть еще один способ сделать это - использовать 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 с полной документацией:

http://github.com/opsxcq/tasker


Dockerception (запуск Docker-контейнеров из другого контейнера) является плохой практикой и должен быть ограничен непрерывной интеграцией. Обходной путь должен был бы использоваться docker execна указанных контейнерах.
HenriTel

1
Tasker не использует docker в docker (Dind / Dockerception), обратите внимание, что в качестве отображения передается сокет docker, все порождаемые контейнеры находятся в демоне, который запускает tasker. И если вы не хотите запускать Tasker внутри Docker, вы можете просто развернуть его как любое другое приложение.
OPSXCQ

1
Я не получаю преимущества использования Tasker. Мне кажется излишним использование java и sh *** только для запуска cron.
Карл Адлер

Смешивание cron и базового образа, который вам нужен (например, python / node), создает дополнительную зависимость, которую необходимо поддерживать и развертывать. В этом сценарии все задания совместно используют один и тот же контейнер, это означает, что вам нужно беспокоиться об очистке всего после каждая работа выполняется. Работы, выполняемые на таскере, являются идемпотентными, поэтому вам не о чем беспокоиться.
OPSXCQ

13

Ответ VonC довольно тщательный. Кроме того, я хотел бы добавить одну вещь, которая помогла мне. Если вы просто хотите запустить задание cron без привязки файла, у вас возникнет соблазн просто удалить команду && tail -f /var/log/cron.logиз команды cron.

Однако это приведет к выходу контейнера Docker вскоре после запуска, потому что, когда команда cron завершается, Docker считает, что последняя команда завершилась, и, следовательно, убивает контейнер. Этого можно избежать, запустив cron на переднем плане через cron -f.


9

Хотя это направлено на запуск заданий рядом с запущенным процессом в контейнере через execинтерфейс Docker , это может вас заинтересовать.

Я написал демон, который наблюдает за контейнерами и планирует задания, определенные в их метаданных. Пример:

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

«Классическая», cron-подобная конфигурация также возможна.

Вот документы , вот хранилище изображений .


Спасибо. Этот ответ является наиболее правильным для среды контейнеров Docker. Никаких изменений в изображениях Docker, только добавление специального контейнера для выполнения задач, он работает как команда docker exec <container_name> <some_command>по расписанию.
PRIHLOP

Это самый простой и понятный ответ.
Ибрагим Авад

9

Я создал образ 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

Для справки, изображение здесь .


Интересное изображение. +1
VonC

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

В supervisor.log я получаю сообщение об ошибке, что служба cron останавливается несколько раз и переходит в состояние FATAL. Однако cron, кажется, работает на вершине и выполняет cronjobs нормально. Спасибо за это!
lephleg

Да, то же самое случилось со мной, но это работает как обычно, так что не беспокойтесь.
Сагар Гуге

5

Определите 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 Swarm. Получение myservice unknownошибок.
Марк Граймс

Должно быть предупреждение о высоком уровне безопасности при установке разъема док-станции
случайно

4

Если вы используете docker для окон, помните, что вам нужно изменить формат конца строки с CRLF на LF (то есть с dos на unix), если вы собираетесь импортировать файл crontab из windows в контейнер ubuntu. Если нет, ваша работа не будет работать. Вот рабочий пример:

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

На самом деле это заняло у меня несколько часов, поскольку отладка заданий cron в Docker-контейнерах - утомительная задача. Надеюсь, это поможет всем, кто не может заставить свой код работать!


3

Из приведенных выше примеров я создал эту комбинацию:

Alpine Image & Edit с использованием Crontab в Nano (я ненавижу 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

2

Настроить cron параллельно с разовой работой

Создайте файл сценария, скажем, run.sh, с заданием, которое должно периодически выполняться.

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

Сохранить и выйти.

Используйте Entrypoint вместо CMD

Если у вас есть несколько заданий, которые нужно запустить во время контейнера докера, используйте файл точки входа, чтобы запустить их все.

Файл точки входа - это файл сценария, который вступает в действие при выполнении команды запуска Docker. Итак, все шаги, которые мы хотим выполнить, могут быть помещены в этот файл скрипта.

Например, у нас есть 2 задания для запуска:

Запустить один раз задание : echo «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, который был установлен в файле

* * * * *: Расписание Cron; работа должна выполняться каждую минуту. Вы можете обновить расписание на основе ваших требований.

/run.sh: Путь к файлу скрипта, который должен периодически запускаться

/var/log/cron.log: Имя файла для сохранения вывода запланированного задания cron.

2>&1: Журналы ошибок (если таковые имеются) также будут перенаправлены в тот же выходной файл, который использовался выше.

Примечание : не забудьте добавить дополнительную новую строку, так как это делает ее действительным cron. Scheduler.txt: полная установка cron будет перенаправлена ​​в файл.

Использование системных / пользовательских переменных среды в cron

Моя настоящая работа cron ожидала большинство аргументов, поскольку переменные окружения передавались команде запуска docker. Но с 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!


1
@himanshuIIITian Я попробовал это, проблема в том, что сценарий "запустить один раз" никогда не возвращается, а также кукуруза -f не возвращается, так что ... это не работает для меня, какие-нибудь идеи? спасибо
Дорон Леви

@DoronLevi - не могли бы вы поделиться некоторыми журналами, чтобы разобраться в проблеме? Или вы можете проверить весь код отсюда - github.com/nehabhardwaj01/docker-cron
himanshuIIITian

Спасибо за ответ. Я рад, что ответ был полезным.
himanshuIIITian

1

Задания Cron хранятся в / var / spool / cron / crontabs (общее место во всех известных мне дистрибутивах). Кстати, вы можете создать вкладку cron в bash, используя что-то вроде этого:

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

Это создаст временный файл с задачей cron, а затем запрограммирует его с помощью crontab. Последняя строка удаляет временный файл.


cronДемон обычно не работает в контейнере.
Мэтт

@BhargavNanekalva это должно быть специально настроено в контейнере, к которому этот ответ не относится.
Мэтт

@ Matt, не могли бы вы указать, как именно это должно быть указано в контейнере? , Я делаю crontab -l и команда показывается - kagda.ru/i/6d014816d43_29-06-2017-11:38:59_6d01.png но все еще не запускается
Тебе

@ Копать_Шо_я_нашел Вы должны работать crondв дополнение к службе, которую вы используете в контейнере, обычно с менеджером службы, таким как s6. Вероятно, задайте это как вопрос, чтобы получить правильный ответ
Мэтт

1

При работе с некоторыми обрезанными изображениями, которые ограничивают доступ root, мне пришлось добавить своего пользователя в 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

Может быть, это помогает кому-то


Я считаю, что изображение узла использует узел пользователя; так что, возможно, вам нужно было добавить разрешения для этого пользователя
bozdoz

1

Итак, моя проблема была той же. Исправление состояло в том, чтобы изменить командный раздел в docker-compose.yml.

Из

команда: crontab / etc / crontab && tail -f / etc / crontab

к

команда: crontab / etc / crontab

команда: tail -f / etc / crontab

Проблема была «&&» между командами. После удаления все было нормально.


-1

Самый надежный способ, который я нашел до сих пор, это запустить независимый контейнер cron - установить клиент Docker и связать, смонтировать докер Sock, чтобы вы могли общаться с сервером Docker на хосте.

Затем просто используйте env vars для каждого задания cron и скрипт точки входа для генерации / etc / crontab

Вот изображение, которое я создал, используя этот принцип и используя его в производстве в течение последних 3-4 лет.

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


Ответы должны быть автономными, а не ссылаться на внешние ресурсы
Николас Булиан,

-2

Попробуйте использовать часовой механизм для планирования задач. Следуйте инструкциям, приведенным в этой ссылке.

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

Вы можете вызвать задачу rake внутри файла lib / clock.rb, как показано ниже.

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

1
Хорошая идея использовать какой-то другой инструмент для планирования. Тем не менее, этот вопрос задан специально для cron, так что ваше предложение было бы лучше в качестве комментария к вопросу на мой взгляд.
Ричард Кифер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.