シンプルな開発環境と本番環境の構築(Django+PostgreSQL)

今回は開発環境と本番環境についてです。構築する環境でどのように違いがあるのかを中心に書いていきます。
主にPythonDjangoを使う場合で書いてますが他のフレームワークなどでも使えます。

開発環境

こちらは自分の好きなように環境を用意できますが、以下のどちらかの方法がやりやすいです。

  1. 仮想環境を用意して普通に自分の環境で開発する。
  2. Docker環境を用意する。

1について仮想環境は言語の外部ライブラリをプロジェクトごとに管理するためです。忘れると面倒くさいです。しかしおすすめは2の方法です。Dockerで開発環境を用意することは非常に楽で、自分だけの環境に依存しないので、他者との開発環境の共有もすぐにできます(というかDockerfileと必要ならdocker-compose.ymlファイルを共有するだけです)。またインストールするライブラリなどもプロジェクトごとに管理できます。しかも、ある程度使うフレームワークやライブラリは雛形も決まっていて、例えば、DjangoのDocker環境の構築方法も調べればすぐに出てきます。一番シンプルなDjango+Postgre環境は以下のような感じです(自分のPCにDockerがインストールされていて起動していることが前提です)。

Dockerfile

# Python3のイメージを基にする
FROM python:3
ENV PYTHONUNBUFFERED 1

# ビルド時に/codeというディレクトリを作成する
RUN mkdir /code

# ワークディレクトリの設定
WORKDIR /code

# requirements.txtを/code/にコピーする
ADD requirements.txt /code/

# requirements.txtを基にpip installする
RUN pip install -r requirements.txt

ADD . /code/

docker-compose.yml

version: '3'

services:

  # データベース
  db:
    image: postgres
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  # Djangoアプリ
  web:
    build: .
    command: python3 manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db
  
volumes:
  postgres_data:

requirements.txt

Django==3.2
gunicorn
django-environ~=0.4.5
dj-database-url
psycopg2==2.8.5 

以上のファイルを自分のDjangoプロジェクトのmanage.pyと同じ階層に配置して下のコマンドを実行すれば開発環境の構築は終了です。ブラウザからアクセスして確認してください。

# ビルドする
docker-compose build
# マイグレーション
docker-compose run web python manage.py makemigrations
docker-compose run web python manage.py migrate
# サーバー起動
docker-compose up

追加するソフトウェアはDockerfileに、Pythonのライブラリはrequirements.txtに記述して追加できます。

本番環境

ここでは、Herokuを使いますが、ある程度の規模ならPass(Platform as a Service)が楽でおすすめです。
Platform as a Service | Heroku
他にはAWSVPSを使う方法もあります。

Herokuでデプロイ
  1. GitHubにコードをプッシュします。
  2. HerokuとGitHubを連携します。
  3. Heroku上でアプリを作成します。
  4. buildpackでPythonを追加します。これでPythonがそのアプリで使えます。
  5. requirements.txt、Procfileを用意します。
  6. アプリのページからGitHubのブランチを選択してデプロイボタンを押します。
気を付けること
  • 開発環境と本番環境で使うサーバーが違う。開発環境では、Django標準のサーバー(python manage.py runserver)で十分ですが、本番環境ではそうは行きません。本番環境では「gunicorn」というライブラリを使います。その理由はこの辺の記事が分かりやすいです。

Heroku公式サイト
Gunicorn を使用した Python アプリケーションのデプロイ | Heroku Dev Center
その他の記事
gunicornでPython製Webアプリケーションを動作させよう(DjangoとFlask) - Make組ブログ

Procfileは以下のようになり、webの所(web dyno)で起動するコマンドを書いています。

release: python manage.py migrate
web: gunicorn プロジェクト名.wsgi

DBやその他の重要な設定は環境変数に保存します。Djangoの場合はsettings.pyにデータベースの情報などを保存すると思いますが、Herokuの場合は以下のようにすると良いと思います。説明と関係がある所だけ書いています。

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = "HEROKU_EXTERNAL_HOSTNAME" not in os.environ

ALLOWED_HOSTS = []
CSRF_TRUSTED_ORIGINS = []

HEROKU_EXTERNAL_HOSTNAME = os.environ.get('HEROKU_EXTERNAL_HOSTNAME')
if HEROKU_EXTERNAL_HOSTNAME:
    ALLOWED_HOSTS.append(HEROKU_EXTERNAL_HOSTNAME)
    CSRF_TRUSTED_ORIGINS.append("https://" + HEROKU_EXTERNAL_HOSTNAME + "/*")

# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
if DEBUG:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'postgres',
            'USER': 'root',
            'PASSWORD': 'password',
            'HOST': 'db',
            'PORT': 5432,
            'TEST': {
                'NAME': 'custom_test_database'
            }
        }
    }
else:
    DATABASES = {"default": dj_database_url.config()}

STATIC_URL = '/static/'
MEDIA_URL = '/media/'

if not DEBUG:
    STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
    STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

まず環境変数変数の有無によって本番環境か開発環境がわかり、変数Debugの値を変えています。これによってDebugがTrueなのかFalseなのかで処理を分けることができます。さらにHEROKU_EXTERNAL_HOSTNAMEという環境変数にあらかじめ「アプリ名.heroku.com」という値を入れておきます。Herokuの場合はアプリの設定画面から環境変数を追加、変更できます。データベースの情報も同じようにdj_database_url.config()で取り出しています。DjangoのSECRET_KEYに関しては以下のサイトを参考にしました。
Djangoでの"秘密にしたい値"の取り扱い - ティッシュ残り一枚
開発でのSECRET_KEYの共有方法
DjangoのSECRET_KEYをバージョン管理対象外にする - Qiita
(CircleCIを使う場合は、CircleCI上でも「python generate_secretkey_setting.py > local_settings.py」をする必要あり)