Как запустить cron в docker

Иногда необходимо запустить какой-то повторяющийся процесс внутри докера. В зависимости от того что конкретно нужно сделать есть несколько способов решить эту задачу.

В docker нужно запускать только повторяющий процесс

Вот пример задачи: каждую минуту нужно запускать в докере скрипт, который записывает в файл текущую дату и время. Задача искусственная, но на ее примере станет понятно как решать подобные задачи.

Итак. Создаем файл cron со следующим содержимым:

* * * * *   root    date >> /data/asdf

В той же папке с файлом cron создаем файл Dockerfile:

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y \
    cron \
    rsyslog

COPY cron /etc/cron.d/sample

RUN mkdir /data

CMD service rsyslog start && service cron start && tail -f /var/log/syslog

Теперь нужно собрать образ. Выполняем команду:

docker build --tag sample_cron .

Через некоторое время команда отработает и у нас появится новый докерный образ с именем sample_cron. А дальше его нужно запустить (при запуске образа появляется работающий контейнер):

docker run \
    --detach \
    --volume `pwd`/data:/data \
    --name sample_cron \
    sample_cron

Эта команда мгновенно отработает и выдаст на экран id контейнера, что-то вроде:

6c5544ff1b0cea7e235aa53904b8372c766444633f60896b1fd86681480e5a3e

Итак — мы сделали докерный образ и запустили из него контейнер. Внутри контейнера работает крон, который каждую минуту записывает в файл /data/asdf текущую дату и время. При запуске контейнера мы указали

--volume `pwd`/data:/data

благодаря этому то что находится внутри контейнера в папке /data становится доступно на хост машине в папке data, там где мы запускали контейнер.

Действительно, если заглянуть в эту папку на хост машине, то можно увидеть этот файл:

$ tree data
data
└── asdf

0 directories, 1 file
$ cat data/asdf
Sun Jun 11 12:04:01 UTC 2017
Sun Jun 11 12:05:01 UTC 2017
Sun Jun 11 12:06:01 UTC 2017
Sun Jun 11 12:07:01 UTC 2017
Sun Jun 11 12:08:01 UTC 2017
Sun Jun 11 12:09:01 UTC 2017

Так же можно подключится к докерному контейнеру и посмотреть что выводить процесс, который работает в контейнере:

docker logs -f sample_cron

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

Jun 11 12:04:01 6c5544ff1b0c CRON[38]: (root) CMD (   date >> /data/asdf)
Jun 11 12:05:01 6c5544ff1b0c CRON[41]: (root) CMD (   date >> /data/asdf)
Jun 11 12:06:01 6c5544ff1b0c CRON[44]: (root) CMD (   date >> /data/asdf)
Jun 11 12:07:01 6c5544ff1b0c CRON[47]: (root) CMD (   date >> /data/asdf)
Jun 11 12:08:01 6c5544ff1b0c CRON[50]: (root) CMD (   date >> /data/asdf)
Jun 11 12:09:01 6c5544ff1b0c CRON[53]: (root) CMD (   date >> /data/asdf)
Jun 11 12:10:01 6c5544ff1b0c CRON[56]: (root) CMD (   date >> /data/asdf)
Jun 11 12:11:01 6c5544ff1b0c CRON[59]: (root) CMD (   date >> /data/asdf)

После нужно выполнить команду:

docker rm -f sample_cron

Это остановит и удалит контейнер.

Итак, нам удалось запустить крон в докере. Чуть-чуть подробностей про содержимое Dockerfile.

Первая команда в Dockerfile — это FROM. Эта команда говорит на базе какого образа мы создаем новый образ. Мы используем ubuntu:16.04. На текущий момент — это последняя LTS версия ubuntu, сейчас стоит использовать именно эту версию. Можно бы бы использовать другой образ в качестве базы, но ubuntu — это хороший дистрибутив.

FROM ubuntu:16.04

Следующая команда RUN — она написана на трех строчках. Эта команда запускается при сборке образа и она устанавливает указанные пакеты в систему.

RUN apt-get update && apt-get install -y \
    cron \
    rsyslog

Дальше идет команда COPY — она копирует файл cron в создаваемый докерный образ. Результат работы этой команды — в докерном образе появляется файл /etc/cron.d/sample

COPY cron /etc/cron.d/sample

Следующая команда опять RUN — она создает папку /data внутри докерного образа.

RUN mkdir /data

Последняя команда — это CMD, эта команда говорит что нужно выполнить при при запуске докерного образа. Тут мы говорим что нужно запустить сервис rsyslog, потом нужно запустить сервис cron, а потом нужно запустить бесконечный вывод на экран содержимого файла. Докер контейнер работает до тех пор пока работает процесс указанный в CMD. Если бы мы не указали tail -f, то при запуске контейнера, он бы запустился, запустил в фоне сервисы rsyslog и cron и тут же бы вышел. Команда tail -f нужна для того чтобы контейнер работал.

CMD service rsyslog start && service cron start && tail -f /var/log/syslog

В docker нужно запускать повторяющийся процесс, плюс еще другие процессы

Вот пример задачи. Нужно в докере запустить две вещи. Во-первых, нужно чтобы в докере работал крон который каждую минуту создает пустой файл с текущим timestamp в качестве имени. Во-вторых, нужно чтобы из контейнера торчал веб-сервер, который позволит просмотреть эти файлы.

Снова пишем файл cron. Для того чтобы создать файл с текущим timestamp нужно выполнить вот такую команду:

touch /data/`date +%s`

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

* * * * *   root    touch /data/`date +\%s`

В качестве веб сервера можно использовать кучу всего. Например, можно использовать python однострочник, но в этом примере я буду использовать nginx. Создаем файл nginx.conf:

daemon off;

events {
}

http {
    server {
        listen 80;

        location / {
            autoindex on;
            root /data/;
        }
    }
}

А дальше начинается специфика докера. В контейнере может быть запущен только один главный процесс. Поскольку нам нужно запустить сразу 2 процесса, то нам нужна еще некая обертка которая запустит оба эти процесса. Вот эта обертка — это программа supervisor, штука, которая позволяет запускать другие процессы.

Пишем файл supervisor.conf, в нем описываем оба процесса которые нужно будет запускать:

[supervisord]
nodaemon=true

[program:nginx]
command=nginx -c /etc/nginx/nginx.conf

[program:cron]
command=cron -f

И дальше пишем Dockerfile:

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y \
    cron \
    nginx \
    supervisor

COPY nginx.conf /etc/nginx/nginx.conf
COPY supervisor.conf /etc/supervisor/conf.d/supervisor.conf
COPY cron /etc/cron.d/sample

RUN mkdir /data

EXPOSE 80

CMD /usr/bin/supervisord

Собираем образ:

docker build --tag sample_cron2 .

И запускаем его:

docker run \
    --detach \
    --publish 80:80 \
    --name sample_cron2 \
    sample_cron2

После этого можно зайти браузером на адрес на котором работает докер и сначала увидеть там пустую папку, а через минуту увидеть файл, который создал крон.

nginx пустой index of

nginx index of с одним файлом

Можно зайти в контейнер и увидеть иерархию процессов:

$ docker exec -it sample_cron2 bash
root@59cbe56fce04:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4508   796 ?        Ss   17:58   0:00 /bin/sh -c /usr/bin/supervisord
root         5  0.0  1.7  48036 18256 ?        S    17:58   0:00 /usr/bin/python /usr/bin/supervisord
root         8  0.0  0.2  26068  2548 ?        S    17:58   0:00  \_ cron -f
root         9  0.0  0.9 124972  9728 ?        S    17:58   0:00  \_ nginx: master process nginx -c /etc/nginx/nginx.conf
nobody      10  0.0  0.3 125164  3300 ?        S    17:58   0:00      \_ nginx: worker process
root        37  0.0  0.3  18208  3212 ?        Ss   18:01   0:00 bash
root        49  0.0  0.2  34424  2860 ?        R+   18:01   0:00  \_ ps auxf
root@59cbe56fce04:/#

Другие способы для работы с кроном и докером

Мы рассмотрели два способа работы с кроном в докере:

Но так же возможен и другой способ работы. Можно использовать cron не внутри докера, а на той же машине на которой работает докер. Например, можно написать такой крон на хост машине:

* * * * *   root    docker run --rm ubuntu:16.04 date >> /tmp/dates

Каждую минуту будет запускаться короткоживущий контейнер. Он будет выполнять команду date и тут же завершать свою работу. Результат этой работы будет сохранятся в файл /tmp/dates на хост машине.

Еще один пример. На хост машине работает докер контейнер с nginx. Он был запущен вот так:

docker run \
    --detach \
    --publish 80:80 \
    --name nginx \
    nginx:1.13.1

Если на хост машине создать вот такой cron:

* * * * *   root    docker exec nginx sh -c 'date >> /usr/share/nginx/html/dates.html'

То каждую минуту внутри контейнера (именно внутри контейнера, а не на хост машину) будет выполнятся команда:

date >> /usr/share/nginx/html/dates.html

И к этому файлу можно будет обратится через браузер.

Иван Бессарабов
ivan@bessarabov.ru

11 июня 2017