須通り
Sudo Masaaki official site
For the reinstatement of
population ecology.

コンテナに接続して操作するのも、サーバーにリクエストを送ってウェブアプリを実行するのも、方法論はよく似ている。

ホーム | 統計 Top | Docker おぼえがき(03)コンテナの操作方法

コンテナの起動や終了データの出し入れを覚えたところで、コンテナの操作方法にも触れておく。

目次

Docker コンテナの操作手段

とりあえず docker run 等のコマンド解説は行ったが、起動した Docker コンテナをどんな手段で操作して、自分の仕事に役立てるのかが問題である。個人的な観測範囲だと、以下の 3 パターンが多いようだ。

  1. ターミナルエミュレータ(コマンドライン)
  2. ブラウザによるノートブック操作(Jupyter)
  3. エディタ(特に Visual Studio Code)および IDE

コマンドラインからコンテナを立ち上げたわけだから、そのままコマンド操作するのが最も安全確実である。一方、実用的な機械学習コードについてはシェルだけで操作するのが難しいと思われる。データを放り込んだり学習を進めたりする際に書かねばならない、コードの行数が多いためだ。とはいえ操作だけならばスクリプトファイルを読み込めばいいが、教師画像のデータを目で確認しながら処理したり、誤判定率等の指標をプロットしたりする必要もある。グラフィカルな環境が使えないと決定的に不便である。

個人のスキル習得としては、全ての方法でコンテナが扱えることを体験をしておくのが吉だと思う。その上で、2 と 3 は好みで選べばいい。1 は基本操作なので必須であろう。

Docker をターミナルから操作する

データの構造がそこまで複雑でなければ、コマンドラインでゴリゴリ操作して機械学習するのも悪くない選択肢である。たとえばみんな大好き MNIST を keras で実行するコードが参考になる。このコードを動かすコンテナ環境としては、TensorFlow 公式で配布しているイメージのうち、GPU サポートされていて(イメージ名に -gpu という接尾辞が付く)、かつ Python3 がインストールされている(-py3)ものを使う。コンテナを起動して bash に入り、ソースを落として Python3 で実行。


## tensorflow/tensorflow:latest-gpu-py3 イメージを拾ってコンテナ起動。
docker run -it --rm --gpus all tensorflow/tensorflow:latest-gpu-py3 bash

## bash を付けて起動したコンテナ内で
cd /home
pip3 install keras
apt update
apt install wget
wget https://raw.githubusercontent.com/keras-team/keras-io/master/examples/vision/mnist_convnet.py
python3 mnist_convnet.py


注意:古い tensorflow イメージから起動したコンテナで、最新の mnist_convnet.py を拾ってきて実行すると、以下のエラーが出る。
TypeError: The added layer must be an instance of class Layer. Found: Tensor("input_1:0", shape=(?, 28, 28, 1), dtype=float32)
これは keras の仕様変更が原因。おとなしく最新版のイメージをダウンロードしなおしたほうがいい。

またスクリプトファイルの配布 URL は時々変更されるっぽいので、最新版を適宜検索して見つけること。

まあ MNIST はそれほど重いタスクではないので、CPU オンリーのコンテナでも数十秒で終わるらしい。

Docker を Notebook から操作する

一方、機械学習でよく用いられるのが Notebook 形式での解析手続きの記載である。もちろん Docker でも、コンテナ内に Jupyter がインストールされていれば使用可能である。だが Docker そのものは伝統的にはデスクトップ環境を用意せず、コンテナで稼働するアプリは GUI を持たないことが多い(X を立ち上げる方法もあるが面倒)。そこでホスト側のブラウザをクライアントとして、コンテナ内側のノートブックを開く形で操作する。要するに、Docker の中に Jupyter のサーバーを立てて、ブラウザから接続するのである。

ホストのブラウザがコンテナ内に何故アクセスできるんだ?という疑問はひとまず脇に置いて、とにかく動かしてみう。GPU サポートと Jupyter の両方が入っているコンテナとして、ここでは TensorFlow 公式が配布しているサンプルを使ってみる。イメージを落としてきて docker run するところまでは、上の解説通り。


Docker 内部で Jupyter Notebook を操作する
https://www.tensorflow.org/install/docker

## まず以下の GPU support + Jupyter 全部入りイメージをダウンロード。どちらか好きな方で。
$ docker pull tensorflow/tensorflow:latest-gpu-jupyter # 最新版
$ docker pull tensorflow/tensorflow:2.4.1-gpu-jupyter # 天風呂 2.4.1 決め打ち(2.4 GB)

## 起動。
$ docker run --gpus all --rm -it -p 8888:8888 tensorflow/tensorflow:2.4.1-gpu-jupyter

注意:どのバージョンの TensorFlow を使うかは、ホストの Nvidia driver との互換性を調べて決める必要がある。

なお上のコマンドでは、計算を試すだけ試して破棄するので --rm を付けて run している。停止(&コミット)でコンテナの状態を保存したければ、--rm を付けずに起動すること。


## 以下のような表示がコンソールに出る
[I 11:46:47.073 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_http_over_ws extension initialized. Listening on /http_over_websocket
[I 11:46:47.261 NotebookApp] Serving notebooks from local directory: /tf
[I 11:46:47.261 NotebookApp] Jupyter Notebook 6.2.0 is running at:
[I 11:46:47.261 NotebookApp] http://6f7f6f653457:8888/?token=031c8a40d28262200fd9361f6b6790d669fdfb7fd4d600eb
[I 11:46:47.261 NotebookApp]  or http://127.0.0.1:8888/?token=031c8a40d28262200fd9361f6b6790d669fdfb7fd4d600eb
[I 11:46:47.261 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 11:46:47.265 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
    Or copy and paste one of these URLs:
        http://6f7f6f653457:8888/?token=031c8a40d28262200fd9361f6b6790d669fdfb7fd4d600eb
     or http://127.0.0.1:8888/?token=031c8a40d28262200fd9361f6b6790d669fdfb7fd4d600eb

## 以下の部分がトークンで、コンテナを立ち上げるたびに変化する。
031c8a40d28262200fd9361f6b6790d669fdfb7fd4d600eb

上記の URL のどれか 1 つに、ホストのブラウザでアクセス。URL が http://localhost:8888/tree に転送され、Jupyter の画面が開くことを確認する(Fig. 1)。資料によっては 127.0.0.1 のかわりに localhost と書いてあるが、http://localhost:8888/?token=以下とーくん でも同じく接続できる。

Docker Jupyter Tree

Fig. 1 | ポート -p 8888:8888 を指定して tensorflow/tensorflow:2.4.1-gpu-jupyter を起動し、ブラウザから http://127.0.0.1:8888/ へアクセスした直後。トークンは認証されており、TensorFlow チュートリアルへのリンクが表示されている。

Docker Jupyter TensorFlow Examples

Fig. 2 | Fig. 1 の tensorflow-tutorials フォルダの中身。まだノートブックを開いていない(Running な notebook が存在しない)状態。

Jupyter が正常に表示されれば、/tensorflow-tutorials というフォルダが見えており、開くと幾つかのノートブックファイル(*.ipynb)が入っているはずだ(Fig. 2)。

適当なノートブック、たとえば classification.ipynb を開いて(たぶんブラウザの新しいタブで開くはずだ)、四角で囲まれているコードのブロック(セル、チャンクともいう。 In [ ]: となっているところ)を上から順に Run してみよう。ページ上のボタン、もしくは Shift + Enter キーの同時押しで、そのセルに書かれたコードを実行できる。実行中は表記が In [*]: に代わり、完了すると In [1]: などと数字が出るはずだ。GPU が有効化されていれば、学習そのものは十数秒で終わるだろう。

蛇足:最近の jupyter には、Fig. 1 のように http://localhost:8888/tree にアクセスした直後、http://localhost:8888/lab に書き換えてアクセスすることで、より新しい IDE 環境である JupyterLab に切り替わる機能がある。ただし、今回用いた tensorflow のコンテナでは Not found になってしまうので、使えないようだ。

コンテナ内操作で Jupyter を開く

上記の tensorflow/tensorflow:2.4.1-gpu-jupyter のイメージは、起動時に自動で Jupyter が開くように設計されている。元のコンソールにはプロンプト # が出ない。一方イメージによっては、run しただけでは bash しか開かないので、


jupyter notebook --ip=0.0.0.0 --allow-root

などとして明示的に Jupyter を開く必要がある。

その際、

  • --allow-root で Jupyter を root として起動し、また
  • ip=0.0.0.0 として接続元の ip アドレスに拘わらず、http 等の通信プロトコルを用いてブラウザから接続できるように設定

している。ホストマシンが外部ネットワークからの接続を受け入れるよう設定されている場合や、LAN に他人のパソコンがぶら下がっている状態では、ip の範囲を区切ることも検討すべきだが、当面は以下で説明するように「トークン」の保護機能で十分だろう。

nohup によるバックグラウンド起動

別の方法として、コンテナを起動してまずシェルに接続した後、手動で、かつバックグラウンドで Jupyter を立ち上げることもできる。以下のように nohup というコマンドを噛ませる。


# nohup コマンドの対象として "jupyter notebook" コマンドを評価させる。
# 最後の & までの間に存在する部分が、仮に操作端末を閉じても実行され続ける。
nohup jupyter notebook >> ~/jupyter.log 2>&1 &

# 上にあったオプション --ip=0.0.0.0 --allow-root は入っていないが、入れても良い。

続く ">> ~/jupyter.log" という部分は、jupyter が吐く何らかのエラー出力を jupyter.log という名前のログファイル(稼働中のコンテナのホームディレクトリ下に作成される)、エラーが発生するたびに逐一書き足していくという指令である。その後の "2>&1" は、前記のエラー出力(2)の出力ファイル(つまり jupyter.log のこと)に、jupyter の標準出力(1)の内容も一緒に追記するよう指示している。これらの指令により、jupyter が立ち上がり次第プロンプトが戻ってきて、jupyter とは無関係のコマンド操作をコンテナ上で行うことも可能となる。

デタッチ

コンテナ起動時に Jupyter を開くにせよ、後から開くにせよ、いったんコンソールをホストに戻したければ Ctrl-p + Ctrl-q で detach するとよい。蛇足だがデタッチして container ls すると、以下のように見える。


## ポートが解放されているコンテナは下のように見える
$ docker container ls

CONTAINER ID   IMAGE                                     COMMAND                  CREATED        STATUS        PORTS                                       NAMES
6f7f6f653457   tensorflow/tensorflow:2.4.1-gpu-jupyter   "bash -c 'source /et…"   18 hours ago   Up 18 hours   0.0.0.0:8888->8888/tcp, :::8888->8888/tcp   nostalgic_lovelace

## 再度入りたければ
$ docker attach nostalgic_lovelace 

再度 docker 環境に入りたければ attach が使える。ただし、デタッチ状態でもブラウザから Notebook を操作可能であり、再度アタッチする意味は薄いだろう。

Notebook の終了

Notebook 上で必要な仕事が全部終わったら、必要に応じて保存処理を行ったのち、Notebook の終了と Jupyter 本体のサーバー停止処理を実行する。

  • 現在のノートブックのチェックポイントを作成(上書き保存&バックアップ)したければ、Notebook のメニュー> File から "Save and Checkpoint" として Notebook に状態を保存(Fig. 3)。
  • Notebook そのものをローカル環境に「名前を付けて保存」したければ、"Download as" を使う。

しかる後、File > Close and Halt というメニュー項目で Notebook を閉じる。

Docker Jupyter Menu File

Fig. 3 | Jupyter Notebook の File メニュー。

なお http://localhost:8888/tree/tensorflow-tutorials で ipynb ファイルの一覧を見ていると、開かれていて実行中のノートブックには Running という緑色の文字が出ており、 Close and Halt するとこれが消える。

Docker Jupyter Menu File

Fig. 4 | ブラウザで最初に開いたタブ内で「Running」タブを選んだときに表示される、現在稼働中のノートブックの一覧。

最初に開いたブラウザタブの中にあるタブを "Running" に切り替えることでも、稼働中のノートブックが一覧できる(Fig. 4)。Running 中のタブをブラウザの × ボタンで消すと、いわゆる強制終了の状態になってしまうため、データが失われることがある。

ブラウザ上のログアウトと Jupyter サーバーの終了

一方ブラウザから開いた Jupyter Notebook の画面には、右上に Quit とか Logout といった画面もある(Fig. 4)。正確には、ブラウザ上で最初に開いた "http://localhost:8888/tree/なんちゃら" というタブには Quit と Logout が、次以降に開いたタブには Logout だけが存在する。

このうち、Logout は「現在のブラウザにおいて Jupyter への接続を終了する」処理である。ブラウザ上でログアウトしたただけの状態では、コンテナ内の Jupyter のサーバーはまだ動いている。再度トークン付きでアクセスすれば、ノートブックを開き直して実行可能である。

さて、Jupyter サーバーを完全に終了するには、方法が 2 つある。1 つはブラウザで、タブの右上にある Quit を押すことである。もう 1 つは、Running なノートブックが無いことを確認した後、コンソールで Ctrl + D キーなどを用いて Docker コンテナを停止する。


## ブラウザで Quit して Jupyter サーバーを停止する。
[I 06:30:32.622 NotebookApp] Shutting down on /api/shutdown request.
[I 06:30:32.624 NotebookApp] Shutting down 0 kernels
[I 06:30:32.625 NotebookApp] Shutting down 0 terminals

## で、最終的に Jupyter が閉じるのと同時にコンテナも停止する(--rm あるので削除される)。
user@machine:~$ 

コンテナ上の Notebook 操作の絡繰

以上が基本的な操作である。一応、なぜホストからコンテナ内の Notebook を操作できるのかという疑問にも触れておきたい。

ポートフォワーディング

上で docker run して Jupyter つきのコンテナを立ち上げる際に、見慣れない表記として -p オプションが登場した。これは「ポート開放」を指示する。ホストからコンテナ内の Notebook を操作する行為は、広い意味ではポートフォワーディング※を用いて、コンテナ内のサーバーに外部からアクセスする処理を指す。※ ポートフォワーディングとは、特定のポート番号宛に外部から届いたパケットを、内部の指定したポートに転送すること。上の例では docker run に -p というオプションを付け、(外部ポート番号:コンテナ側ポート番号)を指定する。

つまりコンテナ外のブラウザから、http://127.0.0.1/ というサイトへポート番号 8888 番で接続しており、ノートブックを表示せよ、最初のセルを評価せよ等のリクエストが、コンテナ内のネットワークのポート番号 8888 番へ転送される。コンテナ上では jupyter のプロセスが動いており、デフォルトで待ち受け番号 8888 を持つ(起動コマンドを jupyter notebook --port 9999 などとしてアレンジも可能である)。よって、8888 番へ送ったリクエストに基づいて Jupyter 上で Python コマンドを実行し、ポートを逆にたどって結果をブラウザに返すことが可能である。

ノートブックの保護機能

ちょっと前の資料には、Notebook にパスワードを掛けることで、他のユーザーが勝手にブラウザ経由で操作できないようにしましょう、と書かれていた。しかし Version 4.3 以降の Jupyter では、「トークン」がデフォルトで有効化されるようになったため、そちらを使えば Notebook に手動でパスワード保護する必要はなくなった。厳密に言うと tensorflow のイメージは、Jupyter の立ち上げ直後に、トークン発行を要求するコードが Dockerfile の段階で挿入されており、おかげでブラウザへコピペするだけで色々整う。よくできている。

一方、経路そのものを暗号化して傍受を防ぐ方法として、SSL の使用がある。とはいえ上の例で単純に、localhost へ繋ぐ際の http を https にしても、コンテナの設定をいい感じにいじらないと接続が確立しない。これは、Jupyter サーバー側の SSL 証明書が設定されていないためである。Jupyter Notebook Docs: Running a notebook server「Qiita:CentOS7にJupyter Notebookを導入」「AWS Deep Learning AMI 開発者ガイド:Jupyter サーバーの保護」あたりを読むと分かるが、ssl の証明書と秘密鍵をコンテナ内で生成し、ホーム以下の適当なフォルダへ配置しておく必要があるようだ(自分の環境でまだ試していない)。


https://jupyter-notebook.readthedocs.io/en/latest/public_server.html

## openssl を用いて自己署名証明書を作成する
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mykey.key -out mycert.pem

## その後、jupyter の立ち上げ時に certfile フラグを指定して、自己署名証明書と秘密鍵を読ませる
$ jupyter notebook --certfile=mycert.pem --keyfile mykey.key

ただし Chrome 系のブラウザでは自己署名証明書が「めっ」されるので、この場合は Let's Encrypt を使う。
なお、正式な作法としては ~/.jupyter 以下に jupyter_notebook_config.py という構成ファイルを置いて jupyter notebook の実行時に毎回読まれるようにし、この中に証明書等へのパスを記載しておくものらしい。

証明書作成は当然、Jupyter サーバーの立ち上げ前に実行する必要があるため、コンテナ内で Jupyter が自動的に立ち上がってしまう、現在の tensorflow 公式イメージでは扱いづらい。-jupyter 接尾辞が付いていないイメージを拾ってきて、自分で jupyter をインストールするよう Dockerfile を書くのがいい(pip install jupyterlab で入る)。

また注意点として、トークンを用いたノートブックの保護機能はあくまで、コンテナを立ち上げたユーザー 1 人が排他的にノートブックを使う場合にのみ、うまく機能する。同じトークンを使いまわした場合、LAN にぶら下がっている複数のユーザーがノートブックにアクセスできてしまうのだが、保存状態に不整合が生じてデータを破壊することになる。

Docker を Visual Studio Code から操作する

ここから、最初に述べた 3 つのコンテナ操作方法の、その 3 に移る。

ソースファイルをエディタで開いておいて、コードの全体やチャンクごとに Docker から評価できれば効率が上がる。この目的で近年広く使われるようになったエディタが Visual Studio Code であり、プラグインを用いればさらに、ノートブック形式のファイルの読み込み・実行・データのグラフィカルな表示まで可能になる。

ただし VS Code 側の拡張機能が複数あって関連が分かりにくいのと、多少 Dockerfile の知識が必要なこともあって、やや取っつき難い印象はある。ぱっと見だけでも Docker(ms-azuretools.vscode-docker)と Remote - Containers(ms-vscode-remote.remote-containers)という 2 つの拡張機能が存在する。

このうち Remote - Containers 拡張機能は、開発プロジェクトにおける検証環境を、Docker 等のコンテナ内に持たせる枠組みを構築する。正確にはホストマシンのエディタ(VS Code)から、まずコンテナを起動し、コンテナ内に VS Code Server を立て、このサーバーにエディタが接続することで、コンテナ内のファイルシステムを VS Code から操作する。これによりコンテナ内の環境で、アプリケーションの実行やデバッグを可能にする。

Remote - Containers 拡張機能を用いた VS Code からのコンテナ操作

VS Code 上での拡張機能タブで Remote - Containers (ms-vscode-remote.remote-containers) を探して「インストール」をクリックする。あるいは Remote Development (ms-vscode-remote.vscode-remote-extensionpack) をインストールしてもいい。こちらは Remote - Containers の他にも Remote - SSH および Remote - WSL という拡張機能が含まれる欲張りセットで、それぞれ SSH 接続した遠隔マシン、WSL 内の Linux サブシステムに接続して、内部のソースファイルをホストのエディタから編集・実行できる枠組みを提供する。まあ母艦が Linux の人は WSL は必要ないと思うので、個別に入れよう。

VS Code Remote Development を読む限り、基本的な使い方としては、まず開発プロジェクトのフォルダをローカルの適当な場所に作る(仮に ~/hoge/project とする)。実際のソースコードは、この package フォルダ以下に配置しておき、コンテナ起動時に --mount や -v でマウントするとよい。さらに project 直下には、.devcontainer という隠しフォルダを作る。ここには Dockerfile と devcontainer.json という気味の悪い拡張子のファイルを置く。VS Code は、これら .devcontainer 以下の設定ファイルに従って(必要なイメージを引っ張り)コンテナを起動する。

なおここで使う Dockerfile は自分で一から書き下すことも可能だが、Microsoft が用意している雛型が、VS Code 向けにユーザー権限を設定してくれているので、改造して使うのが便利。そもそも Dockerfile とは何ぞやについては、いずれ項を改めて説明する。一般的には、既に Docker Hub で配布されているイメージ(ベースイメージ:白紙からの作成も可能だが面倒)に、自分で追加のアプリケーションやライブラリやデータセットをレイヤーとして付加し、オリジナルのイメージをビルドするための、コマンドを列挙した設計書を指す。もちろんコンテナを起動してから、内部的に apt や pip で入れてもいい(本ページ内の keras の例がそうなっている)が、環境の再現性と通信量抑制の両面から、Dockerfile で環境を固めておくのが作法上よい。

ホスト上の既存フォルダを作業ディレクトリとして既存のコンテナを開く

まあ最初からゴリゴリ Dockerfile を書くのは骨なので、試しに適当なコンテナを起動してみよう。ここからしばらく、VS Code 公式ヘルプ Developing inside a Container の Quick start: Try a development container にある方法に従う。

最初にホストマシン上の、どのローカルフォルダを、プロジェクトフォルダとしてコンテナにマウントするかを決める。ベストプラクティスは、まず VS Code で「フォルダーを開く...」メニューから、対象フォルダを開く。次にコマンドパレットを開いて、"Add Development Container Configuration Files" というコマンドを実行する。起動したいコンテナの種類(実行したい言語)に応じ、 .devcontainer フォルダおよび中身(Dockerfile と devcontainer.json)を生成するための、雛型をここで選択できる(Fig. 5)。

Docker Jupyter Menu File

Fig. 5 | "Add Development Container Configuration Files" コマンドパレットから、起動したいコンテナの種類に応じた、設定ファイルの雛型を選ぶ。

プリセットから起動したいコンテナの種類を選ぶと、.devcontainer フォルダとその中身がプロジェクトフォルダ直下に作成される。なお、生成場所は「現在 VS Code で選ばれている作業ディレクトリ下の .devcontainer フォルダ」に決め打ちされるので注意。誤って別のフォルダに作成してしまった場合は、(下で説明するイメージのビルドが開始していなければ)devcontainer.json を閉じてから .devcontainer フォルダごと、しれっとプロジェクトフォルダへ移設してやればいい。

さらに、追加のアプリケーションを動かすために Dockerfile を加筆したり、devcontainer.json を編集してコンテナの環境設定を追加することも可能である。本ページは初学者向けなので詳しくは触れないが、Qiita「VS Code で Docker 開発コンテナを便利に使おう」あたりを読めば、各設定ファイルの仕事内容が掴める。

逆に、まるっきり自作の devcontainer.json や Dockerfile を使いたい場合や、過去のプロジェクトから使いまわしたい場合は、予め .devcontainer フォルダごと、プロジェクトフォルダへ配置しておこう。この場合は "Add Development Container Configuration Files" をスキップして、中に入っている devcontainer.json が使われるようになる。

イメージのビルドとコンテナ起動

さて .devcontainers 以下のゴニョゴニョが準備できたら、再びコマンドパレットを開き Remote-Containers: Open Folder in Container ... というコマンドを実行。コンテナにマウントしたいフォルダを選択するダイアログが出るので、そのまま現在のプロジェクトフォルダを選択する(Fig. 6)。直ちに、VS Code の画面右下に「(Re)open in Container(選択したフォルダをコンテナ内で開くかえ?)」という警告が出てくる。要するにコンテナ環境に入るぞという意味であり、こいつを承認すると、イメージのビルド&コンテナの起動が開始する。

Docker Jupyter Menu File

Fig. 6 | Visual Studio Code のコマンドパレットで Remote-Containers: Open Folder in Container ... を開き、コンテナにマウントしたいホスト側のプロジェクトフォルダを選択する。

Starting Dev Container (show log): Building image というステータスに変化し、左側の「エクスプローラー」タブは「リモートを開いています」という、タスク進行中の状態が表示される(Fig. 7)。コンテナの初回起動時に、イメージを外部リポジトリから拾ってきてビルドする作業であるため、初回だけはそこそこ(数十分)時間が掛かる。

Docker VSCode Not connected Docker VSCode building Docker VSCode connected

Fig. 7 | VS Code でサーバーに接続していない通常の状態(上)、"Starting Dev Container (show log): Building image" として Docker image をビルドしている状態(中)、VS Code Server 上のコンテナに接続した状態(下)のステータスバー。

コンテナがめでたく立ち上がると、Dev Container の中で当該のプロジェクトフォルダが開かれる。左側のエクスプローラーから、適当なソースファイルを選ぶと、通常の VS Code の機能と同じように編集したり Run したりすることが可能になる(Fig. 8)。なお、この時の動作環境は左下のステータスバーに「>< Dev Container: xxxx」(xxxx は選択したイメージ)という文言が、青地のメッセージとして表示されていることから、ホストではなくコンテナに接続しての動作であることが分かる。

Docker VSCode remote container

Fig. 8 | VS Code が Docker のコンテナに接続され、マウントしたフォルダ内のノートブックをコンテナ内で評価しているところ。ここでは R 用の rocker/r-ver:latest イメージから起動したため、TensorFlow がないぞと叱られている。

なお、コンテナ上の VS Code Server はホストの VS Code の設定を完コピしているわけではないため、「コードの実行を司る拡張機能」、たとえば "Docker" や "Code Runner" や "Jupyter" 等は、コンテナ内で追加インストールする必要がある。実際には必要になった段階で「ポテトもお付けしますね?」と訊ねてくるので、ユーザーはダイアログ上の Install ボタンを押すだけでよい。一方「VS Code の UI や入力編集機能を整える拡張機能」は、インターフェースをホストに丸投げしているので不要。

VS Code の拡張機能で用意されている "Jupyter" は、コンテナ内の VS Code Server にもインストールできる。そのため本拡張機能を(Python が使えるコンテナ上で)入れてから、マウントしたプロジェクトフォルダ内の ipynb ファイルを開けば、VS Code でも Jupyter Notebook を対話的に使うことが可能である(Fig. 8)。ただし筆者の環境では、チャンクを評価しようとすると Python 3.8.5 64-bit requires ipykernel to be installed. と初回で叱られた。まあコンテナに pip が入っていれば一発なので、おとなしく入れてやるべし。

ちなみに、VS Code からコンテナを起動している間、ホストシステム上の適当なシェルで docker container ls と打ち込めば、動いているコンテナの情報が見える。VS Code はシステムにインストールされた Docker に、Dockerfile を送って「これでイメージをビルドして、コンテナを立ち上げてくだせえ」と懇願しているわけだから、当たり前ではある。

(もっとも、VS Code から Jupyter Notebook を使う方法はシェル+ブラウザで管理する方法と比べて、少しブラックボックスに見えるきらいがあるため、個人的にはあまり使っていない。)

VS Code から立ち上げたコンテナの終了方法

ホスト側のフォルダをマウントして使っているので、とりあえず VS Code 上での編集内容は Ctrl + S 等でローカルに保存される。それはいいとして、計算終了後にコンテナを停止するもっとも簡単な方法は、VS Code そのものを終了すること(正確に言うと devcontainer.json の "shutdownAction" で、コンテナが停止しない挙動にも変更できる)。VS Code は閉じたくないがコンテナを停止したい場合は、左側の「リモートエクスプローラー」から "Dev Containers" の下の当該コンテナ(ディスプレイのアイコンになっていると思う)を右クリックして、Stop Container を選ぶ(Fig. 9)。

なお、devcontainer.json の設定次第では停止時に自動で削除されるコンテナも作れるらしいが、基本的には docker container ls -a で、停止したコンテナの存在を確認できる。もちろん VS Code から再度接続することもできるし、コマンドラインから起動することもできる。

Docker VSCode stop remote container

Fig. 9 | VS Code から接続している Docker のコンテナを停止する。

その他、コンテナ内でできるけど説明を省く

  • git を使う。予めプロジェクトフォルダに .git も .gitignore もある場合は、そいつを使いまわす。git サーバーへの認証情報もホストからインポートできる。
  • .devcontainer.json の extensions に、予めコンテナでインストールすべき拡張機能を指定しておく。

総評

Notebook ベースでの開発案件は、ブラウザから Jupyter を操作する方が、今のところ手順数が少なくて済む気がする。タイポグラフィやシンタックスハイライトといったソース編集時の便利機能は、VS Code に軍配が上がる。なので個人的に定着した開発フローとしては、

  1. Python 実行環境はコンテナに持たせ、
  2. プロジェクトフォルダはホスト上に作ったものをコンテナにバインドマウントする。
  3. 処理内容を記述したノートブックファイルはブラウザ経由で Jupyter Notebook を用いて編集&実行し、
  4. クラスや関数を定義した .py 形式のソースは、ローカルファイルとしてホストの VS Code で編集

という流れになった。多分これが一番早いと思います。