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 каждую минуту.