見出し画像

【サンプルコード付き】Docker超入門編 Docker × Django開発のテンプレートプロジェクト解説

1.はじめに

こんにちは、ぎーたかです。

今回は、Dockerを使ったDjangoプロジェクト作り方の解説をします。

この記事を読めば、Dockerの概要メリットをざっくりと理解できるようになり、Dockerの基本的な実装・扱い方についてもある程度理解できるようになります。

また、記事の中では具体的なDocker × Djangoプロジェクトサンプルコードの解説をしていきます。記事の最後には、解説したソースコードのGitHubリポジトリURLも掲載していますので、記事を読んですぐにDjango開発で使っていただくこともできます。

『Dockerってよく聞くけど、何が嬉しいの?』、『使ってみたいけど、難しそうで手が出せない』と感じていた方にとっては、まさに読む価値のある記事になっているかと思います。

読者の皆さんがDocker開発の一歩目を踏み出すきっかけにしてもらいたい、という思いで書いて行きますので、ぜひ最後まで読んでいっていただけると嬉しいです。

次のパートでは、まずDockerの概要・メリットから理解していきましょう

2.Dockerの概要・メリット

■概要
Dockerとは何か、Wikipediaからの引用を掲載します。

Dockerは、コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアあるいはオープンプラットフォームである。
Dockerはコンテナ仮想化を用いたOSレベルの仮想化によりアプリケーション開発・実行環境から隔離し、アプリケーションの素早い提供を可能にする。かつその環境自体をアプリケーションと同じようにコード(イメージ)として管理可能にする。Dockerを開発・テスト・デプロイに用いることで「コードを書く」と「コードが製品として実行される」間の時間的ギャップを大きく短縮できる。
(出典:Wikipedia https://ja.wikipedia.org/wiki/Docker )

学習しはじめだと分かりづらいと思いますが、概要として大事なキーワードはまとめられています。

記事を読んだ後、自分で実装してみた後に再び読んでみると理解が深まるかと思いますので、思い出して読んでみてください。

概要は分かりづらかったかと思いますが、Dockerのメリットは分かりやすく伝えられるかと思います。筆者の解釈で、3点噛み砕いて説明しましょう。

■メリット
①ローカル開発/テスト実行/本番運用、Dockerfileを含む同じコードだけあれば、いつでもどこでも同じ様にソフトウェアの動く環境を作れる

Dockerを使わない場合、こんな状況に遭遇することがあります。

ローカルPCでは動いたのに、本番のサーバで公開したら動かなくなった。多分同じライブラリで環境構築したのにな...』

大別的な原因としては、

ローカルPCのインストールライブラリは、複数プロジェクトのものが混同していて、本番サーバでインストールし忘れがある
ローカルPCOSと、本番サーバOS異なることで、インストールが必要なライブラリの数や名前が違い、本番サーバでインストールし忘れがある

が多いと思います。

Dockerを使う場合、以下の図のイメージで動作させることができます。

Docker動作イメージ

動作させるDockerコンテナの定義は、Dockerfileというファイルに環境ソフトウェア動作を記述して行います。このDockerfileは、基本的にソフトウェアごとに1ファイル作る必要があり、他プロジェクトの影響を受けない閉じた環境を定義できます。(1点目の解決)

また、Dockerfileで作られるソフトウェアの動作環境は、ホストマシンいかなる影響も受けません。ホストマシンのOS差異にすらも影響を受けず、動作環境を作れます。これはDockerを使う上でホストマシンにインストールが必要なDocker Engineが緩衝材としてはたらくためです。(2点目の解決)

こうした仕組みでDockerコンテナは、ホストマシンの影響を受けずいつでもどこでも実行できる特徴を持っているわけです。

一点余談ですが、Dockerがいつでも同じ様に動かない例外もあります。

USB接続等、外部デバイスを使ったプログラムだけは、プログラムの中でホストマシンの物理的な特徴(USBポートの番号の指定)を書く必要があるので、ホストマシンを変えれば動作は大体変わります。

詳細が気になる方は以下の参考記事を読んで見てください。

外部デバイスを使わないプログラムなら動作は保証されるので、あまり問題視せず余談で挙げておきました。

②Dockerfileによって環境でバージョン管理でき、バグ発生しても前の環境にすぐ戻せる

Dockerを使わない場合、こんな状況に遭遇することがあります。

『あれ?!さっきまで動いてたのに、必要そうなライブラリ入れたら動かなくなった!しかも戻し方分からない...』

Dockerを使う場合、Dockerfileもプロジェクトのバージョン管理対象として含められる様になります。簡単に言えば、Git管理できるようになります。

なのでソフトの動作環境の変更時は、期待通りの動作を細かく確認しつつ更新を進めれば、バグが起きてもコミットせず変更を破棄すればすぐに元に戻せるわけです。

少し脱線ですがGitHubなどのGitホスティングサービスは、SourceTreeというソフトウェアと連携させるとすべてGUIGitコマンド操作できるようになるので、便利です。単なる筆者の趣向ですが、気に入った方は使っていただけると良いかと思います。(⏬サービスページとMacのインストール方法を参考記事で掲載)

『動作環境をコードで管理できる』概念は、IaC(Infrastructure as a Code)という言葉で認知されてきているらしく、重要度が増してきています。
近年は、特にインフラ分野(本番環境を提供するAWS等のサービス)では色々とDokcerを使うことが主流になってきています。

③2つ以上のDockerコンテナを組み合わせたシステムとしても動かせる

このメリットについては、特に従来からの課題はないですが、当然システム構築にも利用できます。むしろソフトウェア開発にDockerを使う開発現場では、この形の方がDockerコンテナを単体で使うよりも一般的かと思います。

2つ以上のコンテナを組み合わせるイメージを以下の図で表します。

DockerCompose動作イメージ

Dockerコンテナは1つのホストマシン上に複数立てられます。そして、Dockerコンテナごとのソフトウェアの連携のために、データの共有通信をさせることもできます。

これにより、例えばWebサービスの3層構造である、DBサーバアプリサーバWebサーバをDockerコンテナ1つずつに分けて連携させたシステムを作ったりできます。(今回の記事で作るDjangoプロジェクトの構成もこの3層構造を作ります。)

もちろんこの連携の定義コードで行います。
複数のDockerコンテナを組み合わせたシステムを作る時は、Dockerfileとは別のファイルdocker-compose.ymlにDockerコンテナ間の連携をコードで表現する必要があります。

具体的な書き方は、後のパートで解説していくので、この記事で学んでいきましょう。Dockerfiledocker-compose.yml文法は難しくないので、初学者でもある程度勉強すれば書けるようになります。

次のパートでは、DockerEngineのインストール〜Dockerfileの実装までのプロセスの流れを大枠で解説します。Docker開発のイメージも持てるように頑張って読んでいきましょう。

3.Dockerスターティングプロセス

DockerEngineのインストール〜Dockerfileの実装までのプロセスは以下のステップで進みます。

<Dockerスターティングプロセス>
①(DockerEngineのインストール)Dockerのインストール
②(DockerEngineのインストール)Dockerの初期設定
③(Dockerfileの作成)DockerHubからベースイメージの選定
④(Dockerfileの作成)Dockerコンテナの環境・ソフトウェアの定義
⑤(docker-compose.ymlの作成)連携Dockerコンテナの定義・連携設定
⑥(コンテナの実行)dockerコマンド/docker-composeコマンド実行

■DockerEngineのインストール
インストール手順・初期設定
参考記事の掲載に替えさせていただきます。

インストール手順は、ローカル開発と本番運用の想定で、ホストマシンがMacOSの場合と、Ubuntuの場合とで以下の記事を参考に実行いただければと思います。

初期設定は、Windows/MacでインストールされるDockerDesktopの場合のみ必要です。MacOSの場合は以下の公式ページを参考に設定してみましょう。

■Dockerfileの作成
Dockerfileの作成は、実際にコンピュータ上でソフトウェアを動かすときの必要物を教えてあげるのと同じです。

・OS
・インストールライブラリ
・ソフトウェアのファイル
・実行コマンド

ざっくり分類すると、これらが必要になります。

Dockerfileの書き出しでは、はじめにベースイメージを選びます。このベースイメージの選定というプロセスが、OSの選定に該当します。ベースイメージには、OSプレインストールソフトが記録されており、Dockerの環境・ソフトウェア構築の出発点になります。

ベースイメージは、DockerHubの公開イメージから選びましょう。

DockerHubには、利用目的別に豊富なベースイメージが登録されています。Dockerを学び始めの頃は、動かしたいソフトウェアのサービス名を検索して出てくるオフィシャルイメージを選ぶと実装しやすいかと思います。

例えば、DjangoアプリのDockerを作りたいなら、`Django``Python`検索結果イメージを使いましょう。

このベースイメージを起点に、ソフトウェア動作の必要物を満足するように、Dockerfileを実装していくことになります。ベースイメージ以外の実装の流れはコード解説のパートで説明するので、本パートでは割愛します。

■docker-compose.ymlの作成
docker-compose.ymlの実装の流れもコード解説のパートで説明するので、本パートでは割愛します。

■コンテナの実行
Dockerインストール後は、dockerコマンド/docker composeコマンドを使えるようになっています。

開発で最低限必要なコマンドについて解説します。詳細な仕様、その他のコマンドは、公式の解説ページを読んで、理解を深めてみてください。

<公式ドキュメント>

<参考記事>
色々とまとまってる参考記事もありましたので、載せておきます。

<開発用最低限コマンド>
①build

// Dockerfileと同ディレクトリで
$ docker build .

pythonしか触れたことがないと馴染みがないかもですが、Dockerではビルドという作業を行いビルドイメージ(実行ファイル的なもの)として実行します。厳密には違うのですが、機械語に翻訳したファイルを作る作業だと捉えてください。

buildは、Dockerfileに基づきビルドイメージを作成するコマンドです。後に解説するrunコマンドで実行できるビルドイメージをホストマシンに保存します。

その他知っておくといいコマンドオプションとして、-tオプションがあります。この辺りは公式ドキュメントから仕様を調べてみてください。

②images

$ docker images

// 出力例
REPOSITORY   TAG    IMAGE ID   CREATED   SIZE
xxxx         xxxx   xxxx       xxxx      xxxxMB

ホストマシンに保存されているビルドイメージの一覧を見れます。コンテナの実行で使うrunコマンドではimagesコマンドの出力のREPOSITORYIMAGE_IDを必要とするので、先に確認しておきましょう。

③run

$ docker run {REPOSITORY or IMAGE_ID}

ビルドイメージを実行するコマンドです。第1引数にimagesコマンドで確認した値を指定して実行します。

その他知っておくといいコマンドオプションとして、-vオプション-pオプション-dオプションがあります。この辺りは公式ドキュメントから仕様を調べてみてください。

④ps

$ docker ps
// 出力例
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS    NAMES
xxxx           xxxx    "…"       xxxx      xxxx     xxxx     xxxx

runで実行中のDockerコンテナの一覧を確認できます。

色々と利用ケースがありますが、Runしたコンテナが動いているか不要なコンテナが動いていないかの確認で使います。CONTAINER_IDやNAMESは以降で紹介するexecや、stopコマンドの引数にもなるのでpsコマンドで確認します。

⑤exec

// ベースイメージがalpineの時、bash -> ashに変更
$ docker exec -it {NAMES} bash

実行中のDockerコンテナファイルシステムシェル(ターミナルのこと)でアクセスできるコマンドです。psコマンドのNAMESを第1引数に当てて実行します。

WEBアプリのマイグレーションテストの実行は、このexecコマンドでコンテナの中に入って実行することになります。また、Dockerコンテナ内に転送したファイルが存在しているか等のデバッグにも使うことがあります。

⑥stop

$ docker stop {NAMES}

実行中のDockerコンテナ停止させるコマンドです。psコマンドのNAMESを第1引数に当てて実行します。不要なコンテナはホストマシンのメモリを圧迫するので、使ってないものは止めておきましょう。

⑦compose build

// docker-compose.ymlと同ディレクトリで実行
$ docker compose build

複数のDockerコンテナでシステム構築する場合、docker-compose.ymlファイルに定義・指定された全てのDockerイメージビルドするコマンドです。

⑧compose up

// docker-compose.ymlと同ディレクトリで実行
$ docker compose up

docker-composeファイルに従い、必要なビルドイメージ定義通り連携させながらrunするコマンドです。compose buildコマンドと同様、docker-compose.ymlのあるディレクトリ直下でしか有効でないので、注意しましょう。

4.解説するDocker × Djangoアプリのシステム構成

今回は、下図のオーソドックスな構成で作ります。

システム構成

WEBアプリのプロジェクトソースコードとしてDjangoを、DBMySQLを、アプリサーバGunicornを、WebサーバNginxを使います。
Dockerコンテナとしては、Django・Gunicornで1台、MySQLで1台、Nginxで1台の計3台を連携させていきます。

ちなみに、この構成はWEBアプリ三層構造とも呼ばれる基本構成なので、知らなかったという方は以下の参考記事をどうぞ。

5.Docker x Djangoテンプレートプロジェクト

解説するDocker x Djangoプロジェクトは以下のURLで公開しています。
もしよかったら、読者のみなさんのホストマシンクローンして使ってみてください。

ディレクトリの構成を説明します。

app/                    # プロジェクトルート
├── Dockerfile            # Dockerfileファイル
├── docker-compose.yml    # docker-composeファイル
├── config/               # 設定ディレクトリ
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings            # settingsディレクトリ
│   │   ├── base.py           # 環境共通設定
│   │   ├── dev.py            # 開発環境設定
│   │   └── prod.py           # 本番環境設定
│   ├── urls.py
│   └── wsgi.py           # Gunicorn接続先
├── static/
├── templates/
├── manage.py
├── nginx/                # Nginx設定ディレクトリ
│   ├── mime.types          # MediaType設定
│   └── nginx.conf          # Nginx設定
├── requirements/         # ライブラリディレクトリ
│   ├── base.txt            # 共通ライブラリ
│   ├── dev.txt             # 開発環境ライブラリ
│   └── prod.txt            # 本番環境ライブラリ
└── scripts/              # スクリプトディレクトリ
    └── entrypoint.sh       # Django実行スクリプト
    

Djangoプロジェクトに、Dockerコンテナシステム必要ファイルを追加しました。

Dockerfiledocker-compose.ymlの他にも、使用するPythonライブラリリストを記したrequirementsディレクトリNginxコンテナで使う設定ファイルDjangoアプリ起動時コマンドをまとめたscriptsディレクトリを追加しています。

Dockerfile以外は、動作させたいシステムによって追加物は変わります。

DjangoなどのWebアプリ作成では、1つのWebアプリごとに上記のプロジェクトのファイル体型を作っていくことになります。

次のパートからは、追加したファイル具体的な中身・実装について解説していきましょう。

6.解説①Dockerfile

使用したDockerfile特有の文法・命令を先に列挙しておきます。

<使用したDockerfile命令>
・FROM {イメージ名}
:pullする(使う)ベースイメージを宣言
・RUN {ターミナルコマンド}:コンテナ内でのコマンド実行
・ENV {環境変数名} {値}:コンテナ内で有効な環境変数の定義
・WORKDIR {ディレクトリパス}:作業ディレクトリの宣言
・COPY {ホストパス} {Docker内パス}:ホストマシンからファイルコピー
・CMD [{ターミナルコマンド}]:Docker起動時の命令の宣言

# Use debian base image
FROM python:3.8.12-slim-bullseye

# update, and install packages, and remove cache
RUN apt-get update && \
   apt-get install -y build-essential libssl-dev libxml2-dev libxslt1-dev libmariadb-dev default-libmysqlclient-dev && \
   pip3 install -U pip && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/* /usr/bin/mysqld* /usr/bin/mysql*

# setup python env
ENV PYTHONUNBUFFERED 1
ENV PYTHONIOENCODING utf-8

# prepare app root directory
ENV ROOT_PATH /app
RUN mkdir ${ROOT_PATH}
WORKDIR ${ROOT_PATH}

# transfer local django dev env
COPY . ${ROOT_PATH}

# install common library for Django application
RUN pip3 install -r requirements/base.txt

# start application script
COPY scripts/entrypoint.sh /usr/bin
RUN chmod +x /usr/bin/entrypoint.sh
CMD [ "entrypoint.sh" ]

今回はPythonのベースイメージを使います。DockerHubから検索して見つけたpython:3.8.12-slim-bullseyeを使うことにしました。

Pythonベースイメージ

Slimは省サイズのイメージで、bullseyeはDebian(LinuxOSの派生系OS)のイメージを表してます。

Dockerベストプラクティスイメージは小さく保つ記載があったのと、Docker公式推奨のベースイメージがDebianのため今回はそうしています。
`イメージを小さく保つ`はベースイメージの指定以外にも、Dockerfileそのものの書き方を気をつけないといけないのですが、まずは小さいのを選ぶのが出発点です。

続いてOS周りで必要なライブラリインストールを行います。作るシステムの構成やベースイメージの選定(UbuntuかDebianかAlpineか、プレインストールが多いか、等)で大きく変わるので、プロジェクトの都度必要なライブラリを調べながら指定します。
例えば、今回はMySQLを使用することから、ベースイメージには含まれないlibmariadb-devdefault-libmysqlclient-devをインストールしています。

次に環境変数を2つ設定しています。どちらもPython実行下で効く設定の環境変数です。おまじない程度に捉えていても問題はありません。

次に作業ディレクトリを指定します。Linuxはデフォルトで以下の参考記事にあるディレクトリ構造を持っています。

ここにDjangoアプリファイル配置するディレクトリを作りたいので、名前は何でもいいですが今回は`/app`のディレクトリを追加します。そして、以降は`/app`のディレクトリで実行したいコマンドが多いので、作業ディレクトリにします。

ちなみにdocker execコマンドでコンテナ内部に入るときも、WORKDIRのパスからシェルがスタートします。

次にホストマシンにあるDjangoアプリのディレクトリを、ごっそりDockerコンテナ内`/app`コピーします。COPY句の第1引数の`.`現在のディレクトリを表すので、Dockerfileと同じ階層のディレクトリの内容をコピーするという命令になります。

次に開発/本番共通で使うPythonライブラリ(djangoなど)をインストールします。

最後にDjangoアプリ実行の前処理〜サーバー立ち上げまでのコマンドを記載したシェルスクリプトをコピー、CMD句でコンテナ起動後に実行させる命令をしました。

こんな具合でDockerfileは完成になります。

コードの解説はここまでででちょっと余談です。最後のCMD句は少し特殊で、Dockerfile内で1回しか使えません。どうしても2回使いたい時はCMDによく似た命令でENTRYPOINTというものがあります。

ただし、CMDと併用する場合、CMDとENTRYPOINTどちらを先に書くかでコマンドの実行タイミングに変化が起きます。なので、併用する時は読者のみなさんがさせたい処理と相違がないか注意が必要です。以下の参考記事にその辺りがまとまっていたので、気になる方はこちらをどうぞ。

7.解説②requiementsディレクトリ

■base.txt

Django==3.1.*
djangorestframework==3.12.*
django-cleanup==4.0.*
django-environ==0.4.*
django-security==0.12.*
django-cors-headers==3.7.*

インストールライブラリをまとめたrequirementsディレクトリは、開発用と本番用で使用ライブラリの違うケースが多いので、ファイル分割します。

今回は筆者の趣向で、開発/本番共通のライブラリはbase.txtに、開発用だけのライブラリはdev.txtに、本番用だけのライブラリはprod.txtにまとめる方針にしました。

base.txtには以下を指定しました。

<baseライブラリ>
・Django...Djangoです
・djangorestframework...DjangoでAPIの作成・提供を許可(DRF)
・django-cleanup...データベースのレコード削除時に関係ファイルも削除
・django-environ...環境変数ファイルの読み込みを許可
・django-security...Webアプリで一般的なセキュリティミドルウェアを包含
・django-cors-headers...DRFのAPIを実行するIPホワイトリスト定義を許可

requirements系のファイルには、必ずバージョン指定してください。
pipなどのパッケージ管理ツールは、バージョン指定のない場合は基本的に最新バージョンをインストールします。ある日突然、メジャーバージョンアップされたライブラリの影響で、アプリが動かなくなることもあります。
バージョンは下1桁以外(下1桁は互換性のあるバージョンアップのため)は固定しましょう。

■dev.txt

mysqlclient==2.0.*
gunicorn==20.1.*
django-debug-toolbar==3.2.*
django-extensions==2.2.*
flake8==3.9.*
autopep8==1.5.*

コードの説明しやすさの都合、今回は開発環境もDjango + MySQL + Gunicorn + Nginxのコンテナを使います。そのため、開発用ライブラリにもMySQLGunicornのライブラリを含みます。

ただし、開発用DBSQliteにするのが一般的なので、読者の皆さんのホストマシンではSQliteを使用する様に、挑戦してみても良いかも知れません。

dev.txtには、MySQLとGunicornの他、以下のライブラリを追加しました。

<devライブラリ>
・django-debug-toolbar...開発時の便利デバッグ機能の使用を許可
・django-extensions...開発時の便利機能を多数包含
・flake8...Lint(構文チェック)ライブラリ
・autopep8...Lint(構文チェック)ライブラリ、flake8とどちらかでもOK

django-debug-toolbarとdjango-extensionsの機能の紹介は、以下の参考記事の掲載に替えさせていただきます。

■prod.txt

mysqlclient==2.0.*
gunicorn==20.1.*

MySQLとGunicornのライブラリのみです。dev.txtと共通なので、base.txtにまとめてもよいですが、開発のスタイルとして有用なファイル分割の例を紹介したかったので、敢えて同じ内容でも分けて作ってみました。

8.解説③scriptsディレクトリ

#!/bin/bash

# Set setting env, please exchange the comment out line to switch for the other env
export DJANGO_SETTINGS_MODULE=config.settings.dev
# export DJANGO_SETTINGS_MODULE=config.settings.prod

# Install packages for dev or prod, about which packages will be installed, please decide by changing above env value
if [ $DJANGO_SETTINGS_MODULE = "config.settings.dev" ]; then
   pip3 install -r requirements/dev.txt
else
   pip3 install -r requirements/prod.txt
   mkdir -p /var/log/app && touch /var/log/app/app.log
   chmod +w /var/log/app/app.log
fi

# Prepare database
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py shell -c "from django.contrib.auth import get_user_model; get_user_model().objects.create_superuser('admin', 'admin@example.com', 'adminpass');"

# Collect Staticfiles
python3 manage.py collectstatic --noinput

# Set server(gunicorn)
mkdir -p /var/run/gunicorn
gunicorn config.wsgi --bind=unix:/var/run/gunicorn/gunicorn.sock

Dockerコンテナ起動時に行わせたいDjangoアプリ設定/環境別インストール/前処理/サーバアップまでのコマンドをまとめました。DockerfileのCMD句では、こうしたシェルスクリプトでコマンドをまとめておく様にしておく方が、バージョン管理の観点から望ましいです。

ソースコードを上からざっくり見ていきましょう。

まずexportコマンドで環境変数を1つ設定します。これは、Djangoアプリで参照する設定ファイルを決める環境変数で、開発/本番のどちらの環境で動かすかで、代入する値を変えて運用します。
いちいち書き換えるのも面倒なので、筆者はdevとprodの値で指定した行を両方書いておき、コメントアウト行入れ替えて運用することにしました。

次の条件分岐で、dev/prodで動かす時の環境別のインストール/前処理コマンドを指定しました。
devの時はdev.txtのインストールを、prodの時はprod.txtのインストールログ記録ファイルの作成を行います。ログ記録先はconfig/settings/prod.pyで指定しています。

ライブラリが揃ったので、最後の前処理としてDBマイグレーションスーパーユーザ作成静的ファイルの集結(Nginx処理用)をしておきます。

最後に、Gunicorn接続設定を行い、Dockerコンテナ起動時にGunicornサーバの立ち上げまで進行できる準備が整いました。

9.解説④docker-compose.ymlファイル

version: "3"

services:
 # Django application
 app:
   build: .
   ports:
     - "8000:8000"
   environment:
     DB_USER: djangouser
     DB_HOST: db                        # Write MySQL container name
     DB_PORT: 3306
     DB_PASSWORD: djangouserpass
     DB_NAME: djangodb
     DB_NAME_TEST: djangodb_test
   volumes:
     - .:/app
     - gunicorn:/var/run/gunicorn
     - staticdata:/var/www/app/static/
   depends_on:
     - db

 # MySQL
 db:
   image: mysql:5.7
   command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
   ports:
     - "3306:3306"
   environment:
     MYSQL_ROOT_PASSWORD: testpass
     MYSQL_DATABASE: djangodb
     MYSQL_USER: djangouser
     MYSQL_PASSWORD: djangouserpass
   volumes:
     - data:/var/lib/mysql
   restart: always

 # Nginx
 web:
   image: nginx:1.21.3
   ports:
     - "80:80"
   volumes:
     - "./nginx/:/etc/nginx/"
     - "staticdata:/var/www/app/static/"
     - gunicorn:/var/run/gunicorn
   depends_on:
     - app
   restart: always

volumes:
 data:
   driver: local
 staticdata:
   driver: local
 gunicorn:
   driver: local

Django + Gunicorn + Nginx + MySQLの連携設定を定義します。

ソースコードを上からざっくり解説していきましょう。

先頭行のversion句では、docker-compose.ymlの書き方バージョンを宣言します。インターネットで書き方を調べる際は、このバージョンを確認してから取り入れるようにしてください。今回は`3`を使いますが、`2`を使った参考記事もよく見かけます。

次に書くservices句の中で、システムとして連携させて動かすコンテナの関係を書きます。Dockerコンテナの情報はインテンド付きで書きます。ymlファイルは、Python同様にインテンドが意味を持つので注意してください。
今回は、連携させるDockerコンテナの名前をapp/db/webとして、各Dockerコンテナの設定を引き続き書きます。

■app句(Djangoコンテナ)
使用イメージ
は、ディレクトリ内のDockerfileビルドイメージを使います

また、ホストマシンポート8000番Dockerコンテナ8000番と接続させます。これによりホストマシンの8000番で受け取るリクエストを、Dockerコンテナの8000番に流してレスポンスさせられます。

また、設定ファイルに書くDB情報を環境変数に含めておきます。ここには、MySQLコンテナ設定値を正しく書き写してください。一点注意ですが、`DB_HOST`はMySQLコンテナの名前を書く必要があるので、違う名前をつけたら変更するのを忘れないようにしましょう。

今回は本番環境にも同じDB情報を設定しています。なので、セキュリティ観点の注意が必要です。うっかりGitHubなどでPublic公開して、DBへの不正侵入ルートを残さない様に、Private公開.envファイルを活用しましょう。

また、volumes句ではホストマシン上ファイルシステムと、コンテナ内ファイルシステム同期命令を行えます。コンテナ実行中、ホストマシンでの編集内容をコンテナ側ファイルに自動反映でき、開発時は役に立ちます。

他のコンテナデータの連携をさせたいときにも、このvolumes句でホストマシン上ファイルシステム経由で渡したりできます。volumes句に追加しているgunicornstaticdataは、この用途です。

最後のdepends_on句は、コンテナ起動順序制御できます。指定しない場合は、おそらく上から順番で起動するはずですが、depends_onで指定しておくと対象コンテナの起動後にしか立ち上がらない設定にできます。
今回はMySQL起動前だと、Djangoアプリ起動時に行うマイグレーションが通らないため、この設定にしました。

■db句(MySQLコンテナ)
イメージはDockerHubからpullするMySQLイメージそのまま使います。DockerHubにReference(使い方)が書いてあるので、それに従って設定していきます。

command句はDockerfileのCMD句と同じ効果です。environment句で指定する環境変数名は、MySQLコンテナの使い方で指定されているので、必ず同じ名前を使ってください。restart句は、ホストOS再起動・Dockerコンテナダウン時に再起動する命令です。

■Web句(Nginxコンテナ)
イメージはDockerHubからpullするNginxイメージそのまま使います。
今回、NginxはWebサーバとして、HTTP通信にレスポンスさせたいので、ports句は80番(HTTPのポート)で開ける設定にします。ちなみに、本番運用でHTTPS通信させる場合は、443に変える必要があります。

HTTP・HTTPSポート番号がなぜ80・443かについての説明は、こちらの参考記事の掲載に替えさせていただきます。結論は『国際的に決まっているから』です。

あとはvolumes句で、Nginxで使用する設定ファイルと、プロキシで返させる静的ファイルを適切なパスにリンクさせれば設定完了です。

最後のvolumes句で、Dockerコンテナ間でデータを連携させるために、ホストマシンのファイルシステム経由させる識別子を記載します。すべて、どこかのコンテナのvolumes句で使った識別子になっているはずなので、うまく動かない時は確認してみてください。

10.クローンして使う際の注意点

読者の皆さんのホストマシンに、クローンして使う際の注意点をいくつか書いておきます。

■注意点①
Nginxディレクトリ
設定ファイルと、config/settingsディレクトリ設定ファイルは、構築したDjango + Gunicorn + Nginx + MySQLシステムの挙動に関係してきます。
Dockerの解説からは逸脱するので細かに解説しませんでしたが、皆さんがクローンして本番環境での使用を検討するなら、必ず一度は中身を読んでみてください。

■注意点②
前述しましたが開発使用時は、scripts/entrypoint.shの環境変数設定の行のうち、config.settings.dev行有効にして、config.settings.prodの行をコメントアウトにします。
また、テスト実施時には`docker exec -it コンテナ名 bash`でコンテナに入り、`python manage.py test`で実行しましょう。

■注意点③
前述しましたが本番使用時は、逆にscripts/entrypoint.shの環境変数設定の行のうち、config.settings.prod行有効にします。
なお、解説したシステムはデータベースDockerで動かすことにしていますが、もし本番環境サーバ壊れた時は、データベースのバックアップがないと復元できないです。
データベースだけは、Dockerシステムから外してAWSRDS等のデータベースマネージドサービスを使うことを筆者はおすすめします。自動バックアップ高可用性なデータベースにできるので、手札として検討してみてください。

11.Dockerfileベストプラクティス

読者のみなさんが、今後Docker開発をするにあたり、Dockerfile作成のベストプラクティスについて知っておいてほしいと思います。いくつか重要な内容を取り上げて解説します。

■`不要パッケージのインストールは避ける`

複雑さ、依存関係、ファイルサイズ、構築階数をそれぞれ減らすために、余分ないし不必要な「入れた方ほうが良いだろう」というパッケージは、インストールを避けるべきです。たとえば、データベース・イメージであればテキストエディタは不要でしょう。
(出典:Docker-docs-ja https://docs.docker.jp/engine/articles/dockerfile_best-practice.html )

イメージサイズを小さく保つためにも、不要パッケージのインストールは避けましょう。イメージのサイズは`docker iamges`コマンドで確認できます。

イメージを小さくすることで、以下のメリットがあります。
・イメージの転送時間が早くなる
・イメージの起動時間が早くなる

本番環境で動かすWebサービスが何らかの理由でダウンした場合、別のサーバコンピュータに移行する必要が出てきます。Dockerイメージのサイズが大きいと↑の時間が長くなることで、サービス復旧までの時間に直接影響してきます。

■`レイヤの数を最小に`

Dockerfile の読みやすさと、使用するイメージレイヤーの数を最小化。両者のバランスを見つける必要があります。戦略的に注意深くレイヤー数を使います。
(出典:Docker-docs-ja https://docs.docker.jp/engine/articles/dockerfile_best-practice.html )

レイヤとはDockerfileコマンド(FROM、COPY、RUNなど)の数です。

レイヤを少なくすることは、ソースコードの見通しを良くする上でも重要です。コンテナ内の状況を考えて、まとめられないCOPY句・RUN句は仕方ないですが、可能な限り命令コマンド行少なくしていきましょう。

■`構築キャッシュ`

開始にあたり、ベース・イメージが既にキャッシュにあれば、次の命令を対象のベース・イメジから派生した全ての子イメージと比較します。同じ命令があれば構築にそのイメージを使います。もし同じ命令がなければ、キャッシュは無効化されます。
(出典:Docker-docs-ja https://docs.docker.jp/engine/articles/dockerfile_best-practice.html )

`docker build`のコマンドでは、前回のビルド履歴と比較して、変更があった箇所からビルドが始まります。

そのため、変更が起きにくいOS周りのライブラリインストール等の命令は、Dockerfileの序盤に記述しておくとキャッシュビルド時間短縮できます。

Docker開発を始めだすと、ビルド時間の長さを鬱陶しく感じるようになると思うので、キャッシュ活用できるコマンド順序を常に模索するようにしてみてください。

12.最後に

いかがでしたか?

Docker2013年OSS化されて以降、幅広い用途で使われてきています。

今のソフトウェア業界新しい技術は、裏側Dockerコンテナ技術支えているものがどんどん増えてきています。

そんな時代背景のため、Dockerへの理解を深めることは、Webの分野に留まらずソフトウェア技術全体の理解を深めることに直結すると言っても過言ではありません。

サービスをただ使うだけでなく、裏側で動く技術を理解できることは、筆者にとってはシンプルに面白く感じます。

技術の理解を深めるには実践が一番です。今回の記事をきっかけに、Docker開発への挑戦者が1人でも増えて、今までよりもっと楽しい・面白いソフトウェア技術者人生を送る方が増えてくださると嬉しいです。

それでは、また次の記事をお楽しみに!

よろしければサポートをお願いします🙇‍♂️ いただいたサポートは、クリエイターの活動資金として使わせていただきます😌 活動を通してえた経験を、また記事として皆さんと共有します👍