Dockerは、大手IT企業が自社のアプリケーションを構築、管理、保護するために広く受け入れられ、使用されているツールです。
Dockerのようなコンテナを使用すると、開発者はサーバー上の各アプリケーションに仮想マシンを専用にするのではなく、単一のオペレーティングシステム上で複数のアプリケーションを分離して実行することができます。このような軽量なコンテナを使用することで、コストの削減、リソースの有効活用、パフォーマンスの向上につながります。
さらに詳しく知りたい方は、Docker: A High Level Introduction」をご覧ください。
この記事では、Flaskを使用して簡単なPythonウェブアプリケーションを書き、それを「Docker化」する準備をします。その後、Docker Imageを作成し、テスト環境と本番環境の両方にデプロイします。
注意:このチュートリアルは、あなたのマシンにDockerがインストールされていることを前提にしています。そうでない場合は、公式のDockerインストールガイドに従うと良いでしょう。
Dockerとは?
Dockerは、開発者がアプリケーションを(ライブラリやその他の依存関係とともに)出荷し、それらがデプロイされる環境にかかわらず、まったく同じ構成で実行できることを保証するためのツールです。
これは、アプリケーションを個々のコンテナに分離することで実現され、コンテナによって分離されてはいるものの、オペレーティングシステムと適切なライブラリは共有されています。
Dockerは、以下のように分解できます。
- Docker Engine – アプリケーションをコンテナ化するために使用されるソフトウェアパッケージングツール。
- Docker Hub – クラウド上でコンテナ・アプリケーションを管理するためのツール。
なぜコンテナなのか?
コンテナの重要性と有用性を理解することは重要です。サーバにデプロイされた単一のアプリケーションや、ホームプロジェクトではあまり違いがないかもしれませんが、堅牢でリソースの多いアプリケーション、特に同じサーバを共有している場合や、多くの異なる環境にデプロイされている場合は、コンテナは救世主となります。
これはまず、VMWareやハイパーバイザーなどの仮想マシンで解決されましたが、効率、スピード、移植性に関しては最適でないことが証明されています。
Dockerコンテナは仮想マシンの軽量な代替品です。VMとは異なり、RAM、CPU、その他のリソースを事前に割り当てる必要はなく、1つのオペレーティングシステムで作業するため、アプリケーションごとに新しいVMを起動する必要がありません。
開発者は、さまざまな環境用に特別なバージョンのソフトウェアを出荷する負担がなく、アプリケーションの背後にある中核的なビジネス・ロジックの作成に集中することができます。
プロジェクトセットアップ
Flask は Python のマイクロフレームワークで、シンプルなものから高度なものまで、様々な Web アプリケーションを作成するために使用されます。その使いやすさとセットアップの簡単さから、私たちのデモアプリケーションにはこれを使用します。
もし、Flask がまだインストールされていない場合は、コマンド一つで簡単にインストールすることができます。
$ pip install flask
Flask のインストールが完了したら、例として FlaskApp
という名前でプロジェクトフォルダを作成します。このフォルダの中に、app.py
のような名前のベースファイルを作成します。
app.py内に
Flask` モジュールをインポートして、以下のように Web アプリを作成します。
from flask import Flask
app = Flask(__name__)`
次に、基本ルート /
とそれに対応するリクエストハンドラを定義します。
@app.route("/")
def index():
return """
<h1Python Flask in Docker!</h1
<pA sample web-app for running Flask inside Docker.</p
"""
最後に、このスクリプトがメインプログラムとして起動された場合に、アプリを起動してみましょう。
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0')
$ python3 app.py
ブラウザでhttp://localhost:5000/
にアクセスしてください。すると、「Dockerzing Python app using Flask」というメッセージが表示されるはずです!
アプリケーションのDocker化
Docker でアプリケーションを実行するには、使用するすべての依存関係を含むコンテナをビルドする必要があります – この例では Flask だけです。これを行うには、必要な依存関係を含む requirements.txt
ファイルをインクルードし、イメージを構築するためにそのファイルに依存する Dockerfile を作成することになります。
また、コンテナを起動する際に、アプリが動作しているHTTPポートにアクセスできるようにする必要があります。
アプリケーションの準備
依存関係を requirements.txt
ファイルに含めるのは非常に簡単です。単に依存関係の名前とバージョンを含めるだけでいいのです。
Flask==1.0.2
次に、アプリケーションの実行に必要なすべての Python ファイルが、例えば app
というトップレベルのフォルダの中にあることを確認する必要があります。
また、デプロイを容易にするために、スクリプトで作成した Flask オブジェクトに app
という名前を付けるのが良い習慣なので、メインのエントリポイントに app.py
という名前を付けることをお勧めします。
docker-flask-tutorial
├── requirements.txt
├── Dockerfile
└── app
└── app.py
└── <other .py="" files=""
Dockerfileの作成
Dockerfile は基本的に、私たちのプロジェクトのために Docker イメージを構築する方法を明確に定義したテキストファイルです。
次に、Ubuntu 16.04 と Python 3.X をベースとした Docker イメージを作成します。
FROM ubuntu:16.04
MAINTAINER Madhuri Koushik "madhuri@koushik.com"
RUN apt-get update -y &&
apt-get install -y python3-pip python3-dev
COPY ./requirements.txt /requirements.txt
WORKDIR /
RUN pip3 install -r requirements.txt
COPY . /
ENTRYPOINT [ "python3" ]
CMD [ "app/app.py" ]
ここでは、適切な説明に値するいくつかのコマンドがあります。
- FROM – すべてのDockerfileは
FROM
キーワードから始まります。これはイメージをビルドする際のベースとなるイメージを指定するために使用します。次の行はイメージのメンテナに関するメタデータを提供します。 - RUN – インストールタスクを実行し、そのコマンドの結果を保存することで、イメージにコンテンツを追加することができます。ここでは、単純にパッケージ情報を更新し、
python3
とpip
をインストールします。2 番目のRUN
コマンドでpip
を使用し、requirements.txt
ファイルにあるすべてのパッケージをインストールします。 - COPY –
COPY
コマンドは、ビルド中にホストマシンからコンテナにファイルやディレクトリをコピーするために使用されます。この例では、requirements.txt
を含むアプリケーションファイルをコピーしています。 - WORKDIR – RUN や COPY などで使用される、コンテナ内の作業ディレクトリを設定します。
- ENTRYPOINT – アプリケーションのエントリポイントを定義します。
- CMD –
app
ディレクトリにあるapp.py
ファイルを実行します。
Dockerイメージの構築方法
Dockerイメージは docker build
コマンドでビルドされます。イメージをビルドする際、Dockerはいわゆる “レイヤー “を作成します。各レイヤーはDockerfileのコマンドによる変更と、コマンド実行後のイメージの状態を記録します。
Dockerはこれらのレイヤーを内部的にキャッシュし、イメージを再ビルドする際に、変更されたレイヤーだけを再作成する必要があります。例えば、一度ubuntu:16.04
のベースイメージをロードすれば、その後の同じコンテナのビルドはすべてこれを再利用することができます。しかし、再ビルドのたびにappディレクトリの内容が変更される可能性があるため、このレイヤーは毎回リビルドされることになります。
どのレイヤーを再構築しても、Dockerfile内でそれに続くすべてのレイヤーも再構築される必要があります。Dockerfileを作成する際には、この事実を心に留めておくことが重要です。例えば、最初に requirements.txt
ファイルを COPY
して、依存関係をインストールしてから、アプリの残りの部分を COPY
します。この結果、すべての依存関係を含むDockerレイヤーが作成されます。このレイヤーは、アプリ内の他のファイルが変更されても、新しい依存関係がない限り、再ビルドする必要はありません。
このように、pip install
をアプリの残りの部分のデプロイから分離することで、コンテナのビルドプロセスを最適化します。
Dockerイメージの構築
Dockerfile が準備できたので、ビルドプロセスの仕組みを理解し、アプリ用の Docker イメージを作成してみましょう。
$ docker build -t docker-flask:latest .
自動再起動のあるデバッグモードでのアプリケーションの実行
前述したコンテナ化の利点から、コンテナ内にデプロイされるアプリケーションをコンテナ内で開発することは理にかなっています。これにより、アプリが構築される環境が最初からクリーンであることが保証され、配信時のサプライズを排除することができます。
しかし、アプリの開発では、開発中の各中間ステップをチェックするために、迅速な再構築とテストサイクルを持つことが重要です。この目的のために、Webアプリの開発者はFlaskのようなフレームワークが提供する自動再起動機能に依存しています。コンテナ内からもこれを利用することが可能です。
自動再起動を有効にするために、開発ディレクトリをコンテナ内のアプリディレクトリにマッピングしてDockerコンテナを起動します。これは、Flaskが(このマッピングを通して)ホスト内のファイルに変更がないか監視し、変更を検出したときに自動的にアプリケーションを再起動することを意味します。
さらに、アプリケーションのポートをコンテナからホストに転送する必要があります。これは、ホスト上で動作するブラウザがアプリケーションにアクセスできるようにするためです。
これを実現するために、ボリュームマッピングとポートフォワーディングのオプションを指定して、Dockerコンテナを起動します。
$ docker run --name flaskapp -v$PWD/app:/app -p5000:5000 docker-flask:latest
これは、以下のようになります。
- 以前ビルドした
docker-flask
イメージをベースにしたコンテナを起動します。 - このコンテナの名前は
flaskapp
に設定されます。name` オプションを指定しない場合、Dockerは任意の(そして非常に興味深い)名前をコンテナに選択します。名前を明示的に指定することで、コンテナの場所を特定するのに役立ちます(停止する場合など)。 - v` オプションは、ホスト上のアプリケーションフォルダをコンテナにマウントします。
- p` オプションはコンテナ上のポートをホストにマッピングします。
これで、アプリケーションは http://localhost:5000
または http://0.0.0.0:5000/
でアクセスできるようになります。
コンテナの実行中にアプリケーションに変更を加えてファイルを保存すると、Flask はその変更を検知してアプリケーションを再起動します。
コンテナを停止するには、Ctrl
–C
を押しながら、docker rm flaskapp
を実行してコンテナを削除します。
本番モードでアプリケーションを実行する
Flask で直接アプリを実行するのは開発には十分ですが、本番環境ではより堅牢なデプロイ方法を使用する必要があります。
通常、Flask の Web アプリは複数の並列接続を処理する必要があるため、WSGI 準拠の Web サーバー上にデプロイされるのが一般的です。
一般的な代替手段は nginx + uwsgi で、このセクションでは本番用のWebアプリをセットアップする方法を紹介します。Nginxはオープンソースのウェブサーバで、uWSGIは「高速で自己回復可能なアプリケーションコンテナサーバ」です。
まず、開発モードか本番モードでアプリケーションを起動するファサードを作成し、モードに応じてnginxかPythonを直接実行するように選択します。
このファイルを launch.sh
と呼び、単純なシェルスクリプトとします。このファイルは entry-point.sh をベースにしています。
#!/bin/bash
if [ ! -f /debug0 ]; then
touch /debug0
while getopts 'hd:' flag; do
case "${flag}" in
h)
echo "options:"
echo "-h show brief help"
echo "-d debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
exit 0
;;
d)
touch /debug1
;;
*)
break
;;
esac
done
fi
if [ -e /debug1 ]; then
echo "Running app in debug mode!"
python3 app/app.py
else
echo "Running app in production mode!"
nginx && uwsgi --ini /app.ini
fi
次に、アプリのためのuWSGI設定ファイルとnginx設定ファイルを作成します。
基本的にこのファイルには、uWSGI/nginx に対するアプリケーションのエントリーポイントを記述します。
[uwsgi]
plugins = /usr/lib/uwsgi/plugins/python3
chdir = /app
module = app:app
uid = nginx
gid = nginx
socket = /run/uwsgiApp.sock
pidfile = /run/.pid
processes = 4
threads = 2
最後に、Dockerfile を修正して nginx と uWSGI を組み込みます。nginx、uWSGI、そしてuWSGI Python3プラグインのインストールとは別に、 nginx.conf
を適切な場所にコピーし、nginxの実行に必要なユーザパーミッションをセットアップするようにしました。
また、Dockerfile の ENTRYPOINT
にはシェルスクリプトが設定され、コンテナをデバッグモードまたは本番モードで実行するのに役立ちます。
FROM ubuntu:16.04
MAINTAINER Madhuri Koushik "madhuri@koushik.com"
RUN apt-get update -y &&
apt-get install -y python3-pip python3-dev &&
apt-get install -y nginx uwsgi uwsgi-plugin-python3
COPY ./requirements.txt /requirements.txt
COPY ./nginx.conf /etc/nginx/nginx.conf
WORKDIR /
RUN pip3 install -r requirements.txt
COPY . /
RUN adduser --disabled-password --gecos '' nginx
&& chown -R nginx:nginx /app
&& chmod 777 /run/ -R
&& chmod 777 /root/ -R
ENTRYPOINT [ "/bin/bash", "/launcher.sh"]
これで、イメージを再構築することができます。
$ docker build -t docker-flask:latest .
そして、nginx を使用してアプリを実行します。
$ docker run -d --name flaskapp --restart=always -p 80:80 docker-flask:latest
このイメージは自己完結しており、デプロイ時にポートマッピングを指定するだけでよい。これで、バックグラウンドでコマンドが起動・実行されます。このコンテナを停止、削除するには、以下のコマンドを実行します。
$ docker stop flaskapp && docker rm flaskapp
また、デバッグや機能追加が必要な場合は、独自のバージョンのソースツリーをマウントして、コンテナをデバッグモードで簡単に実行することができます。
$ docker run -it --name flaskapp -p 5000:5000 -v$PWD/app:/app docker-flask:latest -d
外部依存の管理
アプリをコンテナとして出荷する場合、忘れてはならない重要な項目は、依存関係の管理に対する開発者の責任が増えるということです。正しい依存関係とバージョンを特定し指定することに加え、コンテナ環境での依存関係のインストールとセットアップにも責任を負わなければなりません。
幸いなことに、requirements.txt
は依存関係を指定するための簡単なメカニズムです。pip` 経由で利用可能なパッケージであれば、どのようなものでもこれに追加することができます。
しかし、やはり requirements.txt
ファイルが変更されると、いつでも Docker イメージを再構築する必要があります。
起動時に依存関係をインストールする
時には、起動時に追加の依存関係をインストールすることが必要になることがあります。例えば、開発中に新しいパッケージを試していて、毎回 Docker イメージを再ビルドしたくない場合や、起動時に最新の利用可能なバージョンを使用したい場合などです。アプリケーションの起動時に pip
を実行するようにランチャーを修正することで実現可能です。
同様に、OSレベルのパッケージの依存関係も追加でインストールすることができます。では、launcher.sh
を修正します。
#!/bin/bash
if [ ! -f /debug0 ]; then
touch /debug0
if [ -e requirements_os.txt ]; then
apt-get install -y $(cat requirements_os.txt)
fi
if [ -e requirements.txt ]; then
pip3 install -r requirements.txt
fi
while getopts 'hd' flag; do
case "${flag}" in
h)
echo "options:"
echo "-h show brief help"
echo "-d debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
exit 0
;;
d)
echo "Debug!"
touch /debug1
;;
esac
done
fi
if [ -e /debug1 ]; then
echo "Running app in debug mode!"
python3 app/app.py
else
echo "Running app in production mode!"
nginx && uwsgi --ini /app.ini
fi
ここで、requirements_os.txt
にスペースで区切られたパッケージ名のリストを一行で指定すると、アプリの起動前に requirements.txt
のパッケージと一緒にこれらのパッケージがインストールされます。
これは開発中の便利な機能ですが、いくつかの理由から、起動時に依存関係をインストールするのは良い習慣ではありません。
- デプロイ環境の変更によって変わることのない依存関係を修正し、テストするというコンテナ化の目的の1つを破ってしまう。
- アプリケーションの起動時にオーバーヘッドが追加され、コンテナの起動時間が長くなる。
- アプリケーションを起動するたびに依存関係を取得することは、ネットワークリソースの不適切な使用となります。
結論
この記事では、広く使われているコンテナ化ツールであるDockerに飛び込んでみました。私たちはFlaskを使ったシンプルなWebアプリケーションを作成し、Webアプリケーションを開発モードと本番モードで動作させるためにUbuntuをベースにしたカスタムDockerイメージを作成しました。
最後に、Dockerコンテナ内でnginxとuWSGIを使用してWebアプリケーションのデプロイメントを設定し、外部の依存関係をインストールする方法を検討しました。
コンテナ化は、クラウドでのアプリケーションの迅速な開発とデプロイを可能にする強力な技術であり、ここで学んだことを自分のアプリケーションに適用できることを期待しています。
をご覧ください。