Docker에서 django Crontab 적용하기
프로젝트를 진행하다보면 심심치않게 스케줄러를 개발해야하는 경우가 생긴다. 리눅스에 있는 crontab이라는 스케줄러를 이용해서 개발할 수 있는데, 최근 프로젝트에서 적용하는데 꽤나 고생해서 그 내용을 정리할 생각이다. django-crontab 자체를 다룬다기보다는 docker랑 같이 사용방법에 대해서 위주로 다룰 예정!
개발환경 MacOS M1 Docker, Docker-compose Django Firebase
django-crontab
Django에서 crontab을 적용하고 실행시켜주는 패키지이다. 기본적인 사용법은 간단하고, 공식문서와 블로그들이 괜찮아서 시작부분은 빠르게 패스!
공식문서
Docker랑 합체
crontab은 linux의 기본 패키지이지만, docker에서 python 이미지를 불러와서 다른 패키지들(vi 같은 기본적인 것들…)과 마찬가지로 만들때는 포함되어있지 않다. 따라서 dockerfile
에서 따로 설치하고 적용시켜줘야한다. 나는 docker-entrypoint.sh
를 같이 쓰고 있기 때문에 참고….
# dockerfile
...
ADD ./docker-entrypoint.sh ./
RUN chmod +x ./docker-entrypoint.sh
ENTRYPOINT ./docker-entrypoint.sh
...
# docker-entrypoint.sh
...
echo "3-1. Update apt-get"
apt-get update
echo "3-2. Install cron"
apt-get install -y cron
# cron에 대한 log를 남기기 위해 디렉토리와 파일을 미리 만들어놓는다.
echo "3-2. Create cron.log"
mkdir /usr/log
touch /usr/log/cron.log
# django-crontab add 명령어
echo "3-3. Add CRON"
python manage.py crontab add
echo "3-4. Show CRON"
python manage.py crontab show
...
이렇게 하면 끝!!
라고 하기엔 내가 너무 많은 고생을 했기에, 그 이후의 작업이 있다.
crontab 자체가 안돌아가는데…?
여러 블로그를 참고하다보니, crontab 설치는 했는데 crontab 실행은 안해놓은 케이스… docker-entrypoint.sh
에 실행코드를 추가해주자
# docker-entrypoint.sh
...
echo "3-1. Update apt-get"
apt-get update
echo "3-2. Install cron"
apt-get install -y cron
# cron에 대한 log를 남기기 위해 디렉토리와 파일을 미리 만들어놓는다.
echo "3-2. Create cron.log"
mkdir /usr/log
touch /usr/log/cron.log
# linux에서 cron을 service로 실행시켜준다.
echo "3-3. Start cron"
service cron start
service cron status
# django-crontab add 명령어
echo "3-4. Add CRON"
python manage.py crontab add
echo "3-5. Show CRON"
python manage.py crontab show
...
crontab은 도는데, 왜 아무일도 없음…?
django-crontab은 django 환경에서 코드를 실행시켜준다. 이 말은 django에서 설정한 DB에 접근할 수 있다는 이야기이다. 사실 우리가 스케줄러를 만드는 이유가 주기적으로 DB의 데이터를 활용하기 위함이니까, 굉장히 중요한 부분이라고 할 수 있다. 그런데 여기서 문제는, django-crontab은 linux의 cron을 활용하기 때문에 crontab 자체의 특성을 그대로 가진다. docker안에서 cron이 어떻게 등록됬는지 확인하기위해, container에 접속한 후 crontab -l
로 확인해보자
# crontab -l
*/10 * * * * /usr/local/bin/python /usr/src/manage.py crontab run [CRONJOB ID] >> /usr/log/cron.log # django-cronjobs for [PROJECT NAME]
등록된 코드를 보면 정상적으로 안돌아갈 이유가 없어보이지만, 여기서 주의할 점은 linux의 crontab은 환경변수를 공유하지 않는다는 것이다;;;; log를 열심히 찍으면서 확인해본 결과 django 코드에서 os.getenv()에서 가져오는 환경변수들을 cron에서는 가져오지 못하는 것이다. DB정보나 Firebase 같은 정보들은 전부 .env에 저장해서 따로 관리했는데, 그럼 어떻게 해야할까.
python-dotenv 패키지 사용하기
사실 어차피 private 레파지토리니까 그냥 코드에 적어버릴까…했지만 그건 좀 선 넘은 것 같아서, python-dotenv라는 패키지를 찾아서 해결하기로 했다. 환경변수를 사용하는 모든 곳에 적용해도 좋지만, crontab이 사용하는 부분에서만 써도 괜찮을 것 같아서, 특정 부분에만 적용했다.
패키지
# python-dotenv 설치
poetry add python-dotenv
# or pip install python-dotenv
# settings.py
...
from dotenv import dotenv_values
BASE_DIR = Path(__file__).resolve().parent.parent
ENV_FILE = str(Path(BASE_DIR.parent)) + "/.env"
myEnvs = dotenv_values(ENV_FILE)
...
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": myEnvs["DB_HOST"],
"PORT": myEnvs["DB_PORT"],
"USER": myEnvs["DB_USERNAME"],
"PASSWORD": myEnvs["DB_PASSWORD"],
"NAME": myEnvs["DB_NAME"],
},
}
...
# Cron
CRONJOBS = [
("*/10 * * * *", "radi.cron.test", ">> /usr/log/cron.log"),
]
CRONTAB_COMMAND_PREFIX = f"GOOGLE_APPLICATION_CREDENTIALS={myEnvs['GOOGLE_APPLICATION_CREDENTIALS']}"
이 외에도 firebase 사용하는 부분 등에서 적용해줬고, 드디어 정상적으로 실행되는 것을 확인할 수 있었다. 추가로 따로 환경변수를 적용해주고 싶은 내용은 CRONTAB_COMMAND_PREFIX으로 세팅해줄 수 있다. Firebase의 경우 key파일의 경로를 코드에서 가져다쓰는게 아닌 환경변수에 담아놔야해서 추가로 따로 적용해줬다.
나의 하루…불태웠다…🔥
참고 https://github.com/kraiz/django-crontab/issues/88 django-crontab