Как заставить django-crontab выполнять команды в контейнере Docker?

Для периодического выполнения задач в моем приложении Django я установил django_crontab расширение.

https://pypi.org/project/django-crontab/

Я выполнил все шаги, как описано в параграфе настройки.

settings.py

INSTALLED_APPS = [
    ...
    'django_crontab',
]

...

CRONJOBS = [
    ('*/1 * * * *', 'config.cron.fun')
]

cron.py

def fun():
    print("hello cron")
    with open("./test.txt", "a") as f:
        f.write("Hello")

Я также добавляю задание cron в docker-entrypoint.yml:

python manage.py crontab add
python manage.py crontab show

вывод:

webapp    | no crontab for root
webapp    |   adding cronjob: (ebcca28ea3199afe6d09a445db5d5fd8) -> ('*/1 * * * *', 'config.cron.fun')
webapp    | Currently active jobs in crontab:
webapp    | ebcca28ea3199afe6d09a445db5d5fd8 -> ('*/1 * * * *', 'config.cron.fun')

В Dockerfile я использую python:3.8 образ и устанавливаю cron:

RUN apt-get install -y cron && touch /var/log/cron.log

И никакого ответа после запуска контейнера.

Когда я захожу в контейнер, я вижу, что cron видит задание, но все еще не выполняет его.

root@bar:/back# crontab -l
*/1 * * * * /usr/local/bin/python /back/manage.py crontab run ebcca28ea3199afe6d09a445db5d5fd8 # django-cronjobs for config

Как я могу это исправить?

В контейнере Docker выполняется только один процесс. Вам нужно запустить две отдельные вещи: основной сервер приложений и (после некоторой начальной настройки) демон cron. При правильной настройке можно легко запустить два отдельных контейнера, выполняющих две отдельные команды из одного и того же образа.

Первое изменение, которое я бы здесь сделал, это объединить то, что вы сейчас имеете как разделенные ENTRYPOINT и CMD в один CMD:

# no ENTRYPOINT, but
CMD ["python3", "manage.py", "runserver", "0.0.0.0:8080"]

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

version: '3.8'
services:
  db: { ... }
 webapp:
  build: ./back
  restart: unless-stopped
  ports:
   - "8000:8000"
  env_file:
   - .env
  depends_on:
   - db
  # run the default command: from the image; no override
  # skip unnecessary links:, container_name:, volumes: options
 cron:
  build: ./back     # same as main application
  restart: unless-stopped
  env_file:
   - .env
  depends_on:
   - db
  command: cron -f  # as a long-running foreground process

Здесь мы не сделали ничего, кроме заполнения кронтаба при запуске контейнера. Вот где может пригодиться ENTRYPOINT: если у вас есть ENTRYPOINT, он запускается вместо CMD, но ему передаются CMD в качестве аргументов . Довольно типичным примером здесь является использование ENTRYPOINT для сценария оболочки, который выполняет некоторую первую настройку, а затем завершается exec "$@" для запуска того, что было предоставлено CMD/command:.

#!/bin/sh
# docker-entrypoint.sh

# If this is going to be a cron container, set up the crontab.
if [ "$1" = cron ]; then
  ./manage.py crontab add
fi

# Launch the main container command passed as arguments.
exec "$@"

Затем в Dockerfile укажите этот скрипт как ENTRYPOINT.

...
WORKDIR /back
COPY . ./                                         # including docker-entrypoint.sh
...
ENTRYPOINT ["./docker-entrypoint.sh"]             # must be JSON-array syntax
CMD ["./manage.py", "runserver", "0.0.0.0:8080"]  # as before

Здесь не нужно переопределять entrypoint:. Команды типа docker-compose run web app bash для получения интерактивной отладочной оболочки будут работать нормально; они будут проходить через этот скрипт точки входа и запускать оболочку в качестве заключительной строки exec "$@".

Вернуться на верх