[Django] AWS EC2 + Docker + Nginx 환경에서 Celery 사용하기

정환우·2022년 1월 7일
4

Django

목록 보기
6/6

장고에서 셀러리 적용하는법! (django + ec2 + docker)

  • 사용한 메세지 브로커 : RabbitMQ(Redis를 사용해도 되는데, 나는 그냥 이거 사용함)

Architecture

아키텍쳐

RabbitMQ를 왜 쓰는 건가?

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.pytask.py 위치이다.

celery.py 는 프로젝트 세팅 폴더에,task.py 는 앱 안에 위치해 있어야 한다.

celery.py 가 task의 시작점과 셀러리 세팅 폴더라고 보면 되고, task.py 는 단순히 테스크를 정의해서 celery.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 가 아래에 나오는 함수를 자동으로 인식해주는 것 같다.

tasks.py

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 에 함수 경로를 친절하게 써놓으면 작동한다.

실행 방법

  • 개발환경 : EC2 (Ubuntu LTS 18.04), Docker

나는 도커로 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 에서 매 분마다 갱신하면서 잘 돌아간다.

아직 해결하지 못한 점

  • EC2에서 CPU 사용량이 100%가 되어 다운 되는 경우가 발생한다. 이게 celery 문제인지, 문제라면 어떤 부분이 문제인지 확인이 필요하다.

참고 자료

장고(Django)에서 셀러리(Celery) 사용하기 1편
장고 셀러리 공식 문서

profile
Hongik CE

2개의 댓글

comment-user-thumbnail
2022년 8월 4일

혹시 celery cpu 사용률 문제 해결하셨나요?

1개의 답글