LoginSignup
142
187

More than 1 year has passed since last update.

Dockerひとつでリモートデバッグ可能な開発・本番環境を作る方法

Last updated at Posted at 2021-03-12

MacOS、Windows、Linux等さまざまなプラットフォーム上でアプリ開発をしていると、「この環境じゃ動かない」といった問題がよくあります。Dockerの様なコンテナ技術を利用すれば、コマンド1つで簡単に開発環境を整えることができ、セットアップに無駄な時間を費やすことはありません。

ここでは、Dockerを使ったPythonアプリの環境構築方法を紹介します。
FlaskのWebアプリ開発環境を例に、開発・本番の両環境を構築しますが、Docker Multistage Buildを使って1つのDockerファイルで作成していきます。
また、開発環境にはデバッガーをインストールし、Dockerコンテナ上でのリモートデバッグもセットアップします。
デバッグには、Visual Studio CodeMicrosoft 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のコードをコピペするだけです。

app.py
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.pypython -m app)

Dockerfile

以下を 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 basebaseというステージ名にします。ステージ名は自由につけられます。
以降のコマンドは別の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に以下をコピペして下さい。

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

今回は、ローカルポート50005678をコンテナ内の同じポート番号にマッピングしています。
ポート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のみなので、以下をコピペして下さい。

requirements.txt
Flask

今回はFlaskのバージョンを指定しておりません。必要な場合は適宜設定して下さい。 (例 Flask==1.1.2)

開発環境の起動

開発環境を実行するには、アプリケーションディレクトリ内で次のコマンドを実行するだけです。

Terminal
docker-compose up -d

develop ステージのdockerイメージがビルドされ、アプリのdockerコンテナが立ち上がります。
コンテナを停止するには次のコマンドを実行します。

Terminal
docker-compose down

docker composeをフォアグラウンドで実行したい場合は、-dフラグを省いて下さい。その際は、CTRL+Cでアプリを停止できます。

デバッグ

デバッグにはVisual Studio Codeを使用します。

PyCharmを利用したい場合、無償のCommunity Editionではリモートデバッグをサポートしていない為、Professional Editionライセンスが必要となります。

まずはアプリケーションフォルダで以下のコマンドを実行します。

Terminal
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をインストールします。
python_extension.png

リモートデバッグの設定

デバッグ設定は*.vscode/フォルダ内のlaunch.jsonに保存されます。
リモートデバッグの設定手順は
launch.json*が既にプロジェクトフォルダにあるかによって異なります。

launch.json ファイルの作成方法

launch.jsonファイルが存在しない場合は、次の手順で生成します。
app.py ファイルをVisual Studio Codeで開いて、ボトムバーにPython言語が自動設定されているか、確認します。

python_language_mode.png

左メニューのDebugをクリックし、次に、create a launch.json fileをクリックします。

debug_setup1.png launch.jsonファイルが存在しない場合

Python Extensionのインストールに成功していれば、デバッグ設定のリストからPythonを選択できるはずです。

debug_setup2.png launch.jsonファイルを作成

次に、Remote Attachを選択します。

debug_setup3.png

次に、接続するリモートデバッガーを選択します。ホスト名はlocalhost、ポートは5678番を使用します。

debug_setup4.png
debug_setup5.png

自動作成されたlaunch.jsonは下記の通りです。

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言語が自動設定されているか、確認します。

python_language_mode.png

左メニューのDebugをクリックし、表示されたセレクトボックスから下矢印をクリック**Add Configuration...**を選択し、設定を追加することも可能です。

debug_add_configuration.png launch.jsonファイルが存在する場合

Python Extensionのインストールに成功していれば、デバッグ設定のリストからPythonを選択できるはずです。

debug_add_configuration2.png launch.jsonファイルに設定追加の場合

次に、Remote Attachを選択します。

debug_setup3.png

次に、接続するリモートデバッガーを選択します。ホスト名はlocalhost、ポートは5678番を使用します。

debug_setup4.png
debug_setup5.png

launch.jsonのconfigurationsは下記の通りです。

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": "."
                }
            ]
        }

        // ...他の設定

    ]
}

ブレークポイントの設定とデバッガーのアタッチ

app.py ファイルを開き、7行目にブレークポイントを設定します。次に、左側にある緑色の再生ボタンをクリックして、デバッガーに接続します。
ブラウザで http://localhost:5000 にアクセスし、アプリを起動します。
アプリはブレークポイントで停止するはずです。
debug.png
何度でもデバッガーに接続可能です。

本番ビルド

production ステージをビルドするには、アプリケーションフォルダ内で次のコマンドを実行します。

Terminal
docker build --target production -t my_flask_app .

--target でビルドするステージを指定し、-t my_flask_appでイメージに適当なタグ名を付けます。
最後の*.*は重要で、Dockerfileの場所、つまりカレントディレクトリを指しています。

ビルドされたイメージには任意のタグ名がつけられますが、開発と本番で同じものを使用しない様に注意して下さい。

ビルド済み Images の一覧

docker images コマンドで、ビルドしたイメージをリスト表示することができます。

Terminal
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 の起動

本番イメージからコンテナを生成して実行するには以下のコマンドを使用します。

Terminal
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

次のコマンドで実行中のコンテナをリスト表示できます。

Terminal
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

コンテナの停止は以下のコマンドのみです。

Terminal
docker stop my_flask_app

コマンドをフォアグラウンドで実行したい場合は、-dフラグを省いて下さい。その際は、CTRL+Cでアプリを停止できます。

トラブルシューティング

Remote Attachについて

問題: デバッガ切断ボタンが効かない。
vscode_remote_attach.png

解決方法: vscodeの設定で、Show Sub Sessions In Tool Barが有効になっていることを確認してください。
vscode_settings.png
Githubのチケット

142
187
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
142
187