장고에서 셀러리 적용하는법! (django + ec2 + docker)
RabbitMQ는 메세지 브로커인데, 처음 보는 개념이었다. 현업에서 많이 사용하는 브로커로는 kafka
가 있다. 메시징 시스템인데 비동기 작업을 위해서 필수적인 것. 그러니까 쉽게 말하면 RabbitMQ가 버퍼 역할을 해서 task가 일정하게 수행이 된다고 생각하면 될 것 같다.
Django가 Task를 RabbitMQ에 저장해놓으면, Celery에서 지정한 주기마다 task를 불러오는 식.
$ pip install celery
$ pip install django-celery-results
$ pip install django-celery-beat
이것만 설치하면 된다. RabbitMQ는 도커 컨테이너로 띄울 것이기 때문에..
django-celery-results
는 결과값으로 Django ORM이나 캐시(Cache) 프레임워크를 사용할 수 있게한다.
django-celery-beat
는 Crontab 기능을 사용하기 위해서 설치가 필요하다.
# settings/base.py
INSTALLED_APPS = [
....
'django_celery_beat',
'django_celery_results',
]
# Celery
# AWS 퍼블릭 DNS로 바꿔줘야 정상 작동함.
CELERY_BROKER_URL = 'amqp://AWS-DNS:5672' # amqp://localhost:5672
CELERY_RESULT_BACKEND = 'django-db'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
신기하게 celery
는 등록 안해도 된다. 그리고 아래는 config에 넣든 상관없는데, 나는 설정파일에 넣었다.
중요한건 브로커 URL인데, 나는 EC2에서 구동하므로 AWS 퍼블릭 DNS주소를 넣어주어야 한다. 그게 아니라면 위에 작성한대로 amqp://~~
뭐시기로 작성하는데, aws에서 구동한다면 꼭 aws dns주소를 넣어주어야 한다.
├── Dockerfile
├── Dockerfile.prod
├── README.md
├── 앱
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tasks.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── config
│ ├── __init__.py
│ ├── docker
│ │ └── entrypoint.prod.sh
│ ├── nginx
│ │ ├── Dockerfile
│ │ └── nginx.conf
│ └── scripts
│ └── deploy.sh
├── docker-compose.prod.yml
├── docker-compose.yml
├── manage.py
├── requirements.txt
└── 프로젝트
├── __init__.py
├── asgi.py
├── celery.py
├── settings
│ ├── __init__.py
│ ├── base.py
│ ├── dev.py
│ └── prod.py
├── urls.py
└── wsgi.py
이러한 구조로 되어있는데, 중요한 것은 celery.py
랑 task.py
위치이다.
celery.py
는 프로젝트 세팅 폴더에,task.py
는 앱 안에 위치해 있어야 한다.
celery.py
가 task의 시작점과 셀러리 세팅 폴더라고 보면 되고, task.py
는 단순히 테스크를 정의해서 celery.py
에 넘겨주는 것이라고 이해했다.(틀린 개념일 수도 있다)
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from celery.schedules import crontab
# 기본 장고파일 설정
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '프로젝트.settings')
app = Celery('프로젝트')
app.config_from_object('django.conf:settings', namespace='CELERY')
#등록된 장고 앱 설정에서 task 불러오기
app.autodiscover_tasks()
----이 부분 부터는 커스텀----
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
# crontab()에 인자를 안주면 기본 매 1분마다 실행한다.
app.conf.beat_schedule = {
'add-every-minutes-crontab' : {
'task' : 'coins.tasks.update_api',
'schedule' : crontab(),
}
}
debug_task
는 그냥 디버깅용이고, 아래는 크론탭 스캐줄링을 하기 위해서 설정한 코드이다.
위에 코드는 그대로 해줘야한다. 당연히 본인 프로젝트 이름에 맞게 설정해야 한다.
autodiscover_tasks
가 아래에 나오는 함수를 자동으로 인식해주는 것 같다.
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from .models import Cryptocurrency
import pytz
import datetime
import requests
@shared_task
def update_api(): # 없으면 생성, 있으면 가격 업데이트.
~~~
여기서 데코레이터로 @shared_task
를 작성하고, 그 아래에 함수를 작성하면 자동으로 셀러리에서 이 함수를 인식한다. 그리고 task로 넣어주고 싶은 함수마다 데코레이터를 달아야 한다.
그래서 이렇게 써놓고, 위에 크론탭에서는 task
에 함수 경로를 친절하게 써놓으면 작동한다.
나는 도커로 nginx, gunicorn을 다 띄우기 때문에, 이것또한 docker-compose로 처리했다. crontab기능을 사용하지 않으면 컨테이너를 두개만 띄우면 되는데, crontab기능을 사용하므로 container를 3개를 더 띄워야 한다.
# docker-compose.yml
version: '3.8'
services:
web:
container_name: web
build:
context: ./
dockerfile: Dockerfile.prod
command: gunicorn 프로젝트.wsgi:application --bind 0.0.0.0:8000
environment:
DJANGO_SETTINGS_MODULE: 프로젝트.settings.prod
env_file:
- .env
volumes:
- static:/home/app/web/static
- media:/home/app/web/media
expose:
- 8000
entrypoint:
- sh
- config/docker/entrypoint.prod.sh
nginx:
container_name: nginx
build: ./config/nginx
volumes:
- static:/home/app/web/static
- media:/home/app/web/media
ports:
- "80:80"
depends_on:
- web
rabbitmq:
container_name: rabbitmq
image: rabbitmq:3.7-alpine
environment:
- RABBITMQ_USER=guest
- RABBITMQ_PASSWORD=guest
ports:
- "5672:5672" # Default Port
- "15672:15672" # For UI
expose:
- "15672"
celery_worker:
container_name: celery_worker
build:
context: ./
dockerfile: Dockerfile.prod
ports: []
depends_on:
- web
- rabbitmq
command: sh -c "celery -A 프로젝트이름 worker --loglevel=info"
celery_beat:
container_name: celery_beat
build:
context: ./
dockerfile: Dockerfile.prod
ports: []
depends_on:
- web
- rabbitmq
- celery_worker
command: sh -c "celery -A 프로젝트이름 beat --loglevel=info"
volumes:
static:
media:
web
, nginx
, 컨테이너 말고도 rabbitmq
, celery_worker
, celery_beat
컨테이너가 추가 되었다.
rabbitmq 설정은 아직 잘 몰라서 기본 설정으로 띄웠다.
worker랑 beat는, worker가 구동이 되어있으면 beat가 crontab으로 스케줄링 된 시기미다 worker에 요청을 날려서 worker가 수행하는 구조인 것 같다. 로컬과 ec2에서 로그를 확인해보면 그런식으로 돌아간다.
이렇게하면 실행 끝! AWS 에서 매 분마다 갱신하면서 잘 돌아간다.
혹시 celery cpu 사용률 문제 해결하셨나요?