VS Codeからリモートサーバー上のJupyterLabにアタッチしてデバッグを行う手順

# 1. この投稿で説明すること
以下手順により、JupyterLabとVS Codeを使ってデバッグを行う環境を構築します。
1. リモートサーバー上でJupyterLabを起動
2. JupyterLabから、Pythonのクラスを呼び出す。
3. ローカルPC上のVSCodeから、上記のJupyterLabのnotebookへリモートアタッチをしてデバッグを行う。
4. 図示すると以下の通り。

<img width="709" alt="スクリーンショット 2020-04-08 00.09.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/94a03667-5ea9-0221-c44c-14caac2fa952.png">

以下動画を見ていただくと、具体的にどのようなことができるかのイメージを持っていただけると思います。
![Untitled.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/d36e7e3a-8711-2a71-3680-08c81d6ec5b1.gif)

# 2. Motivation
1. JupyterLabは、ちょっとしたコードを書いたり、結果をきれいにまとめて共有するには便利ですが、本格的にプログラムを作成する時には、VSCode等のエディターを使うことが多いのではないでしょうか。
2. 一方、このように開発したプログラムを使って検証を行うときには、JupyterLabから呼び出すと便利なことが多いです
3. このような使い方をする際に、JupyterLabから呼び出したプログラム上にブレークポイントを設定し、VSCodeからデバッグを行う環境を構築する方法についての説明します。

# 3. 今回構築する環境のポイント
- VS Codeでは、remote developmentという仕組みを用いることで、sshで接続することにより、リモートサーバー上のフォルダやファイルをローカルPCとほぼ同等に編集することが可能です。[詳細はこちらをご参照](https://code.visualstudio.com/docs/remote/remote-overview)。
- VS Codeでは、リモートサーバー上にptvsdというパッケージをインストールすることにより、リモートサーバー上のPythonプロセスにattachをしてデバッグを行うことが可能です。[詳細はこちらをご参照](https://code.visualstudio.com/docs/python/debugging)。
- JupyterLabでは、kernelという仕組みを用いることで、任意のプロセス（Pythonでなくても可）をインタプリタとして使用することが可能。
- 本稿では、上記を組み合わせることで、上で説明したことを実現しています。

# 4. 環境構築
## 4.1. リモートサーバーの環境構築
### 4.1.1. 前提条件
- この記事ではDockerのイメージ「tensorflow/tensorflow:1.14.0-gpu-py3-jupyter」をベースに環境構築を行いました。
- ただ、anaconda等でJupyterLabをインストール済みの場合はそのまま使うことも可能だと思います。（以下つらつら説明してますが、要は、リモートサーバー上でptvsdがインストールされ、JupyterLabのkernelとして設定されていれば動きます。）

### 4.1.2. 必要なアプリのインストール
```bash
# apt-get update
# apt-get install -y wget
# apt-get install -y language-pack-ja
# apt-get install -y ssh
# apt-get install -y git
# update-locale LANG=ja_JP.UTF-8
```
### 4.1.3. nodejsのインストール
JupyterLabのExtensionをインストールするために必要です。kernelの設定をするだけでしたらこの手順は不要ですが、JupyterLabを便利に使うために対応しておいたほうがよいでしょう。

```bash
# curl -sL https://deb.nodesource.com/setup_12.x | bash -
# apt-get install -y nodejs
```

### 4.1.4. Python & JupyterLabのインストール
- 特に、jupyterlabとptvsdが必要です。
- 今回はついでにnumpy, matplotlib, scikit_learnのインストールすることにします。
- jupyterlabは、2020年4月時点では、version2が出ていますが、まだ対応していないExtensionが多いため、今回は1.0.2をインストールすることにします。

```bash
# pip install -r requirements.txt
```
```text:requirements.txt
numpy==1.16.3
matplotlib==3.0.3
scikit_learn==0.20.3
jupyterlab==1.0.2
ptvsd==4.3.2
```

### 4.1.5. ローカルPC側のVS Codeからアクセスするためのsshの起動
VS Codeからリモートサーバーに接続してリモート開発を行うために必要です。今回は、port 19205でsshのポートを開きます。

```bash
# cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org
# sed -e s/#Port/Port/g /etc/ssh/sshd_config.org  | sed -e s/22/19205/g > /etc/ssh/sshd_config
```

次に、sshを起動しておきます。

```bash
root@25fa703a90c4:/tf# service ssh start
 * Starting OpenBSD Secure Shell server sshd                             [ OK ]
```

## 4.2. JupyterLabの設定
次に、JupyterLabの設定を行います。今回は、jupyterLabのサーバーはjupyterユーザーで起動することにします。

### 4.2.1. /usr/local/share/jupyter/labディレクトリの所有権を変更
　まず、準備として、/usr/local/share/jupyter/labディレクトリの所有権をjupyterに変更します。これを実行しないと、jupyterユーザーで起動下JupyterLab上からExtensionのインストールができない等、色々と不便です。
　もっとよい方法があると思います（例えば、これらのファイルはホームディレクトリ内に保存するようにする等）。ここでは安直に、以下で所有権を変更してしまいます。

```bash
# chown -R jupyter:jupyter /usr/local/share/jupyter/lab
```
### 4.2.2. ptvsdをJupyterLabのkernelとして設定
 まず、JupyterLabからデバッグ用のインタプリタを呼び出せるようにするため、/usr/local/share/jupyter/kernels/ptvsd/kernel.jsonというファイル名で、以下ファイルを保存します。（ここで設定したインタプリタをJupyterLabから呼び出す方法は、後ほど説明します。）

※ このディレクトリの所有者はrootになっていると思いますので、rootで保存します。

```json:kernel.json
{
 "argv": [
  "/usr/bin/python3",
  "-m",
  "ptvsd",
  "--host",
  "0.0.0.0",
  "--port",
  "19204",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Python3 Debug",
 "language": "python"
}
```
こんな感じです。

```bash
root@25fa703a90c4:/usr/local/share/jupyter/kernels# pwd
/usr/local/share/jupyter/kernels
root@25fa703a90c4:/usr/local/share/jupyter/kernels# mkdir ptvsd
root@25fa703a90c4:/usr/local/share/jupyter/kernels# cd ptvsd
root@25fa703a90c4:/usr/local/share/jupyter/kernels/ptvsd# cat > kernel.json <<EOL
{
 "argv": [
  "/usr/bin/python3",
  "-m",
  "ptvsd",
  "--host",
  "0.0.0.0",
  "--port",
  "19204",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Python3 Debug",
 "language": "python"
}
EOL
root@25fa703a90c4:/usr/local/share/jupyter/kernels/ptvsd# ls kernel.json
kernel.json
```
以上でJupyterLabの設定は完了です。

## 4.3. VS Codeの設定
次にVS Codeでremote developmentを行うための設定をします。

### 4.3.1. sshでの接続の準備
まず、もしまだでしたら、ローカルPCからリモートサーバーへsshで接続できるよう、鍵交換等の準備をしておく必要があります。詳細の手順は省略しますが、[こちら](https://qiita.com/gotohiro55/items/36a22516de2b381b3c6e)等が参考になると思います。

### 4.3.2. Remote Development Extension Packのインストール
[こちらの手順](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)に従って、Visual StudioにRemote Development extension packをインストールします。

### 4.3.3. VS Codeからリモートサーバーに接続する設定

以下手順により、VS Code1からリモートサーバーに接続してフォルダやファイルの編集ができるようになります。（２回目以降は、保存した設定を用いて接続することもできます。）

1. 画面左下のボタンをクリック

<img width="992" alt="skitch.2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/a1d171ba-1370-31c1-8ea9-a456a7ce83ee.png">
2. コマンドパレットが表示されるので、「Remote-SSH: Connect to Host」を選択

<img width="612" alt="skitch.1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/970bf366-53a6-986e-7dad-0a5b4d6d3bae.png">
3. 「Add New SSH Host...」を選択

<img width="600" alt="skitch.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/5902250c-1160-a77f-527d-3e101b302021.png">
4. コマンドラインからsshでアクセスする時のコマンドを入力。例えば、以下を入力します。便利です！

```
ssh user@host名 -p ポート名
```
<img width="613" alt="skitch.4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/19f341c5-6f49-02c5-40f9-45259cd79efd.png">
5. ssh設定の保存場所を指定（通常は個人フォルダ以下のほうを指定すると思います。）

<img width="598" alt="skitch.3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/504529ef-8aee-890e-ada5-67f0d07b32e0.png">
6.右下に表示されるダイアログで「Connect」ボタンをクリック 

<img width="450" alt="skitch.5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/e5dc2483-f6a7-1660-fbfc-905b92d078f2.png">

### 4.3.4. Python extension for Visual Studio Codeのインストール
　次に、VS Code上でPythonのデバッグ行うために、[こちら](https://marketplace.visualstudio.com/items?itemName=ms-python.python)からPython extension for Visual Studio Codeをインストールします。

　以上で、VS Codeからリモートサーバーへの接続が完了します。この手順により、リモートサーバー上のファイルやフォルダが、あたかもローカルPCにあるかのようにVS Codeで編集できるようになります。

## 5. サンプルプログラムの実行

### 5.1. サンプルプログラムの説明
 とても簡単ですが、以下サンプルプログラムを利用します。まず、リモートサーバー上で、notebookを保存するディレクトリと同じディレクトリに以下のファイルを保存します。

```python:sample.py
import numpy as np

def sample(x):
    y = np.square(x)
    return y
```

### 5.2. JupyterLabへのアクセス
次に、リモートサーバー上で、JupyterLabを起動し、ローカルPC上のブラウザからJupyterLabにアクセスします。（普通にJupyterLabにアクセスする方法と同じです。）

### 5.2.1. リモートPC上でJupyterLabを起動
```bash
jupyter lab --ip=0.0.0.0
```

### 5.2.2. JupyterLabへアクセス
 jupyterLab起動時のログに、以下かのようなメッセージを見つけることができると思いますので、ローカルPCからURLにアクセスすることで、JupyterLabを開くことができます。　＊アクセス時に、IPアドレスはリモートPCのものに変更します。

```
    To access the notebook, open this file in a browser:
        file:///home/jupyter/.local/share/jupyter/runtime/nbserver-13-open.html
    Or copy and paste one of these URLs:
        http://(da9742c1f7ef or 127.0.0.1):8888/?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
ローカルPC上のブラウザから、5.2.1JupyterLab起動時に出力されるURLにアクセスすることで、JupyterLabにアクセスできます。

### 5.3. Kernelを指定し、notebookを作成
 JupyterLabにアクセスすると、以下のように、Notebook欄に、「Python3 debug」というkernelが表示されるようになっていると思います。（これは、手順4.2.2で設定したptvsdを用いたkernelです。）
　このボタンをクリックして、新しいnotebookを作成します。
<img width="1006" alt="JupyterLab.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/357f4c6c-fff1-07f7-435f-ff1be6c2e722.png">

　画面右上が、「Python3 Debug」となっており、先ほど作成したDebug用のKernelが使われていることが分かります。

<img width="1083" alt="JupyterLab2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/353d6aad-3de4-9304-cbf4-4a022f5eda66.png">

とりあえず、以下のようなNotebookを作成し、実行してみます。

<img width="967" alt="Jupyter_and_VSCode_sample.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/3aaf93e7-7ade-623b-adb9-ac743a236bfd.png">

※Qiitaはgistは貼り付けられないみたいですが、[こちら](https://gist.github.com/mxl00474/1d30406f8a520dadda94850c0943663d)にソースがありますのでこちらもご参照ください。

### 5.4. VS Codeでのリモートデバッグの設定

### 5.4.1. リモートサーバーへ接続し、プロジェクトを開く
4.3の手順に従い、VS Codeでリモートサーバーに接続します。接続したら、サンプルプログラムを開きます。こんな感じです。

<img width="1029" alt="sample_py.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/c7556f9c-ec0b-69a7-35ef-7c31a1b0b893.png">

### 5.4.2. デバッグの設定（launch.jsonの設定）

以下手順に従ってデバッグの設定をします。

<img width="1011" alt="VSCode2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/e1cb0f96-d8d0-006c-3a39-64d2d7148c49.png">

設定内容は以下のようにします。

- port: ptvsdのkernelの設定で設定したポートを指定します。
- host: リモートサーバーのIPアドレスを指定します。
- pathMappings: localRootは、以下例のように${workspaceFolder}でよいと思います。remoteRootは、リモートサーバー上のファイルを保存したフォルダを指定します。　


※ブレークポイントの設定がうまくいかない時は、pathMappingsの設定に問題がある可能性があるので、ここをいじってみるとよいと思います。

```json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    
        {
            "name": "Python: アタッチ",
            "type": "python",
            "request": "attach",
            "port": 19204,
            "host": "192.168.2.200",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "/home/jupyter/proj/docker"
                }
            ]
        }
    ]
}
```
以上でデバッグの設定が完了です。次に実際にデバッグをやってみます。

### 5.5. JupyterLabのプロセスにアタッチしてデバッグを実行

デバッグの手順は以下の通りです。詳細は冒頭のデモの動画もご参照ください。

1. VS Code上で適宜ブレークポイントを設定した後、先ほど設定した「Python アタッチ」を実行
2. JupyterLabから、notebookを実行
3. ブレークポイントで処理が中断するので、デバッグを行う。
4. VS Code上の「デバッグコンソール」で実行したコマンドの結果は、JupyterLab上にも表示されます。特に、matplotlib等で内部変数を表示し、グラフで表示したりもできるのでとても便利です！

6. まとめ
　以上、JupyterLabから実行したPythonプロセスをVS Codeからリモートアタッチしてデバッグを行う説明でした。とても便利ですので是非お試しください。ちょっと殴り書きなので不備があるかも知れません。疑問点等ありましたらコメントいただけましたら適宜追記・修正をしたいと思います。