Introduction
Docker를 사용하여 로컬에 개발 환경을 만들어 사용하는 방식이 대부분 업계 표준으로 자리 잡고 있는 듯 하고,
더 나아가 실제 서비스 배포에까지 적용하는 곳이 점점 늘어나고 있는 듯 하다.
이번 포스트에서는, Docker 및 Docker Compose 로 Django Project의 개발 환경을 만들고, 개발에 사용하기 위한 방법을 소개한다.
Prerequisites
이 포스트에서는 Django, Celery, Maria DB, Redis의 4가지 서비스를 서버 어플리케이션을 위해 적용한다.
각각은 서버 내에서 다음의 역할을 수행한다.
Django
Django는 Main Server로써의 기능을 한다. 사용자의 부터의 Request를 처리하여, 적절한 응답을 Response 한다.
Celery
Celery는 Python에서 널리 사용되는 비동기 Task 및 Batch 프로세싱을 위한 Message Queue 기능을 제공한다.
Maria DB
MySQL과 거의 호환 가능한 Open Source Database
Redis
Redis는 매우 경량화된 In-Memory Database이다. 필자는 Django Project 내에서 Cache의 목적으로 Redis를 주로 활용하며,
Django Application과 Celery간의 통신을 위한 Message Broker로 Redis를 활용한다.
아래 작성되는 내용들은 Github Repository khtinsoft/django-celery-dockerize의 단계별 Commit을 통해 확인할 수 있다.
Docker Compose 파일의 구성
참조 Commit: 984610c359f0aa0fb54e2fe3a033d096115393cd
Docker Compose는 다수의 Docker Container를 한번에 관리할 수 있는 툴이다.
프로젝트 단위로 Docker Compose 파일을 구성하고, 이를 통해 Container 들을 관리하면 매우 편리하다.
프로젝트 루트 폴더 내에 docker-compose.yml 아래와 같이 생성한다.
sample-project 라는 프로젝트 Namespace를 가정하며, Django Project 이름은 sample_project 이다.
# docker-compose.yml
version: "3.7"
networks:
sample-project-net: # Container들의 Private Network을 설정한다.
ipam:
config:
- subnet: 172.20.1.0/24
volumes: # Container 들에서 사용되는 Volume을 정의한다.
sample-project-db-volume: {}
sample-project-cache-volume: {}
sample-project-media-volume: {}
services:
sample-project-db:
image: mariadb:10.3.11
environment:
- MYSQL_DATABASE=sample_database
- MYSQL_USER=sample
- MYSQL_PASSWORD=samplepassword
- MYSQL_ROOT_PASSWORD=samplepassword
ports:
- "127.0.0.1:3306:3306"
volumes:
- sample-project-db-volume:/var/lib/mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
healthcheck:
test: ["CMD", "/usr/bin/mysql", "--user=sample", "--password="samplepassword", "--execute=\"SHOW DATABASES\""]
interval: 3s
timeout: 1s
retries: 5
networks:
sample-project-net:
ipv4_address: 172.20.1.2
sample-project-cache:
image: redis:5.0.3-alpine
command: redis-server --requirepass samplepassword
ports:
- "127.0.0.1:6379:6379"
volumes:
- sample-project-cache-volume:/data
healthcheck:
test: "redis-cli -h 127.0.0.1 ping"
interval: 3s
timeout: 1s
retries: 5
networks:
sample-project-net:
ipv4_address: 172.20.1.3
sample-project:
build:
context: .
dockerfile: ./docker/Dockerfile
ports:
- "127.0.0.1:8000:8000"
depends_on:
- sample-project-db
- sample-project-cache
links:
- sample-project-db:sample-project-db
- sample-project-cache:sample-project-cache
command: bash -c "pip3 install -r requirements.txt && python3 manage.py migrate && python3 manage.py runserver 0.0.0.0:8000"
networks:
sample-project-net:
ipv4_address: 172.20.1.4
volumes:
- .:/sample-project/sample-project
- sample-project-media-volume:/sample-project/sample-project-media:Z
sample-project-task:
build:
context: .
dockerfile: ./docker/Dockerfile
depends_on:
- sample-project-db
- sample-project-cache
links:
- sample-project-db:sample-project-db
- sample-project-cache:sample-project-cache
command: bash -c "pip3 install -r requirements.txt && python3 manage.py celery"
networks:
sample-project-net:
ipv4_address: 172.20.1.5
volumes:
- .:/sample-project/sample-project
- sample-project-media-volume:/sample-project/sample-project-media:ZDocker Compose File Configuration를 참조하면 세부적인 Configuration이 무엇인지 확인할 수 있다.
이 포스트에서는 간단히 각각에 설정에 관해 소개한다.
networks:
sample-project-net: # Container들의 Private Network을 설정한다.
ipam:
config:
- subnet: 172.20.1.0/24
volumes: # Container 들에서 사용되는 Volume을 정의한다.
sample-project-db-volume: {}
sample-project-cache-volume: {}
sample-project-media: {}Container 들에서 사용할 Network / Volume 정보를 설정한다. 이 설정에서는 각 컨테이너는 172.20.1.x 의 IP 주소를 가지며,
sample-project-db-volume, sample-project-cache-volume, sample-project-media-volume
3가지의 Volume을 마운트 시켜 사용할 수 있다.
sample-project-db:
image: mariadb:10.3.11
environment:
- MYSQL_DATABASE=sample_database
- MYSQL_USER=sample
- MYSQL_PASSWORD=samplepassword
- MYSQL_ROOT_PASSWORD=samplepassword
ports:
- "127.0.0.1:3306:3306"
volumes:
- sample-project-db-volume:/var/lib/mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
healthcheck:
test: ["CMD", "/usr/bin/mysql", "--user=sample", "--password="samplepassword", > "--execute=\"SHOW DATABASES\""]
interval: 3s
timeout: 1s
retries: 5
networks:
sample-project-net:
ipv4_address: 172.20.1.2Database Service를 정의한다. sampledatabase라는 이름의 database를 생성하고,
_sample//samplepassword의 인증 정보를 설정한다.
sample-project-db-volume 이라는 Docker Volume을 /var/lib/mysql 폴더로 마운트 시킴으로써,
Container를 종료하고 다시 시작해도, Database 가 초기화 되지 않도록 한다
sample-project-cache:
image: redis:5.0.3-alpine
command: redis-server --requirepass samplepassword
ports:
- "127.0.0.1:6379:6379"
volumes:
- sample-project-cache-volume:/data
healthcheck:
test: "redis-cli -h 127.0.0.1 ping"
interval: 3s
timeout: 1s
retries: 5
networks:
sample-project-net:
ipv4_address: 172.20.1.3 Redis Service를 정의한다. samplepassword 의 인증 정보를 설정한다.
sample-project:
build:
context: .
dockerfile: ./docker/Dockerfile
ports:
- "127.0.0.1:8000:8000"
depends_on:
- sample-project-db
- sample-project-cache
links:
- sample-project-db:sample-project-db
- sample-project-cache:sample-project-cache
command: bash -c "pip3 install -r requirements.txt && python3 manage.py migrate && python3 manage.py runserver 0.0.0.0:8000"
networks:
sample-project-net:
ipv4_address: 172.20.1.4
volumes:
- .:/sample-project/sample-project
- sample-project-media-volume:/sample-project/sample-project-media:ZDjango Server를 실행한다. Container내의 /sample-project/sample-project 폴더에 서버 파일들을 마운트하고, 실행되도록 한다.
sample-project-db 와 sample-project-cache를 Link 하여, 프로젝트 내에서 참조할 수 있도록 한다.
sample-project-media-volume을 /sample-project/sample-project-media 로 마운트 하여 프로젝트 내에서 사용한다.
sample-project-task:
build:
context: .
dockerfile: ./docker/Dockerfile
depends_on:
- sample-project-db
- sample-project-cache
links:
- sample-project-db:sample-project-db
- sample-project-cache:sample-project-cache
command: bash -c "pip3 install -r requirements.txt && python3 manage.py celery"
networks:
sample-project-net:
ipv4_address: 172.20.1.5
volumes:
- .:/sample-project/sample-project
- sample-project-media-volume:/sample-project/sample-project-media:ZCelery Worker를 실행한다.
한가지 특이한 부분은, celery 실행 커맨드를 Django Custom Command(python3 manage.py celery)로 설정한 부분이다.
이와 관련된 내용은 Django/Celery 개발 환경 사용 시 Celery Auto Restart (Auto Reload) 적용하기 포스트에 소개한다.
만약 이와 같이 설정하기를 원치 않는다면, celer -A sample_project worker 로 대체할 수 있다.
sample-project, sample-project-task 서비스에서 사용되는 도커 이미지는 Custom 이미지를 사용한다. Ubuntu 18.04 이미지에 Python 3, libmysqlclient 등 필요한 모듈들을 설치하여 이미지를 생성한다.
# docker/Dockerfile
FROM ubuntu:18.04
ENV PYTHONUNBUFFERED 1
ENV PPYTHONENCODING utf-8
RUN apt-get update -y
RUN apt-get install -y software-properties-common build-essential python3 python3-dev python3-pip libmysqlclient-dev language-pack-ko
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
ENV LANG ko_KR.UTF-8
ENV LANGUAGE ko_KR.UTF-8
ENV LC_ALL ko_KR.UTF-8
RUN locale-gen ko_KR.UTF-8
RUN ln -fs /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN dpkg-reconfigure --frontend noninteractive tzdata
RUN python3 -m pip install pip --upgrade
RUN python3 -m pip install wheel
ADD . /sample-project/sample-project
WORKDIR /sample-project/sample-projectDjango Project 생성
참조 Commit: dfa1178e8e88cdebfccb56c079ef4ea43475702c
Docker Compose 파일이 생성된 폴더에, Dependency 관리를 위한 requirements.txt 파일을 생성한다.
# Requirements.txt
django
celery[redis]
redis
mysqlclient
django-redis Python 가상환경을 만들고, Dependency를 가상환경에 설치하고, Django 프로젝트를 생성한다.
(사실, Docker를 사용하는 환경에서, 굳이 로컬 환경 가상환경을 설치하고 그에 필요한 모든 dependency를 설치할 필요는 없다. 하지만 코드 편집기 등을 설정하고 활용하기 위해 로컬 환경에도 Python 가상 환경을 만들고, Dependency를 설치하는 편이다.)
$ virtualenv .venv -p python3
$ source .venv/bin/activate
$ pip3 install -r requirements.txt
$ django-admin.py startproject sample_project .ls -l 명령어를 통해 보면 아래와 같이 구성된 프로젝트를 확인할 수 있다.
-rw-r--r--@ 1 khtinsoft staff 3111 5 19 20:44 docker-compose.yml
-rwxr-xr-x 1 khtinsoft staff 634 5 19 20:51 manage.py
-rw-r--r-- 1 khtinsoft staff 44 5 19 20:47 requirements.txt
drwxr-xr-x 6 khtinsoft staff 192 5 19 20:51 sample_projectDjango 프로젝트 설정
참조 Commit: 38a6b424f16d9674b206ff03d5b34292b9baba91
이제 Django 프로젝트가, 위에서 설정한 Maria DB / Redis Cache / Celery 를 사용할 수 있도록 설정한다.
Maria DB 설정
sample_project/settings.py의 DATABASES를 아래와 같이 설정한다.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'sample_database',
'USER': 'sample',
'PASSWORD': 'samplepassword',
'HOST': 'sample-project-db',
'PORT': '3306',
}
}docker-compose.yml 파일 내에서 Maria DB 서비스 컨테이너를 sample-project-db로 연결해두었기 때문에, 해당 이름을 사용한다.
자세한 설정은 Django 공식 문서를 참고한다.
Redis 설정
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://sample-project-cache:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}Django Redis를 사용하여 Cache를 설정한다. 마찬가지로, sample-project-cache 라는 이름으로 연결한다.
자세한 설정은 Django 공식 문서 및 Django Redis를 참고한다.
Celery 설정
Celery를 사용하기 위해서는 1) sample_project/celery.py 파일을 아래와 같이 생성한다.
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
""" sample_project.settings 파일을 사용하도록 설정 """
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_project.settings')
""" sample_project를 사용하도록 설정 """
app = Celery('sample_project')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))- sampleproject/\_init__.py 파일애 아래 내용을 추가한다.
from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ('celery_app',)-
sample_project/settings.py 파일에 아래 내용을 추가한다.
CELERY_BROKER_URL = 'redis://:samplepassword@sample-project-cache:6379/0'
docker-compose.py 내에 설정한 Redis 링크 이름 및 비밀번호를 통해 Celery가 Message Broker로 Redis를 사용하도록 한다.
세부 적인 설정 방법은 Celery Django 공식 문서 및 Celery Configuration 문서를 참고한다.
프로젝트 실행
Docker Compose 명령어를 통해 정의한 도커 컨테이너들을 실행한다.
$ docker-container up // 컨테이너 전체 실행
$ docker-container down // 컨테이너 종료
$ docker-container down -v // 컨테이너 종료 및 볼륨 종료, Database 내용도 삭제된다.이제 브라우저에서, http://localhost:8000 로 접속해보자, 접속이 잘 되고 있음을 확인할 수 있다
다른 명령어들은 Docker Compose CLI 문서를 확인한다.
Trouble Shootings
만약 아래와 같은 에러가 출력된다면, Ctrl + C를 통해 컨테이너들을 종료하고 다시 실행한다.
(mysql에 데이터베이스가 생성되는 동안, django 프로젝트가 실행된 것이다..)
sample-project_1 | super(Connection, self).__init__(*args, **kwargs2)
sample-project_1 | django.db.utils.OperationalError: (2003, "Can't connect to MySQL server on 'sample-project-db' (111)")
django-celery-dockerize_sample-project-task_1 exited with code 1
django-celery-dockerize_sample-project_1 exited with code 1아래와 같이 에러가 나며, celery 컨테이너가 시작이 되지 않을 수 있다.
sample-project-task_1 | Requirement already satisfied: amqp<3.0,>=2.4.0 in /usr/local/lib/python3.6/dist-packages (from kombu<5.0,>=4.4.0->celery[redis]->-r requirements.txt (line 2)) (2.4.2)
sample-project-task_1 | Unknown command: 'celery'
sample-project-task_1 | Type 'manage.py help' for usage.이 에러는 컨테이너 시작 시 실행되는 command (python3 manage.py celery) 가 정의되지 않아서 발생한다.
Django/Celery 개발 환경 사용 시 Celery Auto Restart (Auto Reload) 적용하기 포스트를 따라 해당 커맨드를 생성하거나,
docker-compose.yml 파일 내의 sample-project-task 서비스의 commands 아래와 같이 수정한다.
commands: bash -c "pip3 install -r requirements.txt && celery -A sample_project worker"References
Compose file version 3 reference
Overview of docker-compose CLI
Django Settings
Django Setting up the cache
django-redis documentation
Celery :: First steps with Django
Celery :: Configuration and defaults