【VSCode】Docker上で動くPythonに接続してリモートデバッグする方法

VSCodeでDocker上のAlpineOSのバージョンを出力

Dockerコンテナ上で実行されているpythonプログラムでもステップ実行(リモートデバッグ)できれば開発効率が向上します。

Visual Studio Code(VSCode)と「ptvsd」というMicrosoftが開発するリモートデバッグ用ライブラリを組み合わせるとそれが可能になるので、その環境を構築してみました。

私の環境は以下の通りです。

構成 環境 バージョン
ローカル側
(DockerホストOS)
MacOS 10.13.2
Docker for Mac 17.12.0-ce-mac49
VSCode 1.20.1
VSCodeの拡張機能「Python」 2018.1.0
リモート側
(DockerゲストOS)
AlpineOS 3.7.0
Python 3.6.4
ptvsd 3.0.0

Docker for Macなどで、Dockerコンテナをローカルマシンで起動できることを前提として話を進めます。

DockerのホストOSがWindowsでも、同様の流れでリモートデバッグが可能です。

「VSCode」と「拡張機能 Python」をインストール

前回の記事を参考に、以下をホストOSにインストールします。

  • VSCode
  • VSCodeの拡張機能「Python
VSCodeでPythonプログラムをステップ実行するための環境構築の手順をまとめました。ステップ実行ができると、プログラムの動きを把握しやすくなります。ブレークポイントやショートカットを駆使できるとより効率的にデバッグできます。

サンプルプログラムを作成する

概要(ディレクトリ構成)

ローカル側に以下のディレクトリ構成でファイルを作成し、VSCodeでこのディレクトリを開きます。

/private/tmp/app/
├── Dockerfile
└── sample.py

後述するコンテナ起動時に、この構成をリモート側にそのままマウントします。

/opt/app/
├── Dockerfile   ## リモート側では使わない
└── sample.py

リモートデバッグにおけるローカル側とリモート側のpyファイルの役割は、以下の通りです。

ファイル 役割
ローカル側のpyファイル リモート側で実行されるプログラムをデバッグするための情報(何行目でどの変数を使って何をしているか等)
リモート側のpyファイル 書かれたプログラムを実行する

今回はマウントする方針ですが、sample.pyをDockerfileのCOPYコマンドなどでコピーしてもリモートデバッグは可能です。

同一のディレクトリ構成で、同一のプログラムファイルがローカル側とリモート側の双方に存在することが重要です。

Dockerfile

以下の環境のコンテナを起動するDockerfileを作成します。

  • ゲストOSは軽量な「Alpine Linux」
  • プログラムを実行する「python」
  • リモートデバッグを可能にするライブラリ「ptvsd」
  • コンテナ起動時に後述するsample.pyをpythonで実行
FROM python:3.6.4-alpine3.7
RUN pip install ptvsd==3.0.0
RUN mkdir -p /opt/app/
CMD ["python", "/opt/app/sample.py"]

ptvsdのバージョンは3.0.0である必要があります。

これ以降のバージョンはまだサポートされておらず、正常に動きません。詳細はこちら

ptvsdは3.0.0をインストールする必要がある

sample.py

例として、以下をサンプルプログラムとして使用します。

import ptvsd
ptvsd.enable_attach("my_secret", address = ('0.0.0.0', 3000))
ptvsd.wait_for_attach()
ptvsd.break_into_debugger()

file = open('/etc/os-release')
content = file.read()
print(content)
file.close()

これがやっていることをプログラム中のコメントに書こうとしましたが、長くなるので以下に記します。

1〜4行目

ライブラリ「ptvsd」を使用して、リモートデバッグの準備をしています。

2行目で、pythonプログラムが外部(ポート番号3000で任意のIPアドレス)からアタッチ(リモートデバッグの開始)されるのを許可しています。

3〜4行目で、アタッチされるのを待機して、アタッチされたら次の行(6行目)で強制的にブレーク(処理を停止)させています。

wait_for_attach()break_into_debugger() について

アタッチの待機が不要の場合、両方とも削除可能です。今回はリモートデバッグでブレークさせる説明のため採用しています。

wait_for_attach()のみだと、その直後の行にブレークポイントを設置してもブレークしません(ブレークできるようになるまで少し時間がかかる)。この2つのメソッドが必要な理由は、こちらのissueを参照ください。

6行目以降

ここから実際に動かしたいプログラムを書きます。やっていることは、ゲストOSの/etc/os-releaseの中身(LinuxOSのバージョン情報)を表示しているだけです。

以下のコマンド(AlpineOS内でcat /etc/os-release)と同一の内容を表示します。

$ docker run --rm python:3.6.4-alpine3.7 cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.7.0
PRETTY_NAME="Alpine Linux v3.7"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"
$

構成(launch.json)を修正する

VSCodeでローカル側とリモート側にあるpyファイルのディレクトリ位置をそれぞれ設定します。

どのファイルとどのファイルが同一のものであるか、リモートデバッグするために必要な情報です。

launch.jsonを表示する方法

VSCodeでsample.pyを表示している状態にして、メニューバーから[デバッグ] > [構成を開く] > [Python]を選択します。

開いているディレクトリ配下に.vscode/launch.jsonが自動生成され、VSCodeに表示されます。

VSCodeでlaunch.jsonを開く

launch.jsonの修正箇所

以下のように赤文字の部分を修正すればOKです。(上記画像の下部に${workspaceFolder}とデフォルトで書かれている2箇所)

“name”: “Python: Attach”がある構成内の

  • “localRoot”: “ローカル側のpyファイルがあるディレクトリ”,
  • “remoteRoot”: “リモート側のpyファイルがあるディレクトリ”,
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python",
...省略...
        },
        {
            "name": "Python: Attach",
            "type": "python",
            "request": "attach",
            "localRoot": "/private/tmp/app/",
            "remoteRoot": "/opt/app/",
            "port": 3000,
            "secret": "my_secret",
            "host": "localhost"
        },
        {
...省略...

コンテナにlocalhost以外でアクセスする場合は、"host"にコンテナのIPアドレス(192.168.x.xなど)やドメイン(xxx.localなど)を設定します。

Dockerコンテナを起動する

Docker上でpythonプログラムを実行させるために、コンテナを起動します。

Dockerfileからイメージをビルド

## 「sample_image」という名前でイメージをビルド
$ docker build /private/tmp/app/ --tag sample_image
## ビルドしたイメージを確認
$ docker images
REPOSITORY    TAG               IMAGE ID       CREATED          SIZE
sample_image  latest            0a523d69746b   23 seconds ago   89.2MB
python        3.6.4-alpine3.7   4b00a94b6f26   6 weeks ago      83.4MB
$

ビルドしたイメージからコンテナを起動

オプションを使って、以下の設定でコンテナを起動する。

  • ホストOSの/private/tmp/app/を、ゲストOSの/opt/app/にマウント
  • ポート番号3000を公開
## 「sample_container」という名前でコンテナを起動
$ docker run -d -v /private/tmp/app/:/opt/app/ -p 3000:3000 --name sample_container sample_image
da5b798c88be252888b73b1f9d8dd2568b3b69c66989cc8ead8ab4ea02edd590
$
## コンテナの起動確認
$ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED        STATUS        PORTS                    NAMES
da5b798c88be   sample_image   "python /opt/app/sam…"   1 second ago   Up 1 second   0.0.0.0:3000->3000/tcp   sample_container
$

これで前述したsample.pyがコンテナ内で実行され、ptvsd.wait_for_attach()の行でアタッチ待機の状態になりました。

Dockerコンテナ上のプログラムにリモートデバッグする

初回のリモートデバッグ

VSCodeで以下の操作をします。

  1. F1を押してコマンドパレットを表示
  2. 初期値で入力されている「>」を削除
  3. 「debug python」と入力
  4. 表示される「Python: Attach」を選択

VSCodeのコマンドパレットで「debug python」と入力

ひとつ上の「Python」を選択すると、ローカルデバッグ(ホストOS上でpythonを実行)になってしまうので注意。

ここまでの設定がうまく出来ているとリモート側でアタッチ待機しているプログラムに接続され、リモートプログラムの動作をVSCodeで操作可能になります。

そして、以下のようにptvsd.break_into_debugger()の次の行でブレークされます。

break_into_debugger()の次の行でブレーク

9行目まで処理を進めると、変数contentにマウスカーソルを重ねるとその中身が見れたり、print文でAlpineOSのバージョン情報がデバッグコンソールに出力されます。(下記画像は冒頭のものと同一です。)

VSCodeでDocker上のAlpineOSのバージョンを出力

F5を押してsample.pyの実行を完了させると、コンテナのSTATUSがExitedになって終了します。

$ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS                     PORTS   NAMES
da5b798c88be   sample_image   "python /opt/app/sam…"   11 minutes ago   Exited (0) 3 minutes ago           sample_container
$

2回目以降のリモートデバッグ

以下の操作を繰り返してプログラムを開発していくことができます。

  • プログラム修正
  • コンテナ起動(=プログラム実行)
  • リモートデバッグ

プログラムを修正する

今度はファイルの中身の1行目だけを読み出すように、sample.pyの7行目をfile.read()からfile.readline()に修正してみました。

再度、コンテナを起動する

プログラムを修正後、以下のコマンドでコンテナを再度起動して、リモートプログラムを実行させます。

## コンテナは既に作成されているので、もう一度起動
$ docker start sample_container
sample_container
$

## 前述した"docker run"した直後と同じ状態になる
$ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS        PORTS                    NAMES
da5b798c88be   sample_image   "python /opt/app/sam…"   13 minutes ago   Up 1 second   0.0.0.0:3000->3000/tcp   sample_container
$

再度、リモートデバッグする

今度はF5を押して、リモートプログラムにアタッチします。

以下のようにVSCodeの左下が、「Python: Attach(app)」と選択されている状態でF5を押すと、前述したコマンドパレットで「Python: Attach」を選択したのと同じ操作になります。

VSCodeの[選択してデバッグ構成を開始]ボタン

修正した通り、/etc/os-releaseの1行目だけが出力されるようになりました。

VSCodeでDocker上のOS名のみ出力

このような流れで、VSCodeを使ってDocker上で実行されるプログラムを修正しながら、リモートデバッグが可能です。

まとめ

ローカルマシンで動くDockerコンテナ上で実行されているpythonプログラムを、VSCodeでリモートデバッグする手順をまとめました。

今まではリモートで動くpythonプログラムの変数の中身を確認する方法はログ出力くらいしか無いと思っていましたが、これで開発がかなり効率化できます。

VSCode、なかなか使えそうです。