Так уж вышло, что запуск cron в Docker-контейнере — дело весьма специфическое, если не сказать сложное. В сети полно решений и идей на эту тему. Вот один из самых популярных (и простых) способов запуска:
cron -f

Но такое решение (и большинство других тоже) обладает рядом недостатков, которые сходу обойти достаточно сложно:
  • неудобство просмотра логов (команда docker logs не работает)
  • cron использует свой собственный Environment (переменные окружения, переданные при запуске контейнера, не видимы для cron заданий)
  • невозможно нормально (gracefully) остановить контейнер командой docker stop (в конце концов в контейнер прилетает SIGKILL)
  • контейнер останавливается с ненулевым кодом ошибки


Logs


Проблему просмотра логов с использованием стандартных средств Docker устранить сравнительно легко. Для этого достаточно принять решение о том, в какой файл будут писать свои логи cron-задания. Предположим, что это /var/log/cron.log:
* * * * * www-data task.sh >> /var/log/cron.log 2>&1

Запуская после этого контейнер при помощи команды:
cron && tail -f /var/log/cron.log

мы всегда сможем видеть результаты выполнения заданий при помощи «docker logs».

Аналогичного эффекта можно добиться воспользовавшись перенаправлением /var/log/cron.log в стандартный вывод контейнера:
ln -sf /dev/stdout /var/log/cron.log

UPD: Такой способ работать не будет по этой причине.

Если cron-задания пишут логи в разные файлы, то, скорее всего, предпочтительнее будет вариант с использованием tail, который может «следить» за несколькими логами одновременно:
cron && tail -f /var/log/task1.log /var/log/task2.log

UPD: Файл(ы) для лога удобнее создавать в виде named pipe (FIFO). Это позволит избежать накопления внутри контейнера ненужной информации, а задачи log rotate возложить на Docker. Пример:
mkfifo --mode 0666 /var/log/cron.log


Environment variables


Изучая информацию на тему назначения переменных окружения для задач cron, выяснил, что последний может использовать так называемые подключаемые модули аутентификации (PAM). Что на первый взгляд является не относящимся к сабжу теме фактом. Но у PAM есть возможность определять и переопределять любые переменные окружения для служб, которые его (точнее их, модули аутентификации) используют, в том числе и для cron. Вся настройка производится в файле /etc/security/pam_env.conf (в случае Debian/Ubuntu). То есть любая переменная, описанная в этом файле, автоматически попадает в Environment всех cron-заданий.

Но есть одна проблема, точнее даже две. Синтаксис файла (его описание) при первом взгляде может ввести в ступор обескуражить. Вторая проблема — это как при запуске контейнера перенести переменные окружения внутрь pam_env.conf.

Опытные Docker-пользователи насчет второй проблемы наверняка сразу скажут, что можно воспользоваться лайфхаком под названием docker-entrypoint.sh и будут правы. Суть этого лайфхака заключается в написании специального скрипта, запускаемого в момент старта контейнера, и являющегося входной точкой для параметров, перечисленных в CMD или переданных в командной строке. Скрипт можно прописать внутри Dockerfile, например, так:
ENTRYPOINT ["/docker-entrypoint.sh"]

А его код при этом должен быть написан специальным образом:
docker-entrypoint.sh
#!/usr/bin/env bash
set -e

# код переноса переменных окружения в /etc/security/pam_env.conf

exec "$@"


Вернемся к переносу переменных окружения немного позже, а пока остановимся на синтаксисе файла pam_env.conf. При описании любой переменной в этом файле значение можно указать c помощью двух директив: DEFAULT и OVERRIDE. Первая позволяет указать значение переменной по умолчанию (если та вообще не определена в текущем окружении), а вторая позволяет значение переменной переопределить (если значение этой переменной в текущем окружении есть). Помимо этих двух кейсов, в файле в качестве примера описаны более сложные кейсы, но нас по большому счету интересует только DEFAULT. Итого, чтобы определить значение для какой-нибудь переменной окружения, которая затем будет использовать в cron, можно воспользоваться таким примером:
VAR DEFAULT="value"

Обратите внимание на то, что value в данном случае не должно содержать названий переменных (например, $VAR), потому как контекст файла выполняется внутри целевого Environment, где указанные переменные отсутствуют (либо имеют другое значение).

Но можно поступить еще проще (и такой способ почему-то не описан в примерах pam_env.conf). Если вас устраивает, что переменная в целевом Environment будут иметь указанное значение, независимо от того, определена она уже в этом окружении или нет, то вместо вышеупомянутой строки можно записать просто:
VAR="value"

Тут следует предупредить о том, что $PWD, $USER и $PATH вы не сможете заменить для cron-заданий при любом желании, потому как cron назначает значения этих переменных исходя из своих собственных убеждений. Можно, конечно, воспользоваться различными хаками, среди которых есть и рабочие, но это уже на ваше усмотрение.

Ну и наконец, если нужно перенести все текущие переменные в окружение cron-заданий, то в этом случае можно использовать такой скрипт:
docker-entrypoint.sh
#!/usr/bin/env bash
set -e

# переносим значения переменных из текущего окружения
env | while read -r LINE; do  # читаем результат команды 'env' построчно
    # делим строку на две части, используя в качестве разделителя "=" (см. IFS)
    IFS="=" read VAR VAL <<< ${LINE}
    # удаляем все предыдущие упоминания о переменной, игнорируя код возврата
    sed --in-place "/^${VAR}/d" /etc/security/pam_env.conf || true
    # добавляем определение новой переменной в конец файла
    echo "${VAR} DEFAULT=\"${VAL}\"" >> /etc/security/pam_env.conf
done

exec "$@"


Поместив скрипт «print_env» в папку /etc/cron.d внутри образа и запустив контейнер (см. Dockerfile), мы сможем убедиться в работоспособности этого решения:
print_env
* * * * * www-data env >> /var/log/cron.log 2>&1


Dockerfile
FROM debian:jessie

RUN apt-get clean && apt-get update && apt-get install -y cron

RUN rm -rf /var/lib/apt/lists/*

RUN mkfifo --mode 0666 /var/log/cron.log

COPY docker-entrypoint.sh /

COPY print_env /etc/cron.d

ENTRYPOINT ["/docker-entrypoint.sh"]

CMD ["/bin/bash", "-c", "cron && tail -f /var/log/cron.log"]


запуск контейнера
docker build --tag cron_test .
docker run --detach --name cron --env "CUSTOM_ENV=custom_value" cron_test
docker logs -f cron  # нужно подождать минуту



Graceful shutdown


Говоря о причине невозможности нормального завершения описанного контейнера с cron, следует упомянуть о способе общения демона Docker с запущенной внутри него службой. Любая такая служба (процесс) запускается с PID=1, и только с этим PID Docker умеет работать. То есть каждый раз, когда Docker посылает управляющий сигнал в контейнер, он адресует его процессу с PID=1. В случае с «docker stop» это SIGTERM и, если процесс продолжает работу, через 10 секунд SIGKILL. Так как для запуска используется "/bin/bash -c" (в случае с «CMD cron && tail -f /var/log/cron.log» Docker все равно использует "/bin/bash -c", просто неявно), то PID=1 получает процесс /bin/bash, а cron и tail уже получают другие PID, предугадать значения которых не представляется возможным по очевидным причинам.

Вот и выходит, что когда мы выполняем команду «docker stop cron» SIGTERM получает процесс "/bin/bash -с", а он в этом режиме игнорирует любой полученный сигнал (кроме SIGKILL, разумеется).

Первая мысль в этом случае обычно — надо как-то «кильнуть» процесс tail. Ну это сделать достаточно легко:
docker exec cron killall -HUP tail

Круто, контейнер тут же прекращает работу. Правда насчет graceful тут есть некоторые сомнения. Да и код ошибки по прежнему отличен от нуля. В общем, я не смог продвинуться в решении проблемы, следуя этим путем.

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

True graceful shutdown with zero exit code


Остается только одно — написать отдельный скрипт запуска демона cron, умеющий при этом правильно реагировать на управляющие сигналы. Относительно легко, даже если раньше на bash'е писать не приходилось, можно найти информацию о том, что в нем есть возможность запрограммировать обработку сигналов (при помощи команды trap). Вот как, к примеру, мог бы выглядеть такой скрипт:
start-cron
#!/usr/bin/env bash

# запускаем cron
service cron start

# ловим SIGINT или SIGTERM и выходим
trap "service cron stop; exit" SIGINT SIGTERM


если бы мы могли каким-то образом заставить этот скрипт работать бесконечно (до получения сигнала). И тут на помощь приходит еще один лайфхак, подсмотренный тут, а именно — добавление в конец нашего скрипта такой строчки:
tail -f /var/log/cron.log & wait $!

Или, если cron-задания пишут логи в разные файлы:
tail -f /var/log/task1.log /var/log/task2.log & wait $!


Заключение


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

В конце привожу полные листинги Dockerfile и start-cron, которыми я пользуюсь сейчас.
start-cron
#!/usr/bin/env bash

# переносим значения переменных из текущего окружения
env | while read -r LINE; do  # читаем результат команды 'env' построчно
    # делим строку на две части, используя в качестве разделителя "=" (см. IFS)
    IFS="=" read VAR VAL <<< ${LINE}
    # удаляем все предыдущие упоминания о переменной, игнорируя код возврата
    sed --in-place "/^${VAR}/d" /etc/security/pam_env.conf || true
    # добавляем определение новой переменной в конец файла
    echo "${VAR} DEFAULT=\"${VAL}\"" >> /etc/security/pam_env.conf
done

# стартуем "демона"
tail -f /var/log/cron.log &

# сохраняем PID последней запущенной службы
PID=$!

# запускаем cron
service cron start

# ловим SIGINT или SIGTERM и выходим
trap "service cron stop; kill ${PID}; exit" SIGINT SIGTERM

# ожидаем завершения работы
wait ${PID}


Dockerfile
FROM debian:jessie

RUN apt-get clean && apt-get update && apt-get install -y cron

RUN rm -rf /var/lib/apt/lists/*

RUN mkfifo --mode 0666 /var/log/cron.log

COPY start-cron /usr/sbin

COPY cron.d /etc

CMD start-cron


UPD: Обновил листинги с учетом использования named pipe (FIFO).
Поделиться с друзьями
-->

Комментарии (81)


  1. Sild
    12.07.2016 14:19
    +3

    Для ленивых, сделаю краткую выжимку: используйте свой entrypoint.sh и делайте там все, что захотите.


    1. stavinsky
      12.07.2016 16:36

      Поддерживаю. Я бы только поменял на что-то типа supervisord. Мне лично очень помогало на опеншифтовых контейнерах.


      1. niksite
        17.07.2016 00:15

        Это тогда будет дублирование функциональности.
        Докер сам супервизором работать умеет и работает. Зачем еще один супервизор в контейнере?


        1. stavinsky
          21.07.2016 21:40

          Поддерживаю. Но иногда требуется запустить несколько процессов. И это немного лучше чем баш скрипт. Конечно это IMHO.


          1. symbix
            22.07.2016 03:27

            Граница, думаю, проходит где-то на том моменте, когда баш-скрипт начинает напоминать инит-систему. :)


  1. mcleod095
    12.07.2016 14:27
    +6

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


    1. gibson_dev
      12.07.2016 16:20

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


    1. Igelko
      12.07.2016 16:22
      +2

      а если в контейнера надо запускать consul-template, помимо основного сервиса?


      1. rudenkovk
        12.07.2016 16:23

        по мне, consul-template, как и крон выносятся на хост тачку.
        Хотя тут и есть вопросы с swarm и прочим.


        1. Igelko
          12.07.2016 16:34

          мне в этом решении резко не понравилось, что
          1) в этом случае контейнер перестаёт быть самодостаточной сущностью.
          2) конфиг консул-темплейта на хосте становится сложным и зависит от состава запущенных контейнеров.
          3) нужно монтировать внутрь ещё и конфиги.
          т.е. выходит циклическая зависимость хост <-> контейнер, которую хотелось бы избежать.


          1. rudenkovk
            12.07.2016 16:36
            -1

            1) точно так же он перестает ей быть, когда начинается решение неспецифических задач в нем. Например 2 сервиса, или куча шаблонизаторов конфигов.
            2) я решил лейблами докер-сервиса
            3) с моей точки зрения это гораздо удобнее, чем контролировать сервисы внутри.


            Ну а так выходит непредсказуемость поведения контейнера.


            1. Igelko
              12.07.2016 16:45

              1) генерация конфига для работоспособности содержимого — имхо вполне специфичная задача, которая имеет непосредственную связь 1 к 1 с сервисом.
              2) а можно меня носом в мануал ткнуть, пожалуйста? я поизучаю на досуге.
              3) это неудобно только тем, что неправильный шаблон может обнаружиться только после сборки тестинга (имеем поломанный билд). зато это очень удобно, что ты можешь выкинуть контейнер на любую машинку в и он сам себя правильно сконфигурит. Среда корректно определяется тем, какой консул-кластер — test / stage / prod.


              1. rudenkovk
                12.07.2016 16:49

                1) опять же, ваш выбор. я разницы, где держать конфиги, не вижу. А вот доп сервисы в контейнере вижу.
                2) https://docs.docker.com/engine/userguide/labels-custom-metadata/ отталкиваться от сюда. В swarm указываю, при старте конейнеров, условно, запустить 10 штук, на машина с лабелом блабла.
                3) Опять же, не вижу тут проблем. Вопрос решается системой управления конфигурации.


              1. SirEdvin
                13.07.2016 01:09

                Внесу небольшое дополнение по третьему пункту:


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


                1. Igelko
                  13.07.2016 12:27

                  по факту значимые значения конфигурации хранятся в консуле и их можно прямо оттуда поменять на лету — consul-template сам перегенерит конфиг и передёрнет приложение.
                  А если ломается структура конфига — это так и так новый релиз => пересборка нового образа.


      1. o_serega
        13.07.2016 11:51

        Докер образы тем и хороши, что они иммутабельны, и исходя из 12 факторов микросервисов, конфигурация, в идеале, должна быть через переменные окружения: «Приложение двенадцати факторов хранит конфигурацию в переменных окружения» 12factor.net/ru/config, если отталкиваться от всего этого, то, для приложений, которые хранят конфигурацию в конфиге, необходимо на каждое изменение пересобрать образ и заново задеплоить, ну это сугубо по букве закона, это да не удобно, но и всякие консул темплейты и conf.d внутри докер контейнера тоже, как по мне. Я бы предпочел такие приложения не пихать в докер. Все выше сказанное — сугубо мое имхо.


        1. Igelko
          13.07.2016 12:32
          +1

          у меня конфигурация — это потенциально жирный json с метадатой, который нужно иногда менять быстро по желанию левой пятки заказчика.
          Но при этом, ему не место в БД с обычными данными.
          Прокидывать его через переменные окружения — чудовищная идея, которая ещё и принуждает меня контейнер рестартовать, когда можно дать команду на перечитывание конфига.


          1. o_serega
            13.07.2016 15:42

            Тут вопрос в том, что docker, немного, не тот инструментарий, под ваши задачи. То, что вы его приспособили под свои нужды — это другой разговор. Заюзайте тот же etcd для хранения конфигов и научити свое приложение с ним работать. Вон kubernetes так и поступает, в качестве значения для ключа, он хранит json

            etcdctl get registry/services/specs/default/kubernetes|python -mjson.tool
            {
            «apiVersion»: «v1»,
            «kind»: «Service»,
            «metadata»: {
            «creationTimestamp»: «2016-07-03T12:13:31Z»,
            «labels»: {
            «component»: «apiserver»,
            «provider»: «kubernetes»
            },
            «name»: «kubernetes»,
            «namespace»: «default»,
            «uid»: «8ec05311-4117-11e6-ade8-3a3965313736»
            },
            «spec»: {
            «clusterIP»: «192.168.90.1»,
            «ports»: [
            {
            «name»: «https»,
            «port»: 443,
            «protocol»: «TCP»,
            «targetPort»: 443
            }
            ],
            «sessionAffinity»: «None»,
            «type»: «ClusterIP»
            },
            «status»: {
            «loadBalancer»: {}
            }
            }


            1. Igelko
              13.07.2016 17:21
              +1

              Оукееей. Допустим, это не моё приложение со специфичным конфигом. Например, rabbitmq или nginx, или тот же consul.
              Т.е. любое стороннее приложение, разработчиков которого более чем устраивают простые текстовые файлы (иногда длинные).
              consul-template пусть и неизящно, но позволяет подрихтовать конфиг на лету (без рестарта контейнера, что важно).

              Или приложение уже давным-давно написано и не хочется лезть и менять в его код — мы просто пакуем его в контейнер, подкладываем рядом consul-template и небольшой entrypoint, который позаботится, чтоб всё хорошо стартовало-умирало.
              Так мы относительно дёшево получаем неизменный артефакт для деплоя (здесь я мысленно проклял npm и миллиарды зависимостей).
              А обучение приложения работать напрямую с consul по api — уже отдельная задача, которую можно спокойно выкинуть в беклог с невысоким приоритетом.


              1. o_serega
                13.07.2016 17:31

                Я ранее писал, что можно натянуть ужа на шар, потребуется немного времени и костылей)


                1. Igelko
                  13.07.2016 17:42

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

                  Докер даёт очень хорошую гарантию — в прод едут ровно те бинари и те npm-пакеты. которые проверялись на тестинге.
                  Плюс при грамотной упаковке элементов в контейнеры становится плевать, на какой физической машинке они бегут и сколько экземпляров одинакового сервиса поднято — и это тоже удобно.
                  Наши приложения в большинстве своём stateless, поэтому docker или аналоги напрашивались. Опять же на решение влияет хайп, количество готовых костылей и подпорок^W^W^W инструментов, чтоб это работало.
                  Это можно решать через зипованные образы lxc / openvz, но количество готовых костылей и подпорок для них меньше, чем для докера.


                  1. o_serega
                    13.07.2016 17:48

                    Это Вам так кажется, что меньше костылей и подпорок для других решений. Я повторюсь еще раз, ваш юзкейс не шибко вписывается в идеологию и архитектуру docker. Вы не первый кто пытается уйти от навязанной докером архитектуры один контейнер — одно приложение и всеми силами из докер контейнера делаете аналог lxc, fleet и т.п., что бы внутри поиметь что-то наподобие нормальной инит системы.

                    Думаю можно остановиться на этом, каждый останется при своем мнении)


                    1. Igelko
                      13.07.2016 18:12

                      охотно верю. в будущем я осмотрюсь вокруг ещё раз и возможно выберу другой стек в соответствии с теми требованиями, которые будут стоять потом.


    1. renskiy
      12.07.2016 19:04

      Вы правильно заметили, что внутри контейнера не рекомендуется запускать более одного приложения. И я не собираюсь утверждать обратное. Но ведь вас никто не заставляет внутри контейнера запускать больше одного cron задания. Тем более, если мы можем при старте указать любую необходимую конфигурацию (через инъекцию Environment Variables) для вашых cron скриптов и запустить столько контейнеров, сколько различных заданий у вас имеется.

      И зачем мне использовать LXC, если у меня уже есть удобный, масштабируемый и единообразный для всех компонентов инструмент запуска приложений в контейнерах и проверенный временем и всем знакомый планировщик задач (cron)?


      1. shuron
        14.07.2016 17:45

        Тут фишка в том что не надо быть догматичным. Это хорошее правило…
        Но вы решаете что такое «аппликация» и сколько процессов она должна стартануть что быть самодостаточной.


        1. renskiy
          15.07.2016 09:20

          В «догме» нет ничего плохого, но важно понимать причину таких рекомендаций. В данном случае причиной является то, что Docker может управлять только одним процессом (с PID=1). Запуск нескольких служб внутри контейнера неизбежно приведет к тому, что внутри контейнера будет несколько служб с разными PID, что приведет к невозможности нормального функционирования контейнера.

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


          1. E_STRICT
            15.07.2016 09:54

            что приведет к невозможности нормального функционирования контейнера
            А в чём это будет проявлятся?


            1. renskiy
              15.07.2016 10:24

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


              1. E_STRICT
                15.07.2016 10:40

                Менеджер процессов решает это проблему полностью? На гитхабе туча докер сборок сделаных с помощью супервизора.


                1. renskiy
                  15.07.2016 11:01

                  Я так полагаю, что решает.


              1. E_STRICT
                15.07.2016 10:48

                trap «service cron stop; exit» SIGINT SIGTERM

                Можно ведь тоже самое сделать для любых других процессов.

                Пример с runit.


                1. renskiy
                  15.07.2016 11:11

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


            1. Igelko
              15.07.2016 10:26

              если запустить внутри пачку демонов тупо баш-скриптом, то остановить контейнер можно будет только по kill, что не есть хорошо — баш не сможет отправить сигналы демонам, которые от него отвязались.
              Т.е. требуется что-то вроде supervisord для рулёжки процессами в контейнере.
              Ну и неудобства в виде одного единственного stdout/stderr на весь контейнер, например.
              Т.е. нужно лишний раз включать голову и решать вопросы, которых бы и не возникло, не пользуйся мы докером.


    1. Suvitruf
      13.07.2016 01:27
      +2

      Один контейнер должен служить для конкретной задачи, но не для «одного приложения». Здесь неплохо этот момент описан. Так как нынче везде кричат про микросервисы, то рекомендую ещё смежную статью почитать Microservices — Not a free lunch!


    1. shuron
      14.07.2016 17:43

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


      Приложения, сервиса… и не обезательно что это один процесс, хотя проще, да…
      И вам решать что есть именно ваше приложение… Для микросервица очень вазжна акакрза его самодостаточность и его как можно меньшая зависимость от внешнего мира в плане конфигурации и прочих предположений о среде…


  1. rudenkovk
    12.07.2016 15:05
    +1

    Честно говоря, я с трудом представляю юзкейсы, где такое надо (про велосипеды молчим). Мне видится более удобным вариантом запускать контейнер системным кроном. Возможно на выделенной тачке.


    1. Igelko
      12.07.2016 16:26

      а если этот контейнер с cron сам дёргает по расписанию другие ручки в кластере? и совсем не хотелось бы, чтоб он умер вместе с машиной.
      этакий кластер-менеджмент для бедных.


      1. rudenkovk
        12.07.2016 16:27

        Мне думается это ошибка в архитектуре. Я не говорю о том, что таких кейсов не может быть. Но все таки контейнеру подразумевают какое-то CI и систему управления конфигурацией.


        1. Igelko
          12.07.2016 16:38

          Ну мне подумалось, что это может быть аналогом kubernetes для бедных — дёргаем скриптами alive-чеки, смотрим где плохо или наоборот лишнее и в автоматическом режиме дёргаем ручки для масштабирования.
          Это может быть вполне нормальным решением для портирования старой системы масштабирования вместо переезда на kubernetes / nomad / etc.

          P.S. у нас джобы по расписанию запускаются контейнеров, только они сделаны велосипедными библиотеками для nodejs.


          1. rudenkovk
            12.07.2016 16:43

            кстати, надо посомтреть поподробнее, что там предлагается.


            Я думаю, что крон — это изменение данных в некой центральной точке. Потому проще держать отдельный инстанс.


            1. Igelko
              12.07.2016 17:12
              +1

              наши крон-джобы — это наоборот, всякая периферия.
              Досылка данных в очередь, если мгновенная отправка не прошла по какой-то причине (все сервисы должны уметь в семантику one or more), файлообмен с внешними системами итд.
              Каждый адаптер для внешней системы — это отдельный сервис и нелогично отделять его работу по расписанию от него самого.


              1. rudenkovk
                12.07.2016 17:14

                Возможно. Хотя мне кажется можно сделать иначе.


  1. script88
    12.07.2016 15:19
    +3

    Это как раз тот случай, когда докер пытаются запихать туда, где его быть не должно.


  1. Merkat0r
    12.07.2016 15:26
    +4

    думаю предадут анафеме, но в нынешнем виде докер почти не нужен в проде…
    Он или много не умеет и нужны костыли или, что еще хуже, его пытаются запихнуть туда где он в принципе не нужен, но тупо моден сейчас


    1. erop
      12.07.2016 17:14
      -5

      Вот либо я не умею готовить, либо руки кривые, либо еще что-то. Но факт остается фактом…


      Пару лет назад, наверное, решил таки приспособить Vagrant для PHP разработки. Ведь все же кругом говорят о вагранте. Да и PhpStorm его круто поддерживает. Куча статей, мануалов, скринкастов… А MAMP же — это не круто, не модно и не молодежно. Началось уже не помню с чего, но, как оказалось, с Parallels (параллели использовал для запуска виндовой машины по работе, соответственно, без параллелей никак) Vagrant дружил не очень. Хорошо, давай ставить VirtualBox. Ой, VirtualBox не может жить вместе c Parallels? Пичалька… Ладно, давай разбираться как подружить Vagrant c параллелями. А, ну вот же, подружились! Запускаем!!! Ой, а чего так медленно читаем shared folder? Ведь параллели обещали, что все должно летать. Что, монитровать проект по ssh? Исходники на одной машине, а база на другой? Как-то непривычно. Ведь в MAMP'е же все лежит в одном месте! Э-э-э, ладно, MAMP, дружок, рано я от тебя отказался… Давай поработаем с тобой еще.


      Настает время Docker'а. Все вокруг, докер, да докер. MS — Docker, AWS — Docker, все нарядно и молодежно! Да вот уже и готовые есть Dockerfile'ы и docker-compose для моих фреймворков. Как же хорошо-то! Ладно, давай пощупаем, что это за докер такой. А, не виртуалка… ага… понятно… слава тнб, не вагрант снова. Да и приходится на виндовом десктопе работать. А MySQL ставить на винду как-то не красиво… ОК, буду приспосабливать MySQL работать в Docker'е! Ага, понятно, $ docker run mysql. Ура!!! Заработало!!! Ой, а что это скорость у MySQL вапще никакая?! И это так у всех (после непродолжительного гугления)? Хорошо, Docker, я с тобой не прощаюсь. Но давай, всего хорошего тебе, до лучших времен.


      В сухом остатке… ИМХО декларируемые фичи — это, безусловно, все очень круто и здорово. Но каждый раз натыкаешься на маленькие "но", которые практические перечеркивают все эти радости. И по сравнению с затраченным уже временем на ощупывание этих технологий быстрая сборка виртуалки в вагранте или мгновенный запуск контейнера докер уже не так греют душу.


      1. taulatin_one
        12.07.2016 22:05

        Это вы зря так батенька резко, тут адепты секты docker. Рассказали бы народу про запуск Windows 95 в контейнере docker или других чудесах инженерной.


      1. SirEdvin
        13.07.2016 00:53
        +1

        Про Vagrant:

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

        Про Docker:

        На Windows в официальных релизах docker создает виртуальную машину с минимальным линуском и запускает все в ней. То есть к обычном докеру еще все проблемы вашей виртуалки приехали. Судя по предыдущему опыту, они от нее.


      1. shuron
        14.07.2016 17:49

        Докер не то решает что вы пытаетесь там ковырять.
        Ну не нужен он вам, не используйте.
        Вот тут может о том почему это не замена виртуалке и не об этом вообще
        http://stackoverflow.com/questions/16047306/how-is-docker-different-from-a-normal-virtual-machine/25938347#25938347


    1. symbix
      12.07.2016 18:38
      +2

      Докер — это proof of concept, который оброс костылями и дополз до них до продакшена. :)

      В rkt пытаются сделать то же самое правильно, вроде неплохо получается.


      1. o_serega
        13.07.2016 18:10

        Ну пока у CoreOS кроме самой ос (хотя это под вопросом еще) неплохо взлетел etcd, но на coreos ставку делают ребята из кубернетса, возможно они будут развивать rkt, как вариант не быть залочеными на одном docker, избежать вендорлока, так сказать. Ну это мое мнение, оно, скорее всего, не отражает действительности


      1. shuron
        14.07.2016 17:49

        Да? И чем же?


  1. taulatin_one
    12.07.2016 18:07
    -3

    По моему мнению Docker запоздал так лет на 10. И его мнимая популярность держится на пиаре заинтересованных компаний. Как мы знаем, большинство готово «кушать» то, что им дают всякого рода гуру от маркетинга.

    А вот от рынка виртуализации можно ожидать появления легковесных виртуальных машин, в Microsoft это уже сделали, начиная с Windows 2012 имеется такая фича. Что в конечном итоге и убьет хипсторские контейнеры.


    1. Igelko
      12.07.2016 18:31
      +4

      Легковесные виртуалки (джейлы фряхи, openvz линукса, зоны в солярке) существуют и применяются значительно дольше, чем существует Docker.
      Тем не менее контейнеры почему-то полетели.
      Меня в докере подкупила идея разделения понятия образа и контейнера, а также иммутабельных слоёв ФС — это по-своему правильно и хорошо.
      Стало на порядок проще воспроизводить тот бардак зависимостей (npm-пакетов), с которыми у меня сейчас сервис в проде работает — взял ворох докер-образов таких же, как на проде — и ковыряешь.
      И докер не опоздал лет на 10, а пришёлся как раз вовремя — много компаний доросли до кластеров и начали задумываться над вопросом, а что делать, если нужно будет валить с одного облачного провайдера в другой.


      1. Merkat0r
        12.07.2016 18:49
        +2

        Почему полетело: соглашусь с комментарием выше — в его пиар какое-то нереальное кол-во бабла влили. Его презентовали вообще везде и всюду — от самых маленьких приватных митапов до всемирных конференций, не пропустив, вроде, вообще ни одной.
        Сам концепт то очень хорош, но уж больно костыльная реализация, что тоже выше уже заметили :)


        1. gunya
          12.07.2016 19:10

          docker при использовании docker-native сервисов работает в разы удобнее, чем при оркестрации через puppet+rpm.
          Костыли начинаются, когда приходится поднимать сервисы, которым гораздо лучше без docker. Например, когда у сервиса есть любое внутреннее состояние, когда перезапуск != удалить контейнер и создать новый.


      1. navion
        13.07.2016 01:28

        Стало на порядок проще воспроизводить тот бардак зависимостей (npm-пакетов)

        Поэтому и взлетел, с Rails такая же ерунда.


    1. navion
      13.07.2016 01:34

      можно ожидать появления легковесных виртуальных машин, в Microsoft это уже сделали, начиная с Windows 2012

      В 2012 ускорили загрузку обычных ВМ, а в 2016 сделают поддержку docker. VMware умеет форкать ВМ и это тоже используется для docker в vSphere Integrated Containers (хз кто ими пользуется, но всё же).


      1. taulatin_one
        13.07.2016 02:58
        +1

        Лишний раз доказывает, что IT-шный мир уже давно сошел с ума. Маркетинг преобладает над здравым смыслом.


        1. navion
          13.07.2016 14:09

          С точки зрения администрирования докер оправдан как средство развёртывания ПО на современных фреймворках с кучей зависимостей (Nodejs, Rails, Go), чтобы не привязывать сборку к конкретным версиям и не писать километровые установочные скрипты.
          А Instant Clones ещё используются для VDI, но это совсем другая тема.


          1. mcleod095
            13.07.2016 14:58

            Nodejs, Rails еще понятно, но как сюда попал go?
            С ним то вообще проблем нет, есть бинарник и нет зависимостей!


            1. navion
              13.07.2016 18:14

              Я не настоящий сварщик, но встречал нелестные отзывы в его сторону.


              1. mcleod095
                13.07.2016 18:34

                Можно много чего сказать хорошего и плохого, но зачем?
                Лучше один раз увидеть, чем 100 раз услышать.


              1. symbix
                14.07.2016 02:09

                Странные претензии какие-то. Товарищ не осилил godep?


  1. umputun
    12.07.2016 19:24
    +3

    В отличии от комментаторов выше, я не вижу ничего странного в том, что в контейнере запускается нечто по расписанию. На мой взгля, во многих случаях это вовсе не «пытаются использовать его как виртуальную машину» и никак не противоречит мифической «документации по docker». Если, например, внутри контейнера живет процесс которому надо по расписанию нечто активировать, например обновить какие данные, нечто очистить/загрузить и прочее в этом роде, то дернуть процесс (сигналом либо каким прочим вызовом) из контейнерного крона вполне нормально, Идея делать это с хостовой машины — это, как мне кажется, совсем плохая идея, убивающая переносимость и прибивающая конкретный контейнер к конкретному инстансу ржавыми гвоздями.

    Нет никакой особой разницы как именно генерируется этот «специальный сигнал по времени» внутри контейнера. Можно прикрутить нечто прямо в процесс сервиса если есть такое религиозное неприятие к второму процессу, но оно ничем не лучшее обычного крона.


    1. SirEdvin
      13.07.2016 01:06

      У вас небольшая проблема в примере:
      Если данные нужно скачивать на жесткий диск хостовой системы, то переносимость давно убита. А если оно просто прокидывается куда-то дальше, то разницы, откуда вызывать скрипт нет, то это можно делать и из хостовой машины.


      Если брать более сложный случай, и нам нужна система, которая бы по расписанию запускала различные таски, написанные, скажем, на Python, то для таких задач есть более умные и простые системы, типа Celery или, скажем, nats.io.


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


      1. umputun
        13.07.2016 01:22

        откуда, в моем примере, следует что данные надо скачивать и именно в файловую систему? Даже если и речь идет о подобных данных, то есть масса случаев когда они не персистент и это ответственность сервиса/контейнера их себе добыть. Это никак не ухудшает переносимость. Однако, в случаях когда данные прокидываются, тоже есть большая разница откуда вызывать скрипт. Если это скрипт (хотя я про скрипты ничего не говорил) делает нечто, что часть вашего сервиса, то его оторванность от вашего сервиса это совсем не тоже самое, как технически так и концептуально. И идея запускать это нечто на хосте, означает, что хост к такому должен быть подготовлен, что непонятно зачем надо. Ну и перенести это все просто средствами докера будет невозможно, т.к. часть работы делает внешний скрипт.

        Есть много случаев, когда задачи по расписанию чего-то в контейнере – это его внутреннее дело. Например, есть микросервис, который раз в день берет немного данных из внешнего мира, парсит их и вносит новые в некий store, специально для этого сервиса существующий. А все остальное время этот сервис отдает данные по запросам. Мониторинг к этому всему слабо относится — т.е. он никак не мешает мониторить как процесс так и конечное состояние.


  1. E_STRICT
    12.07.2016 22:47

    Как быть если cron должен иметь доступ к файловой системе проекта? Например, чтобы запускать там какие нибудь консольные скрипты.


    1. renskiy
      12.07.2016 23:12

      Вы можете расширить ваш образ с проектом, добавив туда возможность запуска cron. И создавать контейнер с переопределенным cmd:

      docker run --name cron --detach project_image start-cron
      

      В этом случае cron заданиям будут доступна вся файловая система образа проекта.


      1. E_STRICT
        12.07.2016 23:19

        Не совсем понял. Cron будет в том же контейнере что и основной проект? `start-cron` это какой то специальный баш скрипт?


        1. renskiy
          12.07.2016 23:27

          start-cron да, это специальный скрипт. Он подробно описан в этой статье. Так вот этот скрипт, должен лежать в одном образе вместе с вашим проектом. После этого просто создаете два контейнера из одного образа:

          docker run --detach --name project_container project_image
          docker run --detach --name cron_container project_image start-cron
          


          1. E_STRICT
            13.07.2016 08:01

            Все равно не понял. В вашем примере ведь контейнеры полностью изолированны. Как я в первом контейнере получу результат работы крона из второго контейнера? К примеру, в проекте есть какой нибудь консольный скрипт, который должен по крону парсить данные из внешнего источника и сохранять их в базу или на диск.


            1. renskiy
              13.07.2016 08:23

              Если же вам нужна синхронизация между контейнерами в runtime, то для этого у docker есть опции --link, --volume, --volumes-from. Последняя опция позволяет подмонтировать в новый контейнер все точки монтирования из указанного существующего контейнера. Например, вот так делается общая папка /data для двух разных контейнеров:

              docker run --detach --volume /var/project/data:/data --name project_container project_image
              docker run --detach --volume /var/project/data:/data --name cron_container project_image start-cron
              

              --link позволяет объединять контейнеры в общую сеть, позволяя им обращаться друг к другу по имени.


  1. umputun
    12.07.2016 23:42

    по поводу плясок вокруг «как крону передать env» — по моему можно несколько проще. В entrypoint сделать export > env.file а в кроне, в качестве SHELL, дать скрипт, который первым делом этот env.file применит.


    1. SirEdvin
      13.07.2016 00:58

      Хм… а передача сразу в команду docker run параметра --env-file=[] не может помочь в таком случае?


      1. umputun
        13.07.2016 01:00

        помочь в каком случае? передать env в контейнер это не проблема, проблема – как передать env в cron


        1. SirEdvin
          13.07.2016 01:02

          Хм, я думал что переменные из файлов окружения сразу полностью в него записываются и могут быть доступны везде. Или cron не может достучатся до глобального окружения?


          1. umputun
            13.07.2016 01:03
            +1

            cron бежит со своим окружением. это не специфика контейнера/докера, но крона


            1. SirEdvin
              13.07.2016 01:03

              Спасибо большое за информацию, вот с таким раньше не сталкивался.


    1. renskiy
      13.07.2016 06:01

      Да, такой вариант возможен. Но тогда надо будет всегда помнить о SHELL.


      1. umputun
        13.07.2016 06:56

        ну, строго говоря, мы тут и так городим свой entrypoint.sh. Без особого труда тут можно сделать и какой sed для автоматической замены SHELL на то, что надо, и больше об этом не думать.


  1. renskiy
    15.07.2016 09:24

    Обновил статью. Подробности в самом тексте.