Django

[Django] Docker환경에서 Django-crontab을 활용하여 주기적으로 작업 실행하기

jhkimmm 2022. 2. 21. 19:05

Django를 활용해서 웹서버를 구성할 때 기간만료 처리, 주기적인 이메일 발송 등 특정 작업을 주기적으로 실행하는 상황이 생깁니다. 이때 Django 웹서버에서 리눅스의 Crontab을 기반으로한 django-crontab 라이브러리를 사용하면 주기적인 작업 실행을 쉽게 처리할 수 있습니다.

 

간단히 django-crontab 작업을 생성해보고 이를 Docker 컨테이너에서 실행시키는 방법을 알아보겠습니다.

django-crontab 설정

pip install django-crontab

pip 으로 간단하게 django-crontab을 설치한 후

setting.py를 수정해줍니다.

INSTALLED_APPS = [
	...
    'django_crontab'
]

CRONJOBS = [
    ('* * * * *', 'myapp.cron.my_scheduled_job', '>> ./cron.log'),
]

CRONJOBS의 각 Argument의 의미는 다음과 같습니다.

- ' * * * * * ' 

* * * * *  수행할 명령어
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ │
│ │ │ │ └───────── 요일 (0 - 6) (0:일요일, 1:월요일, 2:화요일, …, 6:토요일)
│ │ │ └───────── 월 (1 - 12)
│ │ └───────── 일 (1 - 31)
│ └───────── 시 (0 - 23)
└───────── 분 (0 - 59)

작업이 실행될 주기를 설정합니다. 위 설정은 매분마다 작업을 실행한다는 의미이며 앞부터 분/시/일/월/요일 을 나타냅니다. 시간 설정을 도와주는 유용한 사이트가 있으니 참고 바랍니다.

http://crontab.guru

 

Crontab.guru - The cron schedule expression editor

 

crontab.guru

 

- 'myapp.cron.my_scheduled_job'

실행할 작업을 정의합니다. 저는 cron.py 파일안에 my_scheduled_job라는 함수를 정의 해놓았으며, myapp은 cron.py 파일이 들어있는 앱의 이름입니다.

로그 파일에 hello를 출력하는 간단한 job을 구현하면 아래와 같습니다.

#cron.py

def my_scheduled_job():
    print('hello')

 

- '>> ./cron.log'

django-crontab은 내부적으로 리눅스의 crontab을 사용하므로 print를 통해 출력된 내용은 로그 파일에 저장됩니다.

위 설정은 출력 내용을 저장할 로그 파일의 위치를 결정합니다.

 

여러가지 자세한 설정은 아래 문서를 참고바랍니다.

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

 

django-crontab

dead simple crontab powered job scheduling for django

pypi.org

설정완료 후 아래 명령어를 통해 작업을 스케줄러에 등록하고 잘 등록되었는 지 확인할 수 있습니다.

python manage.py crontab add #crontab 작업 등록
python manage.py crontab show #작업 등록 확인

django-crontab을 도커 환경에서 구동하기

먼저 django-crontab은 리눅스 crontab을 활용하는 별도의 시스템 데몬으로 저희가 구현한 Django 웹서버와는 별도의 프로세스를 가지고 작동하는 서비스라고 할 수 있습니다.

Docker Docs의 Run multiple services in a container 문서를 확인해보면

"It is generally recommended that you separate areas of concern by using one service per container"

라는 내용이 존재합니다.

https://docs.docker.com/config/containers/multi-service_container/

 

Run multiple services in a container

 

docs.docker.com

즉 하나의 컨테이너에서 여러 개의 서비스를 구동할 수는 있지만, 일반적으로는 하나의 서비스에 하나의 컨테이너를 사용하는 것을 추천한다고 명시되어 있습니다.

 

그러므로 웹서버와 django-crontab을 별도의 컨테이너에서 실행시키도록 하겠습니다.

먼저 이미지 생성을 위한 Dockerfile을 작성하겠습니다.

#Dockerfile

FROM python:3.7.3

WORKDIR /usr/src/app

COPY . /usr/src/app

WORKDIR .

#pip업그레이드 이후 의존성 설치
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

# for django-crontab
RUN chmod -R 777 /usr/src/app #권한 설정
RUN apt-get update
RUN apt-get install -y cron && touch ./cron.log #crontab 설치 및 로그파일 생성

다음은 Django 웹서버와 django-crontab을 별도의 컨테이너에서 실행시키기 위한 docker-compose.yml파일 설정입니다.

version: "3"

services:
  _build_image:
    image: main_image
    build: .
    
  my_django: 
    image: main_image
    restart: always
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    env_file:
      - ./.env
    depends_on:
      - _build_image

  cron:
    image: main_image
    restart: always
    command: bash -c "service cron start && python manage.py crontab add && tail -f /dev/null"
    env_file:
      - ./.env
    depends_on:
      - my_django

- django-crontab과 Django웹서버는 서로 다른 프로세스이지만 같은 코드를 사용하기 때문에 _build_image에서 현재 폴더를 한번만 빌드해서 main_image를 만든 후 각 서비스에서 main_image를 사용하도록 설정하였습니다.

이렇게 하면 도커 실행 시에 이미지를 두번 빌드할 필요 없이 한번만 빌드해도 컨테이너를 실행할 수 있습니다.

 

- main_image가 생성된 후에 my_django과 cron 서비스가 실행되어야 하므로 depends_on을 통해 의존성을 결정해 줍니다.

 

- cron 서비스의 command에서  "service cron start && python manage.py crontab add" 만 실행한다면 스케줄러에 crontab 작업을 등록하는 python manage.py crontab add 작업이 끝났을때 컨테이너가 exited 되어버리기 때문에

"tail -f /dev/null"을 추가해서 컨테이너가 종료되지 않도록 설정합니다.

 

이후 docker-compose up을 통해 도커를 실행하면 Django웹서버가 정상적으로 동작하고 django-crontab 서비스도 스케쥴러에 정상적으로 등록된 것을 확인할 수 있습니다.