지난 글에서 Heroku를 통해 Django를 업로드 하고 It worked! 를 보는 부분까지 진행해보았었습니다. 사내에서 사용하는 조편성 시스템을 heroku를 통해 배포하고자 이번엔 static file, views, file read/write 등을 포함하는 기능으로 글을 작성해보았습니다.

진행하기 전 알아야 할 중요한 Django의 특징들을 몇가지 먼저 짚어보고 다음 단계로 진행하려고 합니다.

  1. Django는 직접적으로 static file을 제공하지 않습니다.
  2. Django는 collectstatic 명령어를 통해 static file들을 settings.STATIC_ROOT에 복제합니다.
  3. Django의 collectstatic은 settings.STATIC_ROOT 경로가 존재하지 않을 때 폴더를 강제로 생성하지 않습니다.

Heroku X Django 기본 구조

Django의 django-admin.py startproject {{app_name}}을 통해 프로젝트를 생성하면 app_name을 바탕으로 한 폴더가 manage.py 파일과 함께 생성됩니다. heroku가 소스코드 루트로 인지하게 되는 경로는 이 manage.py 파일이 위치하는 곳이어야 합니다. 지난 글에서도 이러한 문제로 인해 git init을 진행하기 전, 장고 프로젝트를 생성하여 폴더를 만들고 해당 폴더 안에서 git 명령어를 수행하였습니다.

heroku는 이 manage.py가 존재하는 소스코드 루트(django settings.py 에서 BASE_DIR)에서 requirements.txt, runtime.txt, Procfile 등을 확인하고 각 파일들 내에서 제공되는 코드를 이용하여 서비스를 배포합니다. 즉, heroku가 Django 프로젝트를 찾아 실행할 수 있도록 3개의 파일에 대한 설정이 필요함을 의미합니다. 아래에서는 기본적인 웹 환경 구성을 위해 runtime.txt와 Procfile을 설정하는 법을 알아보겠습니다.

runtime.txt

runtime.txt는 배포될 소스코드의 heroku 환경을 지정하는 파일입니다. 대문자 없이 소문자로만 작성하여야 하며, Django, Flask와 같은 Python 프로젝트에서는 python의 버전 지정을 할 수 있습니다.

python-2.7.15

Heroku는 기본적으로 python의 버전을 3.x로 제공하기 때문에 2.x 버전을 사용하기 위해서는 위 코드와 같은 내용을 runtime.txt에 작성하여 BASE_DIR에 저장해야 합니다.

(단, heroku는 서비스 특성상 2.x, 3.x 각각의 최신 버전만을 제공하고 있으니 이점을 참고하여 runtime.txt를 작성해야 합니다.

Procfile

Procfile은 app의 startup에 필요한 command를 기록하는 파일입니다. Process 실행과 관련된 다양한 설정값을 주로 전달합니다. (Worker 갯수, web server 지정 등)

runtime.txt와 마찬가지로 BASE_DIR에 존재해야 하며, 파일을 생성할때는 Procfile명을 그대로 사용해야 하고, 확장자를 입력하지 않아야 합니다.

기본적으로 지정할 수 있는 옵션 항목이 추가적으로 존재하지만 이전 글에서의 예시처럼 기본적으로 web: gunicorn {{app_name}}.wsgi(web: 뒤에 띄어쓰기 조심)와 같이 gunicorn을 실행시키는 설정 값이 들어갑니다. 이 명령어의 정상적인 실행을 위해requirements.txt에 gunicorn을 추가하고 gunicorn을 통해 장고에 접근할 수 있도록 설정하는 것이 heroku를 통해 배포할때에 권장되는 방법입니다.

Procfile 명령어를 통해 프로세스가 시작될때 필요한 일부 설정 값을 지정할 수 있습니다. $PORT와 같은 명령어로 포트를 지정하거나, log file의 설정 등을 수행할 수 있습니다. 아래는 이에 대한 상황별 코드입니다.

# 특정 포트로 생성되도록 지정
web: python manage.py runserver 0.0.0.0:$PORT

# 로그 파일을 생성하도록 설정
web: gunicorn {{app_name}}.wsgi --log-file -

# release 프로세스 타입으로 설정
release: gunicorn {{app_name}}.wsgi

Procfile에서 세부 내용을 더 설정할 수는 있지만, 주로 heroku run명령어로 세부적인 조작 및 제어가 가능하기 때문에, 일반적으로는 위와 같은 설정 정도만 진행한 후 heroku run 명령어를 통해 제어합니다.

Heroku X Django 의 staticfile

글의 처음에서 언급한 staticfile 이슈는 heroku를 처음 사용할 때 Django 개발자들에게 난감함을 줍니다. Static file의 호스팅을 S3 등의 storage로 구성하지 않는다면, Django는 스스로 static file을 제공하지 않습니다. 이러한 문제는 settings.py의 설정값을 변경하거나 조정하는 것으로는 해결이 불가능한 문제입니다.

이러한 상황을 해결해줄 친구로 WhiteNoise가 있습니다. WhiteNoise는 Django와 연동되어 사용될 라이브러리로써 정적 파일의 hosting 없이 장고가 직접적으로 파일을 제공할 수 있도록 도와줍니다. 설정은 settings.py 안에서 모두 가능합니다.

# whitenoise 설치 및 requirements.txt 반영
$ pip install whitenoise
$ pip freeze > requirements.txt
# settings.py 설정 변경
MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'whitenoise.middleware.WhiteNoiseMiddleware',
  ...
]

...

# STATICFILES_STORAGE, STATIC_ROOT 추가
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(DJANGO_DIR, 'static'),
]

manage.py collectstatic은 Django의 static 파일들을 settings.STATIC_ROOT 경로에 수집하고, whitenoise는 STATIC_ROOT경로에 있는 파일을 읽어 제공하기 때문에 위 2가지 설정값(STATICFILES_STORAGE, STATIC_ROOT)이 같이 제공되어야 합니다.

collectstatic 명령어는 지정된 경로에 폴더를 만들지 않습니다. (권한 이슈) 이로인해 위 설정값을 지정한 후 heroku 배포시 오류가 발생합니다. 이를 막기 위해 BASE_DIR 경로에 staticfiles 폴더를 미리 만들고, 해당 폴더에 임의의 파일을 추가하는 방법이 좋습니다.

$ mkdir staticfiles
$ echo ' ' > staticfiles/init

Heroku 와 Django 쓸만 한가?

다른 프레임워크들과 다르게 Django에는 여러 특징적인 부분들이 존재하고 이러한 특징들은 Heroku의 방식이나 로직과는 일부 다를 수 있습니다. Heroku의 특성상 토이프로젝트를 운영 및 배포하기 위한 용도로써의 사용은 나쁘지 않지만, Database를 함께 이용하거나, 관리하는 측면에서는 아무래도 불편함이 있을 수 밖에 없습니다.

다음번에는 Database (PostgreSQL)를 이용하는 Django 서비스를 Heroku에 올리는 방법에 대해 이야기해보겠습니다.