仮想環境に導入したDockerをホスト側のWindows10で利用したい

[以前の記事](https://qiita.com/fuwafuwa-calm/items/17d11d1a233139ac66a1)で仮想環境上のWindows10 HomeにDocker Desktop for Windows WSL2 Backendを導入しましたが、そのDockerをホスト側のWindows10 で使えないか試してみました。

## 簡単なまとめ

 - 素直な構成にするのが一番です。
   - 今回のケースの場合は仮想環境上でWSL2とDocker Desktop for Windows WSL2 Backendを使いましょう。（後述のホスト側のバインドも正常に動作します）
   - 公式が提供するツールを使いましょう。（[Docker Desktop for Windows](https://docs.docker.com/docker-for-windows/)や[Docker Toolbox](https://docs.docker.com/toolbox/)など）
 - Visual Studio Code（VS Code）のRemote Containers 機能拡張が使えるようになります。
 - 今回試した手順では、 __ホスト側のディレクトリをDockerで正常にバインドできません__ でした。

## 検証環境

### ホスト側

 - Windows10 Pro バージョン1909 64bit
 - PowerShell上でOpenSSHクライアントが利用可能

### ゲスト側

 - 仮想化ソフトウェア：VMware Workstation Player 15.5.1
 - Windows10 Home バージョン1909 64bit（Insider Previewを使用）
 - PowerShell上でOpenSSHクライアントが利用可能
 - WSL2が有効
 - Docker Desktop for Windows Edge 2.2.3.0
   - WSL2 Backend機能が有効
   - デフォルトのWSL2ディストリビューション：Ubuntu 無印版 （18.04.4）

## 導入の流れ

### ホスト側の対応

ホスト側でdockerおよびdocker-composeコマンドを利用できるようにするため、Windows版のdocker-cliとdocker-composeをインストールします。

Windows用のパッケージ管理ツール「Chocolatey」を使うと、単体で導入できます。
※Chocolateyの導入方法については、以下の参考サイト様をご参照ください。

> [Chocolateyを使った環境構築の時のメモ](https://qiita.com/konta220/items/95b40b4647a737cb51aa)

```コマンドプロンプト:ホスト側のコマンドプロンプト
# インストール時には管理者権限でコマンドプロンプトを起動する必要があります。
# またはChocolatey GUIでの導入も可能です。

C:\WINDOWS\system32>choco search docker
...省略...
docker-cli 19.03.3 [Approved]
docker-compose 1.25.4 [Approved]
...省略...

C:\WINDOWS\system32>choco install docker-cli docker-compose
```

### ゲスト側のWindows10 Homeの対応

次に、ゲスト側のWindows10 HomeにてOpenSSHサーバーを導入および起動し、ファイアウォール設定を行ってSSHを送受信可能にします。
コマンドで実行する場合は、Microsoft公式の手順を実行します。
※以下に記載の手順は、参考サイト様の内容をほぼ引用しています。

> [Windows Server 2019 および Windows 10 用 OpenSSH のインストール](https://docs.microsoft.com/ja-jp/windows-server/administration/openssh/openssh_install_firstuse#initial-configuration-of-ssh-server)

```PowerShell:ゲスト側のWindows10のPowerShell
# 管理者権限でPowerShellを起動します。

# OpenSSHサーバーはデフォルトではオプション扱いで無効になっているため、有効化します。
# はじめに、OpenSSH関連の機能の状態を確認します。
PS C:\WINDOWS\system32>Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'

Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed
Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent

# OpenSSHサーバーをインストールします。
# インストール時に "OpenSSH-Server-TCP" という名前のファイアウォール規則が作成され、有効になります。
# これにより、ポート22でのSSH受信トラフィックが許可されます。
PS C:\WINDOWS\system32>Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

# OpenSSHサーバーを起動します。
PS C:\WINDOWS\system32>Start-Service sshd

# 以下の設定を行うと、Windowsの起動時にOpenSSH serverが自動起動するようになります。
PS C:\WINDOWS\system32>Set-Service -Name sshd -StartupType 'Automatic'

# ファイアウォールの規則が存在するかチェックします。
PS C:\WINDOWS\system32>Get-NetFirewallRule -Name *ssh*

# "OpenSSH-Server-In-TCP"という名前のファイアウォール規則が存在し、起動していれば
# （※Enabledの項目がTrueになっていれば）この後の作業は不要です。
# ファイアウォール規則が存在しない場合は作成します。
PS C:\WINDOWS\system32>New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
```

WSL2を起動します。ここではデフォルトのディストリビューションで設定しているUbuntuを起動します。
2020/04/10時点のWSL2ではサービスの自動開始ができないようなので、Ubuntu側のOpenSSHサーバーを手動で開始します。
※systemctlコマンドが使えないため、serviceコマンドで起動します。

```bash:ゲスト側のWSL2上のUbuntu
$ sudo service ssh start
 * Starting OpenBSD Secure Shell server sshd  [ OK ]
```

### ゲスト側のWSL2上のDocker Engineをホスト側に引き込む

ここまでで、ゲスト側のWSL2上のDocker Engineをホスト側に引き込む準備ができました。
SSHの多段接続とUnixソケットのローカルポートフォワード設定をホスト側で行います。
ホスト側のOpenSSHクライアント用の設定ファイルを作成します。

ここでは自宅内環境のため割り切って公開鍵認証を行わず、ゲスト側のWindows10 HomeおよびWSL2にパスワード認証にて接続するようにしています。

```shell:ホスト側の「%UserProfile%\.ssh\config」
# %UserProfile%\.ssh\config を編集します。

# ゲスト側のWindows10 Homeへの接続情報です。
# HostNameでIPアドレスを指定する場合は、Windows10 HomeのIPアドレスを指定します。
# userはWindows10 Homeのユーザーを指定します。
Host vm-win10home
    HostName XXX.XXX.XXX.XXX
    User bar

# ゲスト側のWSL2の設定です。
# Windows10 HomeからWSL2へTCP/IPでアクセスする際、ビルド18945以降は
# アドレスにlocalhostで指定できるようになったので、HostNameではlocalhostを指定しています。
# userはWSL2のユーザーを指定します。

# 特定のUnixソケット（Docker Engineの本体）をポート23750にローカルポートフォワードします。
# ProxyCommandのsshは、Windows10 Home側のssh.exeを絶対パスで指定します。
Host wsl2
    HostName localhost
    User baz
    LocalForward localhost:23750 /var/run/docker.sock
    ProxyCommand C:\Windows\System32\OpenSSH\ssh.exe -W %h:%p vm-win10home
```

ホスト側のPowerShellにて、OpenSSHクライアントを使ってSSHの多段接続を行います。

```PowerShell:ホスト側のPowerShell
# WSL2への接続を行います。
# -Nオプションで、リモートでコマンドを実行しないようにします。
# Windows10 Home側、WSL2側でパスワード認証が発生します。
PS C:\Users\foo>ssh -N wsl2
bar@XXX.XXX.XXX.XXX's password:
baz@localhost's password:
```

### 動作確認

ホスト側のコマンドプロンプトまたはPowerShellにて以下のDockerコマンドを実行し、ゲスト側のDocker Engineが認識できているか確認します。

```PowerShell:ホスト側のPowerShell
# コマンド実行時には、管理者権限は不要です。
PS C:\Users\foo>docker -H localhost:23750 version
```

<details><summary>実行結果の例</summary><div>

```PowerShell:ホスト側のPowerShell
Client:
 Version:           19.03.3
 API version:       1.40
 Go version:        go1.12.10
 Git commit:        2355349d-
 Built:             10/14/2019 16:41:26
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b
  Built:            Wed Mar 11 01:29:16 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
```
</div>

### 補足：docker context について

以下の設定を行っておくと、ホスト側でのdockerコマンドの入力時に-H オプションの指定が不要になり、少し手間が省けます。

```PowerShell:ホスト側のPowerShell
# docker コンテキストの一覧を表示します。
PS C:\Users\foo>docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
default *           Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm

# docker コンテキストを新規に作成します。「my-context」部分は自由に名前をつけることができます。
PS C:\Users\foo>docker context create my-context --docker host=tcp://localhost:23750
my-context
Successfully created context "my-context"

# 作成したdocker コンテキストをメインで使用するようにします。
PS C:\Users\foo>docker context use my-context

# docker コンテキストの一覧を再度表示した場合、作成したコンテキストに「*」がついているのを確認します。
PS C:\Users\foo>docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
default             Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
my-context *                                                  tcp://localhost:23750
```

### VS CodeのRemote Containers 機能拡張について

最後に、VS CodeのRemote Containers 機能拡張が動作するように設定します。
ホスト側のVS Codeの機能拡張サイドバーより「Remote」で検索して「Remote Development」機能拡張をインストールします。
※「Remote Containers」機能拡張のみを個別にインストールしてもOKです。

![remote-development.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370731/74689c13-5ac8-f2ca-a26a-c8c7defaf005.png)

併せて「Docker」機能拡張もインストールします。

![docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370731/aabbc982-9010-7455-baed-ff15624d09b1.png)

Docker機能拡張をインストールすると、設定画面にてdockerのホスト指定が行えるようになります。
設定画面を開き、以下の設定を行います。
以下の設定はVS Codeの再起動は不要で、即座に反映されます。

 - 検索欄に「docker.host」と入力します。
 - 「Docker: Host」の設定欄にSSHでローカルポートフォワーディングしているDockerを指定します。
   - ここでは「tcp://localhost:23750」と入力します。

![setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370731/391d713e-4c22-9c71-f638-458275b3b23c.png)

VS Codeのアクティビティバーより「リモート エクスプローラー」（※Remote Development機能拡張インストール後に選択可能）または「Docker」（※Docker機能拡張インストール後に選択可能）をクリックします。

サイドバーにてWSL2上のdockerコンテナやイメージなどが見えること、およびコンテナの起動や起動中のコンテナへVS Codeから接続できることを確認します。

■リモート エクスプローラーの場合
![panel-remote-base.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370731/dd8a8341-1254-f9fd-ec3f-45278a777f30.png)

■Dockerの場合
![panel-docker-base.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370731/27e61281-c092-1a06-c4b3-163d4befb1a7.png)

## ハマった点など

 - Dockerfileまたはdocker-compose.ymlにてホスト側のディレクトリをコンテナにバインドしようとすると、エラーになったり正常にバインドできなかったりとうまくいきません。
今回の構成ではホスト側はゲスト側のDocker Desktop for Windowsの対象範囲外のため、どうしようもないと思われます。
 - 多段SSHを行う際に、「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」という警告が出て接続できなくなるときがあります。これは仮想環境やWSL2のVMのIPアドレスが変更になったり、構成に変化があると発生するようです。
今回は自宅内開発のため、警告が出た場合はknown_hostsから対象の環境の定義を削除することで対応します。
その後、再度多段SSHを行うとうまくいきます。

```PowerShell:PowerShell
# PowerShellにて実行します。
PS C:\Users\foo> ssh-keygen -R XXX.XXX.XXX.XXX
PS C:\Users\foo> ssh-keygen -R localhost
```

## 最後に

Dockerの公式ツールを使えば、上記の手順は全く不要です。ハマリポイントも解消されます。

## 参考サイト様

 - [Chocolateyを使った環境構築の時のメモ](https://qiita.com/konta220/items/95b40b4647a737cb51aa)
 - [Windows Server 2019 および Windows 10 用 OpenSSH のインストール](https://docs.microsoft.com/ja-jp/windows-server/administration/openssh/openssh_install_firstuse#initial-configuration-of-ssh-server)
 - [Windows 10にオンデマンド機能のOpenSSHサーバをインストールする方法](https://www.atmarkit.co.jp/ait/articles/1903/28/news005.html)
 - [Windows Subsystem for Linux のリリース ノート - ビルド 18945](https://docs.microsoft.com/ja-jp/windows/wsl/release-notes#build-18945)
 - [Linuxがほぼそのまま動くようになった「WSL2」のネットワーク機能](https://www.atmarkit.co.jp/ait/articles/1909/09/news020.html)
 - [sshのUNIXドメインソケット転送を利用してリモートのDockerホストに接続する](https://qiita.com/morokosi/items/b03fed7ce5c0dde9c337)
 - [Windwos10でssh ProxyCommandの多段SSHの設定](https://www.suzu6.net/posts/205-ssh-config-proxycommand-windows10/)
 - [ssh接続先のdockerコンテナにVSCodeのRemote Developmentで繋ぐ](https://qiita.com/kanosawa/items/07e26edb19c86091fa48)
 - [docker context](https://docs.docker.com/engine/reference/commandline/context/)
 - [Using an SSH tunnel to connect to a remote Docker host](https://code.visualstudio.com/docs/remote/troubleshooting#_using-an-ssh-tunnel-to-connect-to-a-remote-docker-host)
 - [SSH接続で WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! って言われて接続を拒否られるとき](https://qiita.com/wnoguchi/items/690f3f4651f8f11e4ed3)
 - [SSH接続エラー回避方法：.ssh/known_hostsから特定のホストを削除する/削除しないで対処する3つの方法](https://qiita.com/grgrjnjn/items/8ca33b64ea0406e12938)