MacOS、Windows、Linux等さまざまなプラットフォーム上でアプリ開発をしていると、「この環境じゃ動かない」といった問題がよくあります。Dockerの様なコンテナ技術を利用すれば、コマンド1つで簡単に開発環境を整えることができ、セットアップに無駄な時間を費やすことはありません。
ここでは、Dockerを使ったPythonアプリの環境構築方法を紹介します。
FlaskのWebアプリ開発環境を例に、開発・本番の両環境を構築しますが、Docker Multistage Buildを使って1つのDockerファイルで作成していきます。
また、開発環境にはデバッガーをインストールし、Dockerコンテナ上でのリモートデバッグもセットアップします。
デバッグには、Visual Studio Codeの Microsoft Python Extensionを使用します。
さっそく始めてみましょう!
プロジェクトファイルの構成
まずは以下のような構成でPythonプロジェクトを作成します。
MyFlaskApp/
├ app.py
├ docker-compose.yaml
├ Dockerfile
└ requirements.txt
プロジェクトファイルの内容は、Flaskアプリのコードを書く app.py
、Dockerイメージをビルドする為の Dockerfile
とコンテナで開発環境を構成する為の docker-compose.yaml
、Flaskアプリに必要なPythonモジュールを列挙する requirements.txt
となります。
まずは空のファイルを作成し、それぞれに書く内容は後ほど説明していきます。
mkdir MyFlaskApp
cd MyFlaskApp
touch app.py
touch docker-compose.yaml
touch Dockerfile
touch requirements.txt
アプリケーションコード
app.py
ファイルでは、簡単なデモのためにFlaskのドキュメントからHelloWorldのコードをコピペするだけです。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == "__main__":
app.run(
debug=True,
host='0.0.0.0',
port=5000
)
以下のブロックは
@app.route('/')
def hello_world():
return 'Hello, World!'
ルートに /
を割り当てて、"Hello World!"の文字列を返します。
最後のブロックは
if __name__ == "__main__":
app.run(
debug=True,
host='0.0.0.0',
port=5000
)
コマンドラインからスクリプトを実行したときにFlaskアプリを起動するコードです。
(例: python app.py
や python -m app
)
Dockerfile
以下を Dockerfile
にコピペします。
FROM python:3.7-alpine AS base
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV="docker"
ENV FLASK_APP=app.py
EXPOSE 5000
# Development Stage
FROM base AS develop
RUN pip install debugpy
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1
# Production Stage
FROM base AS production
RUN pip install --no-cache-dir gunicorn
COPY . .
CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:5000", "app:app"]
ここでは、1つのDockerfile
で複数のステージ(Docker images)をビルドできる
Multistage Buildsという機能を利用して、3つのステージを設定しています。
-
base
: 依存モジュールをインストールする -
development
: 開発に必要な依存モジュールのインストール(デバッガーなど) -
production
: イメージにソースファイルをコピーし、本番環境用にプロキシサーバをインストールします。
Baseステージ
Dockerfile
の最初の行で base
ステージを宣言します。
FROM python:3.7-alpine AS base
FROM
ではアプリ構築のベースとなる環境を指定します。
ここではpythonアプリを作成するので、python 3.7のDockerイメージを指定します。
OSの選択は開発したいものにもよりますが、ここでは軽量なUbuntuディストリビューションであるAlpine
を選択します。
AS base
でbase
というステージ名にします。ステージ名は自由につけられます。
以降のコマンドは別のFROM
が現れるまで、base
ステージに適用されます。
WORKDIR /app
は、イメージ内のアプリケーション用のフォルダを作成し、そのフォルダへ移動します。
次の2行で
COPY src/requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
app/
フォルダにPython依存モジュールのリストをコピーし、依存モジュールのインストールまで行います。
次の3行でFlask用の環境変数を定義します。
ENV FLASK_ENV="docker"
ENV FLASK_APP=app.py
EXPOSE 5000
ENV FLASK_ENV="docker"
の行では、Flaskが起動時に参照する環境設定を*.envファイルに定義しています。
このチュートリアルでは.envファイルは使用しませんが、とりあえず追加しておきます。
2行目のENV FLASK_APP=app.py
は、起動時にどのファイルを実行するかをFlaskに指示します。
最後の行のEXPOSE 5000
では、Flaskアプリケーションのポートをapp.py*で設定した5000に設定しています。
Development ステージ
developmentステージでは、base
ステージを再利用し、デバッガーを追加でインストールします。
developmentステージは以下のように定義します。
FROM base as develop
以降のコマンドは別のFROM
が現れるまで、develop
ステージに適用されます。
pythonのデバッガーはdebugpyをインストールします。
RUN pip install debugpy
次に、開発環境用にpythonのセットアップを行います。
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1
最初の行ENV PYTHONDONTWRITEBYTECODE 1
は python のbyte-code generationを無効にします。
これは本番環境では有効にするべき対応ですが、頻繁にコードが変更される開発環境ではハードディスクのメモリが増えてしまいます。
もし開発中に動作が遅いと感じた場合は、この設定をコメントアウトして下さい。
2行目のENV PYTHONUNBUFFERED 1
はログ用のバッファを無効にし、メッセージを直接 dockerコンテナのランタイムに表示します。
ソースファイルはdockerイメージにコピーせず、dockerコンテナ起動時にローカルディレクトリから直接リンクします。セットアップはdocker-compose.yaml
ファイルの説明で行います。
Production ステージ
productionステージは、デバッガーをインストールした状態で本番稼働させたくありません。
なので、base
ステージから作成します。
FROM base as production
本番環境へアクセスさせる為、HTTPサーバとしてgunicornをインストールしています。
RUN pip install --no-cache-dir gunicorn
次に、本番ビルド時にはソースコードの変更はないのでイメージにコピーします。
COPY . .
最後の行では、起動時にgunicorn HTTPサーバを立ち上げます。サーバはbase
ステージで設定した5000番ポートをリッスンし、app.py を実行します。
CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:5000", "app:app"]
Docker Compose ファイル
ここではDocker Composeを使って開発環境のセットアップをします。
docker-compose.yaml
に以下をコピペして下さい。
version: "3.9"
services:
flask-app:
image: my_flask_app-develop
container_name: my_flask_app-develop
build:
context: .
target: develop
ports:
- 5000:5000
- 5678:5678
volumes:
- .:/app
environment:
- FLASK_DEBUG=1
entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app", "--wait-for-client", "--multiprocess", "-m", "flask", "run", "--host", "0.0.0.0", "--port", "5000" ]
networks:
- my_flask_app-develop
networks:
my_flask_app-develop:
name: my_flask_app-develop
次に各セクションの設定を説明していきます。
Services セクション
servicesの部分で使用するサービスを定義します。
今回はFlaskアプリを定義するだけですが、データベースなどのdockerイメージを用意し、サービスとして追加することもできます。
以下の行でFlaskアプリをサービスとして定義しています。
services:
flask-app:
image: my_flask_app-develop
container_name: my_flask_app-develop
image
ではDockerイメージのビルド時に割り当てるタグ名を指定します。
タグ名は後からdocker images
コマンドを使ってイメージのリストから対象のイメージを見つける時に役立ちます。
container_name
ではコンテナ名を指定します。
コンテナ名を指定しない場合は、ランダムに生成されたコンテナ名が割り当てられます。
build
セクションでは、dockerに何をビルドするかを指示します。
build:
context: .
target: develop
context
にはdocker-compose.yaml
からみたビルド対象となるDockerfile
の位置を指定します。
target
では、ビルドするステージを指定します。この場合はdevelopment
ステージです。
ports
セクションでは使用するポートを定義します。
ports:
- 5000:5000
- 5678:5678
今回は、ローカルポート5000と5678をコンテナ内の同じポート番号にマッピングしています。
ポート5000は Flask アプリにアクセスする為の指定で、ブラウザを開いて http://localhost:5000 からアプリを実行することができます。
ポート5678は、デバッガがデバッグ要求をリッスンするために使用します。
volumes
を使用して、コンテナとローカルアプリケーションフォルダをリンクすることができます。
volumes:
- .:/app
これにより、ソースコードを変更する度にdocker imageを再ビルドせず、ソースコードの変更をコンテナに反映することができます。
environment
では環境変数を定義することができます
environment:
- FLASK_DEBUG=1
今回は、Flaskでデバッグモードを有効にします。これにより、ブラウザ上に詳細なエラーメッセージが表示されます。
entrypoint
は最も重要なセクションで、起動時に何を実行するかを定義します。
entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app", "--wait-for-client", "--multiprocess", "-m", "flask", "run", "--host", "0.0.0.0", "--port", "5000" ]
最初にデバッガーを実行してポート 5678 をリッスンします。
次に、Flask アプリをデバッガーに設定し、デバッガーはポート5678でクライアントの接続を待機させます。
multiprocess を指定すると、ポート5000でのデバッグの他に、実行中のアプリケーションにアクセスできるようになります。
Networks セクション
networksでは、サービスをして定義することで、異なるサービス間の通信を制御することができます。
今回はFlaskアプリ用のネットワークだけ定義しています。詳しい情報やDockerコンテナ間の接続の確立方法については、Docker ComposeのドキュメントのNetworkを参照してください。
services:
flask-app:
# ...
networks:
- my_flask_app-develop
networks:
my_flask_app-develop:
name: my_flask_app-develop
requirements.txt ファイル
requirements.txt
ファイルには、Flaskアプリを実行するために必要なpythonモジュールを全て記載します。
今回必要なモジュールはflaskのみなので、以下をコピペして下さい。
Flask
今回はFlaskのバージョンを指定しておりません。必要な場合は適宜設定して下さい。 (例 Flask==1.1.2)
開発環境の起動
開発環境を実行するには、アプリケーションディレクトリ内で次のコマンドを実行するだけです。
docker-compose up -d
develop
ステージのdockerイメージがビルドされ、アプリのdockerコンテナが立ち上がります。
コンテナを停止するには次のコマンドを実行します。
docker-compose down
docker composeをフォアグラウンドで実行したい場合は、-dフラグを省いて下さい。その際は、CTRL+Cでアプリを停止できます。
デバッグ
デバッグにはVisual Studio Codeを使用します。
PyCharmを利用したい場合、無償のCommunity Editionではリモートデバッグをサポートしていない為、Professional Editionライセンスが必要となります。
まずはアプリケーションフォルダで以下のコマンドを実行します。
cd MyFlaskApp
code .
code: command not found
のエラーが表示された場合、Visual Studio Codeをアプリアイコンから開き、
Macの場合はCommand+Shift+P(WindowsやLinuxの場合はCtrl+Shift+P)でコマンドパレットを起動します。
「ShellCommand」と入力し、「Shell Command:Install 'code' command in PATH」を選択します。
アプリケーションフォルダがVisual Studio Codeで開くはずです。
Python Extensionのインストール
左メニューのExtensionsをクリックします。検索バーに"python"と入力し、
Microsoft Python Extensionをインストールします。
リモートデバッグの設定
デバッグ設定は*.vscode/フォルダ内のlaunch.jsonに保存されます。
リモートデバッグの設定手順はlaunch.json*が既にプロジェクトフォルダにあるかによって異なります。
launch.json ファイルの作成方法
launch.jsonファイルが存在しない場合は、次の手順で生成します。
app.py ファイルをVisual Studio Codeで開いて、ボトムバーにPython言語が自動設定されているか、確認します。
左メニューのDebugをクリックし、次に、create a launch.json fileをクリックします。
launch.jsonファイルが存在しない場合Python Extensionのインストールに成功していれば、デバッグ設定のリストからPythonを選択できるはずです。
launch.jsonファイルを作成次に、Remote Attachを選択します。
次に、接続するリモートデバッガーを選択します。ホスト名はlocalhost、ポートは5678番を使用します。
自動作成されたlaunch.jsonは下記の通りです。
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
]
}
既のlaunch.jsonに設定追加方法
プロジェクトフォルダに既にlaunch.jsonファイルがある場合は、以下の設定を追加します。
app.py ファイルをVisual Studio Codeで開いて、ボトムバーにPython言語が自動設定されているか、確認します。
左メニューのDebugをクリックし、表示されたセレクトボックスから下矢印をクリック**Add Configuration...**を選択し、設定を追加することも可能です。
launch.jsonファイルが存在する場合Python Extensionのインストールに成功していれば、デバッグ設定のリストからPythonを選択できるはずです。
launch.jsonファイルに設定追加の場合次に、Remote Attachを選択します。
次に、接続するリモートデバッガーを選択します。ホスト名はlocalhost、ポートは5678番を使用します。
launch.jsonのconfigurationsは下記の通りです。
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
// ...他の設定
]
}
ブレークポイントの設定とデバッガーのアタッチ
app.py
ファイルを開き、7行目にブレークポイントを設定します。次に、左側にある緑色の再生ボタンをクリックして、デバッガーに接続します。
ブラウザで http://localhost:5000 にアクセスし、アプリを起動します。
アプリはブレークポイントで停止するはずです。
何度でもデバッガーに接続可能です。
本番ビルド
production
ステージをビルドするには、アプリケーションフォルダ内で次のコマンドを実行します。
docker build --target production -t my_flask_app .
--target
でビルドするステージを指定し、-t my_flask_app
でイメージに適当なタグ名を付けます。
最後の*.*は重要で、Dockerfileの場所、つまりカレントディレクトリを指しています。
ビルドされたイメージには任意のタグ名がつけられますが、開発と本番で同じものを使用しない様に注意して下さい。
ビルド済み Images の一覧
docker images
コマンドで、ビルドしたイメージをリスト表示することができます。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my_flask_app latest a72b11d35c59 2 days ago 52.2MB
my_flask_app-develop latest 5916db28c0ff 2 days ago 80.5MB
デバッガーあり(80.5MB)とデバッガーなし(52.2MB)のイメージサイズの違いも確認できます。
本番 Image の起動
本番イメージからコンテナを生成して実行するには以下のコマンドを使用します。
docker run -d --rm -p 5001:5000 --name my_flask_app my_flask_app:latest
ここではローカルのポート5001をコンテナのポート5000にマップすることで、本番ステージと開発ステージを同時に実行できるようにします。
Stage | URL |
---|---|
development | http://localhost:5000 |
production | http://localhost:5001 |
次のコマンドで実行中のコンテナをリスト表示できます。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8bc12d89d9f my_flask_app:latest "gunicorn --reload -…" 7 seconds ago Up 6 seconds 0.0.0.0:5001->5000/tcp my_flask_app
コンテナの停止は以下のコマンドのみです。
docker stop my_flask_app
コマンドをフォアグラウンドで実行したい場合は、-dフラグを省いて下さい。その際は、CTRL+Cでアプリを停止できます。
トラブルシューティング
Remote Attachについて
解決方法: vscodeの設定で、Show Sub Sessions In Tool Bar
が有効になっていることを確認してください。
Githubのチケット