【サンプルコード付き】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のインストールライブラリは、複数プロジェクトのものが混同していて、本番サーバでインストールし忘れがある
・ローカルPCのOSと、本番サーバのOSが異なることで、インストールが必要なライブラリの数や名前が違い、本番サーバでインストールし忘れがある
が多いと思います。
Dockerを使う場合、以下の図のイメージで動作させることができます。
動作させるDockerコンテナの定義は、Dockerfileというファイルに環境とソフトウェアの動作を記述して行います。このDockerfileは、基本的にソフトウェアごとに1ファイル作る必要があり、他プロジェクトの影響を受けない閉じた環境を定義できます。(1点目の解決)
また、Dockerfileで作られるソフトウェアの動作環境は、ホストマシンのいかなる影響も受けません。ホストマシンのOS差異にすらも影響を受けず、動作環境を作れます。これはDockerを使う上でホストマシンにインストールが必要なDocker Engineが緩衝材としてはたらくためです。(2点目の解決)
こうした仕組みでDockerコンテナは、ホストマシンの影響を受けずいつでもどこでも実行できる特徴を持っているわけです。
一点余談ですが、Dockerがいつでも同じ様に動かない例外もあります。
USB接続等、外部デバイスを使ったプログラムだけは、プログラムの中でホストマシンの物理的な特徴(USBポートの番号の指定)を書く必要があるので、ホストマシンを変えれば動作は大体変わります。
詳細が気になる方は以下の参考記事を読んで見てください。
外部デバイスを使わないプログラムなら動作は保証されるので、あまり問題視せず余談で挙げておきました。
②Dockerfileによって環境でバージョン管理でき、バグ発生しても前の環境にすぐ戻せる
Dockerを使わない場合、こんな状況に遭遇することがあります。
『あれ?!さっきまで動いてたのに、必要そうなライブラリ入れたら動かなくなった!しかも戻し方分からない...』
Dockerを使う場合、Dockerfileもプロジェクトのバージョン管理対象として含められる様になります。簡単に言えば、Git管理できるようになります。
なのでソフトの動作環境の変更時は、期待通りの動作を細かく確認しつつ更新を進めれば、バグが起きてもコミットせず変更を破棄すればすぐに元に戻せるわけです。
少し脱線ですがGitHubなどのGitホスティングサービスは、SourceTreeというソフトウェアと連携させるとすべてGUIでGitコマンド操作できるようになるので、便利です。単なる筆者の趣向ですが、気に入った方は使っていただけると良いかと思います。(⏬サービスページとMacのインストール方法を参考記事で掲載)
『動作環境をコードで管理できる』概念は、IaC(Infrastructure as a Code)という言葉で認知されてきているらしく、重要度が増してきています。
近年は、特にインフラ分野(本番環境を提供するAWS等のサービス)では色々とDokcerを使うことが主流になってきています。
③2つ以上のDockerコンテナを組み合わせたシステムとしても動かせる
このメリットについては、特に従来からの課題はないですが、当然システム構築にも利用できます。むしろソフトウェア開発にDockerを使う開発現場では、この形の方がDockerコンテナを単体で使うよりも一般的かと思います。
2つ以上のコンテナを組み合わせるイメージを以下の図で表します。
Dockerコンテナは1つのホストマシン上に複数立てられます。そして、Dockerコンテナごとのソフトウェアの連携のために、データの共有や通信をさせることもできます。
これにより、例えばWebサービスの3層構造である、DBサーバ、アプリサーバ、WebサーバをDockerコンテナ1つずつに分けて連携させたシステムを作ったりできます。(今回の記事で作るDjangoプロジェクトの構成もこの3層構造を作ります。)
もちろんこの連携の定義もコードで行います。
複数のDockerコンテナを組み合わせたシステムを作る時は、Dockerfileとは別のファイルdocker-compose.ymlにDockerコンテナ間の連携をコードで表現する必要があります。
具体的な書き方は、後のパートで解説していくので、この記事で学んでいきましょう。Dockerfileもdocker-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コマンドの出力のREPOSITORY、IMAGE_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を、DBにMySQLを、アプリサーバに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コンテナシステムの必要ファイルを追加しました。
Dockerfileやdocker-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を使うことにしました。
Slimは省サイズのイメージで、bullseyeはDebian(LinuxOSの派生系OS)のイメージを表してます。
Dockerベストプラクティスでイメージは小さく保つ記載があったのと、Docker公式推奨のベースイメージがDebianのため今回はそうしています。
`イメージを小さく保つ`はベースイメージの指定以外にも、Dockerfileそのものの書き方を気をつけないといけないのですが、まずは小さいのを選ぶのが出発点です。
続いてOS周りで必要なライブラリのインストールを行います。作るシステムの構成やベースイメージの選定(UbuntuかDebianかAlpineか、プレインストールが多いか、等)で大きく変わるので、プロジェクトの都度必要なライブラリを調べながら指定します。
例えば、今回はMySQLを使用することから、ベースイメージには含まれないlibmariadb-devとdefault-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のコンテナを使います。そのため、開発用ライブラリにもMySQLとGunicornのライブラリを含みます。
ただし、開発用DBはSQliteにするのが一般的なので、読者の皆さんのホストマシンでは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句に追加しているgunicornとstaticdataは、この用途です。
最後の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システムから外してAWSのRDS等のデータベースマネージドサービスを使うことを筆者はおすすめします。自動バックアップで高可用性なデータベースにできるので、手札として検討してみてください。
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.最後に
いかがでしたか?
Dockerは2013年にOSS化されて以降、幅広い用途で使われてきています。
今のソフトウェア業界の新しい技術は、裏側をDockerコンテナ技術が支えているものがどんどん増えてきています。
そんな時代背景のため、Dockerへの理解を深めることは、Webの分野に留まらずソフトウェア技術全体の理解を深めることに直結すると言っても過言ではありません。
サービスをただ使うだけでなく、裏側で動く技術を理解できることは、筆者にとってはシンプルに面白く感じます。
技術の理解を深めるには実践が一番です。今回の記事をきっかけに、Docker開発への挑戦者が1人でも増えて、今までよりもっと楽しい・面白いソフトウェア技術者人生を送る方が増えてくださると嬉しいです。
それでは、また次の記事をお楽しみに!
よろしければサポートをお願いします🙇♂️ いただいたサポートは、クリエイターの活動資金として使わせていただきます😌 活動を通してえた経験を、また記事として皆さんと共有します👍