Windows上のWSLでコンテナによる開発環境を作ってみる（Python環境編）

# 前回までのあらすじ
　前回記事はこちらになります。
　＞  [Windows上のWSLでコンテナによる開発環境を作ってみる（前準備編）](https://qiita.com/smats-rd/private/a28404d6443aad8c5278)

* Pythonの開発環境をコンテナ上に作成してみたくなった
* そのためにWindows上にDockerを入れたい
* そのためにはWSL2のセットアップが必要だったのでそこからやってみた

ということで前回はWSL2のセットアップとDocker for Desktopが動くところまで行きました。これによりWindows上にPythonを導入する際のインストール形態として、仮想環境という観点で３つの方針がありうることになります。

## WindowsへのPythonインストール形態の３パターン

1. Windows上にWindows用のPythonをインストールする。実環境へのインストール。
1. Windows上のWSL上にLinux用のPythonをインストールする。WSL環境へのインストール。
1. Windows上のWSL上で動いているDockerコンテナ内にLinux用のPythonをインストールする。コンテナ環境へのインストール

それぞれ長所短所があると思います。特にGPUなど使って機械学習をゴリゴリ回したい場合は1.になるでしょう。[^1] 
Linux環境の方が好きだけどコンテナ運用までは考えてないなら2.になるでしょう。前回説明したファイルの相互参照も手軽に使えます。また、このパターンでも VSCode を WSL に接続して使うことが可能です。
今回やろうとしているのは3.のコンテナ活用パターンです。コンテナを使うメリットについては前回書きましたので割愛します。


# Windows上でのPython開発環境にはどんなものがあるか
　Pythonを始めようとすると世の中に様々な開発環境があり入り乱れているので何を使ったら良いのかわからなくなります。実はWSLにUbuntuディストリビューションを入れた場合その時点ですでに Python 3.8.10 が入っているようです。

```:Ubuntu
$ python3 -VV
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
[GCC 9.3.0]
```

簡単な開発ならこれでも十分かもしれませんが実際の開発業務でコマンドラインとエディタだけで開発するというようなことはあまりないと思われ、何らかの開発ツールを導入することになると思います。Pythonの開発ツールにはどんな選択肢があるのか、ざっと調べてみました。

|名称|説明|URL|
|---|---|---|
|Anaconda|科学計算向きのパッケージとして配付されているPython用開発ディストリビューション。機械学習やデータ分析でよく使うライブラリがまとめて入っている。|https://www.anaconda.com/products/individual|
|Spyder|Anaconda をインストールすると一緒に入ってくるIDE。|https://docs.spyder-ide.org/current/index.html|
|PyCharm|JetBrain社が提供するIDE。Spyderより使いやすいという評判あり。機能制限された無料版と有料版がある。|https://www.jetbrains.com/ja-jp/pycharm/|
|Jupyter Notebook(JupyterLab) |Anacondaと同様、機械学習や統計処理に向いた開発環境。Webブラウザ上から使うインターフェイスが特徴。JupyterLab はJupyter Notebookの後継にあたるもの。|https://jupyter.org/, https://github.com/jupyterlab/jupyterlab|
|Google Colab|環境構築が不要で、Googleアカウントでサインインするとブラウザ上から使えるWebサービスの一種。機械学習用にGPUも用意されている。無料版と有料版がある。違いは使えるリソースの量や性能だが、無料版でも十分使える。|http://colab.research.google.com/|
|Visual Studio Code (VSCode)|Microsoftが無料で提供しているエディタであり拡張機能により軽量型の開発環境にもなる。JavaScript の開発でよく名前を見かけるものでかなり人気がある。|https://azure.microsoft.com/ja-jp/products/visual-studio-code/|
|venv|これは統合開発環境というようなものではなく、Python 内部の機能として仮想環境を作れる機能。コンテナとは若干趣を異にするが目的は似ている。|https://docs.python.org/ja/3/library/venv.html|
|pyenv|開発環境ではなく一つのマシンに複数のバージョンのpythonをインストールしてそれを切り替えて使えるようにするツール。 |https://github.com/pyenv/pyenv|
|Eclipse|昔からあるIDE。もともと重くて使いづらい上にコンテナ環境での使い方についてあまり情報がなく（Linux版なら可能らしい）、現状のままなら今後主流になっていくであろうコンテナを利用した開発に向かないため、Java開発環境としてもレガシーとなっていく可能性がある。|https://mergedoc.osdn.jp/|

　Anacondaは開発用途が科学計算やデータサイエンス分野である場合に向いているそうです。一方Web、デスクトップ、組み込み、ゲーム、モバイル等のアプリ開発をする場合は組み込まれているモジュールなどの関係で普通のPythonを使う方が使いやすいとのこと。WSL上のDockerコンテナで Spyderを使うにはX11かWSLgなどを使わないといけないようです。
　Jupyter Notebook はその仕組み上コンテナ環境でも使いやすそうなので何らかの理由で VSCode を採用しない場合の有力な選択肢となると思いますが、ブラウザ上で動くという制約上どうしてもVSCodeよりも自由度が落ち軽快さで劣ると思われます。
　Google Colab は性能も悪くないようですが、Webサービスなので常時接続環境があることが前提となります。
　PyCharm は利用者の評判もよく Dockerを使った開発にも対応しているようです。普及率がそれほどでもないようなので今回はこれ以上調べませんが、時間があれば調べてみたいところです。
　WSL + Docker + Python という組み合わせで検索すると出てくる情報としては VSCodeを使うものが圧倒的に多く、WSL + Docker という環境ではこれが今の所主流の開発ツールということになりそうです[^7]。今回は VSCode を使った開発について調べてみます。

# Python開発環境のコンテナを用意するための前提知識
* 今回はDockerに関する用語や概念については最低限の説明にとどめます。入門サイトはたくさんあるので読んでいただければと思います。[^6]
* DockerコンテナはDockerイメージから作成します。DockerイメージとはDockerコンテナを作るための静的なイメージで、環境のスナップショットでありテンプレートのようなものです。内部的な構造としてレイヤーを持っているらしいですが、とりあえず最初はそこはあまり気にしなくてもよさそうです。
* 最近はPythonに限らず、Docker Hub[^2]上に開発環境のDockerイメージが開発元から公式に提供されていることが多いようです。Pythonの公式なDockerイメージも開発元から提供されています。
    * https://hub.docker.com/_/python
* docker pull コマンドにより、docker hub上のイメージをダウンロード可能です。

```:Ubuntuなどのコマンドライン
$ docker pull <名前空間>/<イメージ名>:<タグ名>
```

* イメージには名前空間を付けることが可能です。指定しない場合はDocker公式イメージ用の名前空間であるlibraryが検索されます。
* タグというのは取得したいイメージのバージョンを表すのに使う検索キーのようなものです。省略すると latest (最新断面）になります。
* 実際に docker pull で pythonのバージョン3用イメージの最新版を取得してみます。

```:Ubuntu
$ docker pull python:3
3: Pulling from library/python
0c6b8ff8c37e: Pull complete
412caad352a3: Pull complete
e6d3e61f7a50: Pull complete
461bb1d8c517: Pull complete
808edda3c2e8: Pull complete
724cfd2dc19b: Pull complete
8bd4965a24ab: Pull complete
fccd5fa208a8: Pull complete
c7160e53cd12: Pull complete
Digest: sha256:d4641de8caa679bdb32ff6f50e664d36959a67d0b494b355f6f174bccdb5f639
Status: Downloaded newer image for python:3
docker.io/library/python:3
```
 Digest というのがこのイメージを特定するためのハッシュ値です。

## Dockerイメージの確認
* ダウンロードしたイメージの確認は docker images コマンドでできます。

```
$ docker images 
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
python       3         de529ffbdb66   18 hours ago   886MB
```

* さらに詳しくイメージの情報を出したければ docker inspect コマンドを使います。

```json:
$ docker inspect python:3
[
    {
        "Id": "sha256:de529ffbdb6623fd5d26e3fde1ac721153657dbe9966fd48075163bfeebb37d2",
        "RepoTags": [
            "python:3"
        ],
        "RepoDigests": [
            "python@sha256:d4641de8caa679bdb32ff6f50e664d36959a67d0b494b355f6f174bccdb5f639"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-02-04T23:20:30.292106032Z",
        "Container": "94b7c5b886bb3171e2b06fe7edb5eff41d6fe3c66bdfdb3738f850f42c2074c2",
        "ContainerConfig": {
            "Hostname": "94b7c5b886bb",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=C.UTF-8",
                "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
                "PYTHON_VERSION=3.10.2",
                "PYTHON_PIP_VERSION=21.2.4",
                "PYTHON_SETUPTOOLS_VERSION=58.1.0",
                "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/2caf84b14febcda8077e59e9b8a6ef9a680aa392/public/get-pip.py",
                "PYTHON_GET_PIP_SHA256=7c5239cea323cadae36083079a5ee6b2b3d56f25762a0c060d2867b89e5e06c5"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"python3\"]"
            ],
            "Image": "sha256:a359f492eb7a7dba11cbce1b5e81f77ff95a5768822bd9949bef4a16c9f03e7f",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=C.UTF-8",
                "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
                "PYTHON_VERSION=3.10.2",
                "PYTHON_PIP_VERSION=21.2.4",
                "PYTHON_SETUPTOOLS_VERSION=58.1.0",
                "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/2caf84b14febcda8077e59e9b8a6ef9a680aa392/public/get-pip.py",
                "PYTHON_GET_PIP_SHA256=7c5239cea323cadae36083079a5ee6b2b3d56f25762a0c060d2867b89e5e06c5"
            ],
            "Cmd": [
                "python3"
            ],
            "Image": "sha256:a359f492eb7a7dba11cbce1b5e81f77ff95a5768822bd9949bef4a16c9f03e7f",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 885523149,
        "VirtualSize": 885523149,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/888b1d71de32b660aa3c7a9360038f9511d4ee1418e5f7e2622015ed316fa372/diff:/var/lib/docker/overlay2/44262362c23ee27612e94090b94bdf2e4feabce7aacb21c1d93ccb22d8b300b0/diff:/var/lib/docker/overlay2/b0df7b2fbab239b50bb0d331c5531337fff1739c7398b2434d4ced8341ddf896/diff:/var/lib/docker/overlay2/3bd03441d03de94a52b9b3538c1af58faf8b64d3ca340d9a67e0358da62c2366/diff:/var/lib/docker/overlay2/9f8d36b5e796f499f3813e24aa7aa492d7b18a8460b7180701ba0b55ba14d8e0/diff:/var/lib/docker/overlay2/35b949d69f4cdf242262616fe12dd5f6dd142fae7d4adb2146108fdbf5ce8d29/diff:/var/lib/docker/overlay2/033890bb3efa672ffeca3a313c21a0f4f1f0dc8b3f8e05c57c2dee0365c93e28/diff:/var/lib/docker/overlay2/2c02ccb8da1c55ee4e4f1fb67fa71b9eaf07930a7f33d93088cfb1fdd71ce0e5/diff",
                "MergedDir": "/var/lib/docker/overlay2/32117ba550b5cbd8561c8e8202b257c8af631049c2f6a2bc092f25de71445554/merged",
                "UpperDir": "/var/lib/docker/overlay2/32117ba550b5cbd8561c8e8202b257c8af631049c2f6a2bc092f25de71445554/diff",
                "WorkDir": "/var/lib/docker/overlay2/32117ba550b5cbd8561c8e8202b257c8af631049c2f6a2bc092f25de71445554/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:0b0f2f2f52795d31dd1107e7ac81adca6aa51769f6ec4e23f745e373ae588995",
                "sha256:6398d5cccd2cc2c5989fc2021b9519eb5270bc8e09148b83818d0c1e10c69b98",
                "sha256:bed676ceab7ad31ef6668df4b1d025e32744ca8ea6b7618d25cf318863f21fe8",
                "sha256:613ab28cf833881e95b4ee02829114c33e9cd98ae2f10a8e46a48e6d88d410e2",
                "sha256:204e42b3d47b54f6ee2af1083c0234402afe87eee894949bbb98cf0bb35c6ef7",
                "sha256:a3232401de62e32369f9185f10e47f65bbd7ba3ffb27347139e7aae90a3c4d72",
                "sha256:2a671b56bca21694aa3ec32de523236c67f436676a2bf8a3e62ead950f655af6",
                "sha256:3018490964f804579010465966407950d4c111de257234af9f61aa337c493845",
                "sha256:718062cc061ccd766d8a7f87b779e30b06d3f6772486f3a10e6a98278360c104"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]
```
 残念ながら今は詳細までは調べません。とりあえずこんなことができるということだけ押さえておきます。

* この時点でDocker for Desktop の画面はこうなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/06c97960-d50e-804d-8869-059692f02539.png)
　　↑ 先ほどpullしたpython:3 のイメージがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/9b57d66c-fea1-582f-46e3-8057d3c7072c.png)
　　↑　コンテナはまだありません。

## Docker コンテナを起動してみる
* docker run コマンドでコンテナを起動してみます。オプションの -it は対話モードでの動作を指定、--rm はコマンドが終わったらコンテナを自動的に削除するという意味です。

```:Ubuntu
$ docker run -it --rm python:3
Python 3.10.2 (main, Jan 29 2022, 02:41:17) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello")
Hello
>>> import sys
>>> print(sys.version)
3.10.2 (main, Jan 29 2022, 02:41:17) [GCC 10.2.1 20210110]
```
 ↑　Pythonが対話モードで起動しているので "Hello" と出力してみた後、バージョンを表示させてみました。コンテナのホストであるUbuntu上のPython のバージョンは3.8.10でしたが、コンテナ内のPythonは3.10.2で、こちらの方が少し新しいようです。
　なぜコンテナ起動と同時に対話モードでPythonが起動するかというと、さきほどの inspect の出力で CMD セクションに下記のような記述があったためであるようです。

```json:
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"python3\"]"
            ],
```
 コンテナを特に指定なしで起動した場合に、このコマンドが実行されるようになっているようです。このとき、Docker for Desktop の画面を見るとコンテナが起動しているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/4112ffb7-01c9-f588-0d49-17082c95dfeb.png)

* Pythonを終了させると、rmオプションを指定していたのでコンテナも自動削除されます。

```:Ubuntu
$ docker run -it --rm python:3
Python 3.10.2 (main, Jan 29 2022, 02:41:17) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello")
Hello
>>> import sys
>>> print(sys.version)
3.10.2 (main, Jan 29 2022, 02:41:17) [GCC 10.2.1 20210110]
>>> quit()
$
```
* Docker for Desktop の画面からもコンテナが消えました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/60f0c7d7-4102-1dc1-9caf-1a693c029481.png)

## コンテナ起動時にbashを起動してみたら？

* ではコンテナ起動時にいきなりpythonを起動するのではなく、bash を起動させればプロンプトが出るのか？という疑問がわくと思います。やってみると、こうなります。

```:Ubuntu
$ docker run -it --rm python:3 bash
root@333970f0f928:/# pwd
/
root@333970f0f928:/# ls -la
total 72
drwxr-xr-x   1 root root 4096 Feb  5 18:21 .
drwxr-xr-x   1 root root 4096 Feb  5 18:21 ..
-rwxr-xr-x   1 root root    0 Feb  5 18:21 .dockerenv
drwxr-xr-x   1 root root 4096 Jan 26 02:12 bin
drwxr-xr-x   2 root root 4096 Dec 11 17:25 boot
drwxr-xr-x   5 root root  360 Feb  5 18:21 dev
drwxr-xr-x   1 root root 4096 Feb  5 18:21 etc
drwxr-xr-x   2 root root 4096 Dec 11 17:25 home
drwxr-xr-x   1 root root 4096 Jan 26 02:13 lib
drwxr-xr-x   2 root root 4096 Jan 25 00:00 lib64
drwxr-xr-x   2 root root 4096 Jan 25 00:00 media
drwxr-xr-x   2 root root 4096 Jan 25 00:00 mnt
drwxr-xr-x   2 root root 4096 Jan 25 00:00 opt
dr-xr-xr-x 235 root root    0 Feb  5 18:21 proc
drwx------   1 root root 4096 Jan 29 02:37 root
drwxr-xr-x   3 root root 4096 Jan 25 00:00 run
drwxr-xr-x   1 root root 4096 Jan 26 02:12 sbin
drwxr-xr-x   2 root root 4096 Jan 25 00:00 srv
dr-xr-xr-x  11 root root    0 Feb  5 18:21 sys
drwxrwxrwt   1 root root 4096 Feb  4 23:20 tmp
drwxr-xr-x   1 root root 4096 Jan 25 00:00 usr
drwxr-xr-x   1 root root 4096 Jan 25 00:00 var
root@333970f0f928:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@333970f0f928:/#
```
予想通りコマンドプロンプトが出ました。OSの情報を確認するとDebian GNU/Linux 11 (bullseye)と出てくるのが興味深いです。WSL上ではUbuntuが動いていますが、コンテナ内ではDebianが走っているわけです。プロンプトに表示されている 333970f0f928 というのはコンテナのIDです。

```:コマンドライン
C:\Users\smatsu>docker container ls
CONTAINER ID   IMAGE      COMMAND   CREATED          STATUS          PORTS     NAMES
333970f0f928   python:3   "bash"    10 minutes ago   Up 10 minutes             boring_goldstine
```

なお、これもexitするとコンテナが自動的に消えます。
以上でなんとなくコンテナを起動して動かせたので、VSCodeの導入へ進みたいと思います。


# Visual Studio Code をDocker で使うための準備
* Docker コンテナにVSCodeをつないで、あたかもコンテナ内で動いているかのように開発が可能となります。そのためにいくつか準備が必要です。VSCodeは既にWindows上にインストールしてある前提で進めます。具体的には、以下の３つの準備をします。

1. 拡張機能 VSCode Remote Container をインストールする
2. Docker拡張機能をインストールする
3. Python開発用拡張機能をインストールする

## 拡張機能 VSCode Remote Container をインストールする
* VSCode の拡張機能 VSCode Remote Container をインストールします。これはDockerのコンテナ内にあるファイルをVSCodeで開き、開発することを可能にする拡張機能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/a0fc1513-85ec-d348-0508-1e9fa65893b9.png)
 インストールボタン押下だけで簡単にインストールできます。

## Docker拡張機能をインストールする
* ついでに Docker 拡張機能も入れておきます。これはDockerの操作をVSCodeからできるようにする拡張機能です。タブからコンテナ一覧を見たり、起動・削除ができるようになるそうです。ただし最初のうちはdockerでコマンド叩いた方が勉強になるかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/9a0a5cab-4201-b22a-0b99-bda8a5a04dc8.png)


## Python開発用拡張機能をインストールする
* Python開発用の拡張機能をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/dff6031f-7bba-4df7-366e-ecdddcc07c3c.png)




## 開発環境イメージを作るためのDockerfileなどを用意する
* VSCode からアクセスするためのコンテナを起動する場合、Dockerfile と docker-compose.yml を用意し、そこを基点にして接続するのが普通のようです。
* DockerfileとはDockerイメージを構築するコマンドをテキストに書いておき、バッチファイルのように実行する機能です。DockerでのIaC(Infrastructure as Code)[^3]を実現する機能とも言えます。Dockerfileを使うことでコードベースでインフラ環境を定義することが可能となります。
* docker-compose.yml とは、docker-composeと呼ばれるツールの設定ファイルです。docker-composeはローカル環境でDockerのオーケストレーションを行うためのツールです。オーケストレーションとは、複数のコンテナから成るサービスを構築・実行する手順を自動的に行うことを言います。（と言っても、今回はコンテナは開発用に１つしか使いません）
* docker-compose.yml と Dockerfile の役割分担を簡単に言うと、docker-compose.ymlがコンテナの起動操作をするための情報で、Dockerfile がイメージを構築するための情報になります。Dockerfileとdocker-compose.yml の内容について詳細に説明していくと大変なので、今回は割愛させていただきます。[こちらのページ](https://qiita.com/jhorikawa_err/items/fb9c03c0982c29c5b6d5)[^4]を参考にWSL上のUbuntuのホームディレクトリ(/home/smatsu)にファイルを作成します。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/9dd3ddb5-5cf7-d0c2-345f-c1ea7a9e4000.png)|
|:--|

optディレクトリはコンテナから共有するためのフォルダです。ここに適当なPythonのソースを入れておきます。後でためしに実行してみるためです。今回は参考にしたページにしたがい、sample.py を入れておきます。

```py:sample.py
import math
import sys

def main():
    val = float(sys.argv[1])
    print(math.radians(val))

if __name__ == "__main__":
    main()

```


* Dockerfileの内容です。

```Dockerfile:Dockerfile
FROM python:3
USER root

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
```
FROM のステートメントでpython:3のイメージをベースにすることを指定し、USER で実行ユーザが root であることを指定しています。あとは普通のlinuxコマンドと環境変数なのでなのでなんとなく想像がつくと思います。

* docker-compose.yml の内容です。

```yml:docker-compose.yml
version: '3'
services:
  python3:
    restart: always
    build: .
    container_name: 'python3'
    working_dir: '/root/'
    tty: true
    volumes:
      - ./opt:/root/opt
```

　今回docker-compose.ymlについての詳細は割愛していますが内容を簡単に説明すると、python3というコンテナを用意し、Dockerfileはカレントディレクトリにあるものを参照し、/root を作業ディレクトリとし、対話モードを有効にし、ホストOSの ./opt ディレクトリをコンテナ上の /root/opt ディレクトリにマウントする、という内容になっています。

　これで準備完了です。これまでに作ったdockerイメージとコンテナは紛らわしいので Docker for Desktop 上で消しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/ec86eeb0-833d-bf42-6a1c-75ab7e7c7054.png)


# docker-compose を起動する
* WSL上で先ほど作ったdocker-pythonフォルダへ移動しdocker-composeを起動、イメージとコンテナを作成、起動します。

```:Ubuntu
$ cd docker-python/
$ docker compose up -d --build
・・・
（いろいろ出力されますが長いので割愛）
・・・
[+] Running 1/1
 ⠿ Container python3  Started                                                                                      0.9s
$
```

オプションの -d はデタッチドモード（バックグラウンド実行）、--build はコンテナ起動前にDockerfileを参照してイメージをビルドすることを指定しています。
成功すればイメージとコンテナが追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/a8328720-1b1c-b9ca-a4e0-c2018d5c76b9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/445d71bd-cffa-d7a4-a9eb-f1e858017528.png)

```:Ubuntu
$ docker image ls
REPOSITORY              TAG       IMAGE ID       CREATED         SIZE
docker-python_python3   latest    c025108e0cb3   4 minutes ago   990MB
$ docker container ls
CONTAINER ID   IMAGE                   COMMAND     CREATED         STATUS         PORTS     NAMES
29030d48f75a   docker-python_python3   "python3"   4 minutes ago   Up 4 minutes             python3
```
python3というのはdocker-compose.ymlの中で指定したコンテナの名前で、この名前でコンテナが出来ているのがわかります。

# シェルからコンテナに接続してみる
* docker-compose で起動したコンテナにシェルから接続してみます。

```:Ubuntu
$ docker compose exec python3 bash
root@29030d48f75a:~# ls -la
合計 40
drwx------ 1 root root 4096  2月  6 17:56 .
drwxr-xr-x 1 root root 4096  2月  6 17:38 ..
-rw------- 1 root root   27  2月  6 17:56 .bash_history
-rw-r--r-- 1 root root  571  4月 11  2021 .bashrc
drwxr-xr-x 1 root root 4096  2月  6 17:38 .cache
-rw-r--r-- 1 root root  161  7月  9  2019 .profile
-rw------- 1 root root    0  1月 29 11:37 .python_history
-rw------- 1 root root  820  2月  6 17:47 .viminfo
-rw-r--r-- 1 root root  254  2月  5 08:20 .wget-hsts
drwxr-xr-x 2 1000 1000 4096  1月 16 20:55 opt
root@29030d48f75a:~# python -VV
Python 3.10.2 (main, Jan 29 2022, 02:41:17) [GCC 10.2.1 20210110]
root@29030d48f75a:~#
```

ここで先ほどのように"docker run -it docker-python_python3 bash"としても似たような動作になりますが、既存のコンテナではない新しいコンテナが起動します。"docker exec -it python3 bash" ならば上のdocker compose execコマンドと同様に既存のコンテナに接続します。なお Docker for Desktop からCLIアイコンをクリックしても同様の動きになります。

* いったんコンテナを終了します。

```:Ubuntu
$ docker compose down
[+] Running 2/2
 ⠿ Container python3              Removed                                                                         10.7s
 ⠿ Network docker-python_default  Removed
$ docker container ls
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```
コンテナがなくなっているのがわかります。

# VSCodeでコンテナを起動

* Dockerfile と docker-compose.yml に問題ないことが確認できたのでVSCodeからの接続をしてみます。
* WSL内で "code ." と打つとVSCodeが起動します。先ほど作ったdocker-pythonフォルダで起動してみます。

```:Ubuntsu
$ code .
Updating VS Code Server to version 5554b12acf27056905806867f251c859323ff7e9
Removing previous installation...
Installing VS Code Server for x64 (5554b12acf27056905806867f251c859323ff7e9)
Downloading: 100%
Unpacking: 100%
Unpacked 2158 files and folders to /home/smatsu/.vscode-server/bin/5554b12acf27056905806867f251c859323ff7e9.
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/aa17067e-4201-7349-edfe-9cb954e51441.png)

しかしこれではWSL上のファイルを参照して起動しているだけであって、コンテナに接続しているわけではありません。docker-compose.ymlを右クリックして、Compose Up を選択してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/7bb3dd0f-415d-404a-1a34-82bf60cedaaa.png)
するとターミナルが起動し、docker-compose up が走ってコンテナが起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/e7a3f673-1bf1-960f-2aaa-25037b2065dd.png)
Docker for Desktop の画面にもコンテナが表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/32c3cb19-12a1-a738-75c1-ccd8f8c29597.png)

## コンテナにリモート接続

ここでVSCodeの左下の緑色の部分をクリックすると、リモート接続メニューが（なぜか上の方に）出て来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/a3879cf0-e405-3964-cb97-6050b1253a3b.png)
　　↓　ここでReopen in Container を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/7a356944-28b0-7d33-1632-e16b6e14183e.png)
　　↓　次に From 'docker-compose.yml' を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/21e70419-e7c4-0db9-d5ce-3aaa596a279b.png)

この後、VSCodeが再起動したりいろいろ不思議な動きをするのですが、いろいろ試して[^5]どうにかコンテナが開けました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/3f3239bb-7b0d-c795-2dcd-8beabd363d8d.png)

# サンプルソースを実行する
* 左のバーから実行とデバッグ（虫のマーク）アイコンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/c2289a74-b42c-0a54-714e-72c1cafc61af.png)
　↑　デバッグ設定を選択するように言われるので Python Fileを選択してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/48e9e0ff-dec6-3dbe-d511-b2d3297b9780.png)
　↑　するとプログラムが走ったようですが、5行目で例外が発生してしまいました。起動時の引数が足りないためインデックスエラーが発生したようです。実行メニューから「デバッグの停止」を選んでいったん終了します。（右上にあるバーの □ アイコンをクリックでも終了できます）

## launch.jsonを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/a3e0df55-adbf-5071-940c-c9865d7aa40f.png)
　↑　「実行とデバッグをカスタマイズするには、launch.json ファイルを作成します。」と表示されています。これをクリックするとlaunch.json が生成されました。調べてみると、ここに args というプロパティを追加すると、引数として渡されるようです。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "args": ["180"]
        }
    ]
}
```
　　↓　.vscode というフォルダが作られ、そこにlaunch.jsonファイルが格納されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/1124a279-84e6-2167-663a-8cc4eeb38550.png)

## 再度実行してみる。

* 再度実行してみます。正常終了したのがわかりやすいようにソースに「正常終了！」と出力する print 文を追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/7656513d-97b2-bac4-38c7-c9fa21167533.png)

　　↑　ターミナルに 3.141592653589793 という値が表示され、sample.py プログラムが正常終了したのがわかります。

## デバッグ実行をブレークしてみる

* 今度はデバッグ実行を途中でブレークしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/dbfb8592-2fc0-8f12-7085-7e5be3b38808.png)
　↑　6行目にブレークポイントを置いてみました。F5でデバッグ実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/2edd9808-4337-002c-c72b-460bf9992eec.png)
　↑　ブレークしました。argvに ['/root/opt/sample.py', '180'] という値が入っているのが見えます。
引数が格納される argv は配列変数で、0番目にプログラムのパスが入り、1番目にコマンドライン引数が渡されるということがわかります。ついでにウォッチ式で特殊変数の _____name_____ の値も表示させています。これはこのモジュールの起動の仕方を判別するための特殊な変数ですが、長くなりましたのでここでは深入りしません。

# まとめ
　以上で、WSL、Docker、VSCode という３つのツールを連動させ、コンテナ環境で Python の開発ができるところまでたどり着きました。しかしこれはあくまで開発のスタートラインについただけであって、ここから先が本当の開発になります。そのためには Python や Docker の使い方はもちろん、開発内容のテーマ（機械学習、スクレイピング、作業自動化など）についてさらに勉強が必要になるでしょう。

今回の記事はここでいったん終了とします。ここまで読んでいただきありがとうございました。


[^1]: WSL上でGPUを動かすCUDA on WSL2という仕組みもNVIDIAから提供されているらしい。参照：[待ってました CUDA on WSL 2](https://qiita.com/ksasaki/items/ee864abd74f95fea1efa)
[^2]: Docker Hub とは、the Docker Communityが提供しているイメージ管理用サービス。一種のリポジトリのようなもの。https://hub.docker.com/
[^3]: Infrastructure as Codeはインフラの構成をコードに落とし込み宣言的に環境を構築する手法。
[^4]: [DockerでPython実行環境を作ってみる - Qiita](https://qiita.com/jhorikawa_err/items/fb9c03c0982c29c5b6d5)
[^5]: この辺りはまだまだ環境に不慣れなため、実際にはいろいろトラブって試行錯誤しています。もう少しちゃんと分かってきたら説明を加えるかも知れません。
[^6]: 入門サイトの例としては http://y-ohgi.com/introduction-docker/ の評価が高いようです。
[^7]: コンテナが不要であれば必ずしもコンテナを使う必要はなく WSL + VSCode という形でも開発は可能です。