Django-crontab в контейнере docker: как изменить рабочий каталог cron с "root" на "app"?

Я разрабатываю django-приложение, полагающееся на базу данных mysql. Я пытаюсь запланировать задачу django-crontab для резервного копирования моей базы данных раз в неделю (здесь каждую минуту для тестирования). Мне нужно, чтобы мое приложение работало в docker, поэтому у меня есть один контейнер, запускающий приложение, и один контейнер, запускающий cronjob, оба из одного образа.

При тестировании моего задания django-crontab в docker, задав команду в моем docker-compose: command: python manage.py crontab run {taskhashid} задание успешно запускается один раз и контейнер существует. Пока все хорошо.

Чтобы периодически запускать задание в форсаже моего контейнера, я изменил команду на: command: cron -f. Здесь мое задание выполняется из каталога "root" и всегда терпит неудачу, так как необходимые скрипты сохранены в каталоге "app":

# cat /var/log/cron.log
The badge files are not found
Current working directory: /root
Contents of the current working directory:
- .bashrc
- .profile
- .cache
- .wget-hsts

Мой dockerfile:

# Use the official Python base image
FROM python:3.8

ENV PYTHONUNBUFFERED 1

RUN pip install --upgrade pip
# Install necessary dependencies
RUN apt-get update && apt-get install -y \
    default-mysql-client \
    cron \
    gcc \
    python3-dev \
    default-libmysqlclient-dev

# Ensure cron logs are set up
RUN touch /var/log/cron.log

# Set the PATH variable
ENV PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
# Copy environment variables to .env file
RUN mkdir -p /app && env > /app/.env
COPY ./requirements.txt .
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install ffmpeg libsm6 libxext6  -y

# Create and set the working directory
WORKDIR /app

# Copy the entrypoint script
COPY docker-entrypoint.sh ./
COPY wait-for-db.sh ./
RUN chmod +x ./docker-entrypoint.sh
RUN chmod +x ./wait-for-db.sh
RUN chmod +x ./docker-entrypoint.sh

# Verify the entrypoint script
RUN ls -l ./docker-entrypoint.sh && head -n 10 ./docker-entrypoint.sh

# Copy only necessary files or directories
COPY manage.py ./
COPY webWatcher ./webWatcher/
COPY webWatcherApp ./webWatcherApp/

# Defines the entrypoint script
ENTRYPOINT ["./docker-entrypoint.sh"]

# Default command
CMD ["gunicorn", "webWatcher.wsgi:application", "--bind", "0.0.0.0:8001"]

Мой entrypoint.sh:

set -e

# Initial debugging statement to capture any initial crontab state
echo "Initial crontab state:"
crontab -l || echo "No crontab for $(whoami)"

# Print the current working directory
echo "Current working directory: $(pwd)"

echo "Command send is: $@"

# Function to print the current crontab
print_crontab() {
  echo "Current crontab for user $(whoami):"
  crontab -l || echo "No crontab for user $(whoami)"
}

# Add the crontab if the command is 'cron'
if [ "$1" = "cron" ]; then

  echo "Adding crontab..."
  python manage.py crontab add || { echo 'Crontab add failed' ; exit 1; }

  echo "Executing python manage.py crontab show:"
  python manage.py crontab show || { echo 'Crontab show failed' ; exit 1; }

  echo "Printing crontab before starting cron service:"
  print_crontab

else

  # Function to check if the superuser exists
  check_superuser_exists() {
      python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); exit(0) if User.objects.filter(username='$DJANGO_SUPERUSER_USERNAME').exists() else exit(1)"
  }

  # Run Django management commands for initial setup
  echo "Running Django management commands..."
  python manage.py collectstatic --noinput --ignore admin || { echo 'Collectstatic failed' ; exit 1; }
  python manage.py makemigrations || { echo 'Makemigrations failed' ; exit 1; }
  python manage.py migrate || { echo 'Migrate failed' ; exit 1; }

  # Check if the superuser already exists before trying to create it
  if check_superuser_exists; then
      echo "Superuser already exists. Skipping creation."
  else
      echo "Creating superuser..."
      python manage.py createsuperuser --noinput --username $DJANGO_SUPERUSER_USERNAME --email $DJANGO_SUPERUSER_EMAIL || { echo 'Failed to create superuser' ; exit 1; }
  fi

fi

# Execute the command passed to the script
echo "Executing command: $@"
exec "$@"

Мой docker-compose.yml:

django_app:
  build:
    context: .
  container_name: "django_cont"
  ports:
    - 8001:8001
  environment:
    DJANGO_SUPERUSER_USERNAME: admin
    DJANGO_SUPERUSER_EMAIL: ***
    DJANGO_SUPERUSER_PASSWORD: ***
  depends_on:
    - db
  volumes:
    - biofilesvolumeF:/mnt/biofiles
    - biofilesvolumeE:/mnt/biofiles/3---Data
    - .:/app
  working_dir: /app

cron:
  build:
    context: .
  container_name: "cron_cont"
  environment:
    DJANGO_SUPERUSER_USERNAME: admin
    DJANGO_SUPERUSER_EMAIL: ***
    DJANGO_SUPERUSER_PASSWORD: ***
    DJANGO_SETTINGS_MODULE: webWatcher.settings
  depends_on:
    - django_app
  volumes:
    - biofilesvolumeF:/mnt/biofiles
    - biofilesvolumeE:/mnt/biofiles/3---Data
    - .:/app
  working_dir: /app
  command: cron -f

settings.py:

# Application definition
INSTALLED_APPS = [
    "django_crontab",
    "webWatcherApp.templatetags",
    "webWatcherApp",
    "dbbackup",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "crispy_bootstrap4",
    "crispy_forms",
    "debug_toolbar",
    "guest_user",
]

DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
DBBACKUP_STORAGE_OPTIONS = {"location": os.path.join(BASE_DIR, "db_backups")}

CRONJOBS = [
    (
        "*/1 * * * *",
        "webWatcher.cron.my_scheduled_db_backup",
        ">> /var/log/cron.log 2>&1",
    )
]

cron.py:

import logging
from django.core.management import call_command
import os

# Set up logging
logging.basicConfig(filename="/var/log/cron.log", level=logging.INFO)
logger = logging.getLogger(__name__)


def my_scheduled_db_backup():
    logger.info(f"Current working directory: {os.getcwd()}")
    try:
        logging.info("Starting dbbackup")
        print("Starting dbbackup")
        call_command("dbbackup")
        logging.info("Backup success")
        print("Backup success")
    except Exception as e:
        logging.error(f"Exception during dbbackup: {e}")
        print(f"Exception during dbbackup: {e}")
        pass

Вот консольный вывод моего контейнера cron:

2024-06-19 11:19:54 no crontab for root
2024-06-19 11:19:55 no crontab for root
2024-06-19 11:19:54 Initial crontab state:
2024-06-19 11:19:54 No crontab for root
2024-06-19 11:19:54 Current working directory: /app
2024-06-19 11:19:54 Command send is: cron -f
2024-06-19 11:19:54 Adding crontab...
2024-06-19 11:19:55   adding cronjob: (9881ae7c0739b87e4efc53f7b211c7cd) -> ('*/1 * * * *', 'webWatcher.cron.my_scheduled_db_backup', '>> /var/log/cron.log 2>&1')
2024-06-19 11:19:56 Executing python manage.py crontab show:
2024-06-19 11:19:57 Currently active jobs in crontab:
2024-06-19 11:19:57 9881ae7c0739b87e4efc53f7b211c7cd -> ('*/1 * * * *', 'webWatcher.cron.my_scheduled_db_backup', '>> /var/log/cron.log 2>&1')
2024-06-19 11:19:57 Printing crontab before starting cron service:
2024-06-19 11:19:57 Current crontab for user root:
2024-06-19 11:19:57 */1 * * * * /usr/local/bin/python /app/manage.py crontab run 9881ae7c0739b87e4efc53f7b211c7cd >> /var/log/cron.log 2>&1 # django-cronjobs for webWatcher
2024-06-19 11:19:57 Executing command: cron -f

При cat /var/log/cron.log всегда выдается одно и то же сообщение (как указано выше), рабочий каталог cron - "root" и поэтому не может найти зависимости, это моя проблема.

Я попытался изменить cronjobs в settings.py для точного определения рабочего каталога следующим образом:

CRONJOBS = [
    ('*/1 * * * *', 'cd /app && python manage.py my_scheduled_db_backup', '>> /var/log/cron.log 2>&1'),
]

Тот же результат.

Я попытался создать пользовательскую команду для запуска моей задачи django-crontab:

CRONJOBS = [
    (
        "*/1 * * * *",
        "cd /app && /usr/local/bin/python /app/manage.py run_cronjob_docker",
        ">> /var/log/cron.log 2>&1",
    ),
]

run_cronjob_docker.py:

from django.core.management.base import BaseCommand
from django.core.management import call_command
from django_crontab import crontab


class Command(BaseCommand):
    help = "Run the first cron job found"

    def handle(self, *args, **kwargs):
        # Get the list of current cron jobs
        jobs = crontab.get_current_job_ids()

        if jobs:
            # Assume there's only one job, and run the first one found
            job_id = jobs[0]
            print(f"Running cron job: {job_id}")
            call_command("crontab", "run", job_id)
        else:
            print("No cron jobs found")

Все тот же результат.

Я думаю, что запуск задачи cron в коинтейнере docker с помощью django-crontab должен быть довольно простым. Тем не менее, я застрял на этом на некоторое время.

Можете ли вы помочь мне понять, как заставить мою задачу cron непрерывно выполняться в моем контейнере docker?

Ожидаемый результат: сборка моего докера запустит контейнер django_app и cron_container, cron_container будет запускать задачу django-crontab каждую минуту.

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