WSL/SSHリモート先で、VSCodeのリモートコンテナ機能を使う


**update 2020/07/18**
- VMを使う場合の最近のやり方→ https://qiita.com/74th/items/2ccfc07bd279aba610b1
- WSL2を使う場合、Docker DesktopがWSL2をサポートしている → https://docs.docker.com/docker-for-windows/wsl/

VSCodeにはリモートコンテナ機能があり、開発をDockerコンテナの中で行うことができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/e058240a-8abb-c760-bcb5-a086d04930b6.png)


リモートコンテナ機能の標準的な使い方であれば、Docker Desktop for Windows/Mac をインストールして使うのがよい。しかし、Docker Desktopは仮想マシンで動作させるため、ホストOSとは別にメモリを設定する必要がある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/ff914e30-4ebf-0a72-7f12-757ccf222b87.png)

Windowsの場合は、WSLを使うと、WSL中のLinuxのメモリ容量はホストOSと共有される。メモリ32GB、CPU16コアのマシンでWSL中でリソースを確認すると、以下のように表示される。

<img width="856" alt="wsl.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/e636698a-aed3-1fef-6264-fc52198699f2.png">
<img width="854" alt="wsl2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/dcb84d34-c6f2-6cac-8ed0-3c756b07c044.png">


また、リモートSSH機能では、外部のマシン上で開発できるため、ホストコンピュータ（MacBookProとか）のメモリ量に左右されず、使うことができる。

## やりかたの要約

- WSL/リモート上のDockerをTCP（例：ポート9000）でアクセスできるようにする
- DOCKER_HOST=tcp://remote:9000 を設定し、ホストマシンからdockerコマンドが使えるようにする
   - もしくは`socat TCP-LISTEN:9000,reuseaddr,fork UNIX-CLIENT:/var/run/docker.sock`と、unixsocketをTCPにリダイレクトする
- WSL、リモートSSH先でリポジトリをチェックアウトしておく
- docker-composeでそのリポジトリのディレクトリをマウントしたDevContainerを作り、コンテナを起動する
- コマンド"Remote-Container: Attach to Container"を実行して、コンテナに接続する

## WSLの準備

WSLでDockerを使うには、WSL 2である必要がある。インストール方法については、公式のドキュメントがあるのでそれを参照する。

公式ドキュメント: https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-install

以下のコマンドでWSL 2をデフォルトのバージョンにしてから、Windows StoreからUbuntuをインストールするとよい。

```
> wsl --set-default-version 2
```

## Dockerをtcpでサービスする

以下、WSLの中のUbuntuを前提に解説する。CentOS等異なるOSを使う場合には読み替えること。また、リモートSSH先でも同様である。

```
$ sudo apt update
$ sudo apt install -y docker.io docker-compose
$ sudo gpasswd -a $USER docker
```

通常はDockerサービスを起動した後にdockerにアクセスできるようになっている。この場合、DockerのAPIへのアクセスはUnixソケットを用いて行われる。

```
$ sudo service docker start
$ sudo docker ps
```

Dockerの起動オプションを書き換え、tcpで行えるようにする（以下の例は、すべてのホストに公開する設定になっているため、Firewall中で行う、サービスホスト名を変えるなど対応すること）。

```
$ vi /etc/default/docker
# TCP 9000
DOCKER_OPTS="-H 0.0.0.0:9000"

$ sudo service docker restart

$ export DOCKER_HOST=tcp://127.0.0.1:9000
$ docker ps
```

もしくは、socatを使ってUnixソケットをTCPにリダイレクトさせる。

```
$ socat TCP-LISTEN:9000,reuseaddr,fork UNIX-CLIENT:/var/run/docker.sock &
$ export DOCKER_HOST=tcp://127.0.0.1:9000
$ docker ps
```

WSLの場合、IPアドレスを調べておく。以下の例では`172.19.0.1`である（WSL内のサービスにはlocalhostで通常アクセスできるが、そうでない場合もあるため）。

```
$ ip addr show

20: br-9618f27bf4e9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:b4:05:32:91 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 brd 172.19.255.255 scope global br-9618f27bf4e9
       valid_lft forever preferred_lft forever
    inet6 fe80::42:b4ff:fe05:3291/64 scope link
       valid_lft forever preferred_lft forever
```

## ホストマシンからWSL／SSHリモート先のDockerにアクセスする

TCPでDockerAPIにアクセスできるようにするには、環境変数DOCKER_HOSTに"tcp://host:port"と設定すればよい。

WSLの場合、先のWSLのIPアドレスを設定する。

```:PowerShell
> $ENV:DOCKER_HOST = "tcp://172.19.0.1:9000"
> docker ps
```

SSHリモート先の場合、先のポートフォワーディングを行う。

```:PowerShell
> $ENV:DOCKER_HOST = "tcp://127.0.0.1:9000"
> ssh -L 9000:127.0.0.1:9000 host
> docker ps
```

```:bash
$ export DOCKER_HOST=tcp://127.0.0.1:9000
$ ssh -L 9000:127.0.0.1:9000 host
$ docker ps
```

この環境変数を設定したうえで、`code`コマンドを使い、VSCodeを起動する。
失敗して、リモート先のDockerを認識しない場合、Dockerをインストールする旨のメッセージが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/0258f32e-32ff-5dc5-dee7-78774afa023c.png)

以下のようになれば、Dockerを認識できている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/502ef011-0999-6289-bc8c-f54b6140a22e.png)


## WSL、リモートホストで、リポジトリをマウントするdocker-composeを作る

開発リポジトリをチェックアウトし、いろいろ仕込みを行う。

```yaml:docker-compose.yaml
version: "3"
services:
  python:
    build:
      context: .
      dockerfile: ./Dockerfile
    # 使うポートを先に開けておく
    ports: 
      - 8080:8080
    # WSL、リモートSSHのユーザID、グループIDを設定する
    user: "1000:1000"
    environment: 
      HOME: "/home/nnyn/"
    # とりあえずサービスとして起動させたいので、以下のコマンドを仕込む
    command: /bin/sh -c "while sleep 10000; do :; done"
    volumes:
      # チェックアウトしたディレクトリのマウント
      - /home/nnyn/vscode-book-python:/app
      # gitの設定のマウント
      - /home/nnyn/.git:/home/nnyn/.git
      # githubにアクセスする際に.netrcを使ってパスワードの入力を省略している場合は追加する
      - /home/nnyn/.netrc:/home/nnyn/.netrc
      # VSCodeServerのリソースのディレクトリ
      - /home/nnyn/.vscode-container:/home/nnyn/.vscode-server
```

Linuxでは`id`コマンドを使うと、現在のユーザID、グループIDを取得することができる。開発リポジトリはこのユーザID、グループIDでチェックアウトされているため、このユーザを使うようにする

```
$ id
uid=1000(nnyn) gid=1000(nnyn) groups=1000(nnyn),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(lxd),114(netdev),115(docker)
```

また、開発リポジトリのディレクトリや、gitの設定などをマウントする。

Dockerfileには、以下のようにgitなどもインストールしたり、拡張機能から使うライブラリも入れている（dev-requirements.txtの部分）。

```dockerfile:Dockerfile
FROM python:3.7
RUN apt-get update && apt-get -y install git procps

WORKDIR /app
ADD ./requirements.txt ./
RUN pip install -r requirements.txt
ADD ./dev-requirements.txt ./
RUN pip install -r dev-requirements.txt
```

```:dev-requirements.txt
mypy==0.701
pylint==2.3.1
black==19.3b0
```

これでdocker-composeを起動する。

```
$ docker-compose up -d
$ docker ps
```

## リモートコンテナ機能でdocker-composeのコンテナにアタッチする

リモート開発タブを開くと、起動したコンテナが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/aa16fd0c-f8cd-881e-0634-38baa0e8c1da.png)

コンテナの右に出る開くボタンを押すと、そのコンテナにアタッチできる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/a9204a28-dff1-88f9-e68c-259c48c042da.png)

アタッチして、異なるディレクトリが開かれた場合には「File: Open Folder...」コマンドを使って、目的のディレクトリを開きなおす。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3413/098e7d96-e79e-9162-8941-afb9388c5611.png)

これで、WSL、リモートホストであっても、リモートコンテナ機能が使えます！