2142 words
11 minutes
WSL2 + Docker DesktopのMySQLにVirtualBoxから接続できなかった話

VirtualBox上に作ったWindows環境から、ホストPCのDockerで動かしているMySQLへ接続したい。

構成としてはこんな感じ。

ホストPC(Windows)
├─ WSL2
├─ Docker Desktop
│ └─ MySQLコンテナ
│
└─ VirtualBox
└─ Windows

MySQLコンテナはDocker側で3307番ポートを公開していた。

0.0.0.0:3307->3306/tcp

なので最初は、

ホストPCのIPアドレス:3307

でVirtualBoxから接続すれば終わりだと思っていた。

終わらなかった。

この記事は、VirtualBoxからWSL2 + Docker Desktop上のMySQLへ接続できなかったときの調査ログ。

接続方法だけ知りたい場合は、手順だけを抜き出したこちらの記事へ。

VirtualBoxからDocker Desktop上のMySQLへ接続する方法

まずホストPCのIPが分からん#

VirtualBoxから接続するので、localhost ではない。

VirtualBoxの中でlocalhostと書いたら、それはVirtualBox自身を指す。

ということでホスト側で、

Terminal window
ipconfig

を実行。

ここで地味に困った。

どのIPv4アドレス使うねん。

WSLやVirtualBoxを入れているWindowsでipconfigすると、ネットワークアダプターがいくつも出てくる。

今回は実際にネットワーク接続に使っているWi-Fi側のIPv4アドレスを使った。

これで、

ホストPCのIPv4アドレス:3307

へ接続すればいいはず。

VirtualBox側から確認する。

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 3307

結果。

TcpTestSucceeded : False

はい。

Dockerのポート公開を確認する#

まず疑ったのはDocker側。

Terminal window
docker ps

でMySQLコンテナを見る。

ポートは、

0.0.0.0:3307->3306/tcp

になっていた。

少なくとも設定上は、

ホスト側 3307
↓
コンテナ側 3306

で公開されている。

127.0.0.1:3307だけにバインドされているわけでもない。

じゃあFirewallか?

Windows Firewallを開けてみる#

Windows側で3307番ポートへの受信を許可した。

Terminal window
New-NetFirewallRule `
-DisplayName "Docker MySQL 3307" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 3307 `
-Action Allow

もう一度、

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 3307

結果は、

False

変わらん。

このあたりから、単純に「VirtualBoxからFirewallで弾かれている」だけではなさそうになってきた。

localhostなら通る#

次にホストWindows自身から確認した。

Terminal window
Test-NetConnection localhost -Port 3307

これは、

TcpTestSucceeded : True

だった。

ところが同じWindowsから、自分自身のIPv4アドレスを指定すると、

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 3307

こちらは、

False

だった。

これはかなり大きなヒントだった。

VirtualBoxから届かない以前に、

Windows自身が、自分のIPv4アドレス経由で3307へ接続できていない。

つまり、この段階ではVirtualBoxを疑っても仕方ない。

localhost:3307
→ True
ホストIPv4:3307
→ False

この差を見る必要がある。

LISTENしているポートを確認する#

Windows側で、

Terminal window
netstat -ano | findstr :3307

を実行してみた。

何も出ない。

さらに、

Terminal window
Get-NetTCPConnection -LocalPort 3307 -State Listen

でも該当するオブジェクトが見つからなかった。

Dockerでは、

0.0.0.0:3307->3306/tcp

と表示されている。

localhost:3307にも接続できる。

でもWindows側のnetstatでは3307をLISTENしているものが見えない。

ここで、そういえばこのDocker環境はDocker Desktop + WSL2だったことを思い出した。

Docker DesktopとWSL2ってどういう関係?#

ここ、Docker Desktopを普通に使っていると割と忘れる。

Windows上でDocker Desktopを使っていて、WSL2バックエンドを利用している場合、LinuxコンテナがWindows上で直接そのまま動いているわけではない。

ざっくりイメージすると、

Windows
↓
Docker Desktop
↓
WSL2側のLinux環境
↓
Dockerコンテナ
↓
MySQL

という層がある。

普段はDocker Desktopがこの辺をうまく面倒見てくれるので、

localhost:3307

と書けば普通にMySQLへ届く。

だから普段の開発では、あまり境界を意識しなくても済む。

ところが今回はさらに外側にVirtualBoxがいる。

VirtualBox
↓
Windows
↓
Docker Desktop / WSL2
↓
MySQLコンテナ

VirtualBoxから見ると、まずWindowsホストへ入って、そこからDocker Desktop側へ渡してもらう必要がある。

普段localhostで隠れていた境界が、VirtualBoxという別環境からアクセスした途端に姿を現した。

Windows側に入口を作ってみる#

そこでWindowsのportproxyを使って、外から入ってきた通信をlocalhost:3307へ転送することにした。

最初はこうした。

Terminal window
netsh interface portproxy add v4tov4 `
listenaddress=0.0.0.0 `
listenport=3307 `
connectaddress=127.0.0.1 `
connectport=3307

確認すると、

Terminal window
netsh interface portproxy show all

には、

0.0.0.0 3307 → 127.0.0.1 3307

という設定が出ている。

IP Helperサービスも確認した。

Terminal window
Get-Service iphlpsvc

こちらはRunning。

IPv6のバインドも確認したが、有効になっていた。

設定だけ見ると動きそう。

しかし、

Terminal window
netstat -ano | findstr :3307

には相変わらず何も出なかった。

設定はいる。

でもLISTENしてない。

入口と出口を同じ3307にするのをやめた#

ここで、portproxyの待受側を別ポートにして試すことにした。

いったん3307の設定を削除。

Terminal window
netsh interface portproxy delete v4tov4 `
listenaddress=0.0.0.0 `
listenport=3307

今度は13307を入口にする。

Terminal window
netsh interface portproxy add v4tov4 `
listenaddress=0.0.0.0 `
listenport=13307 `
connectaddress=127.0.0.1 `
connectport=3307

Firewallにも13307の受信許可を追加。

Terminal window
New-NetFirewallRule `
-DisplayName "VirtualBox to Docker MySQL 13307" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 13307 `
-Action Allow

そして、

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 13307

結果。

TcpTestSucceeded : True

来た。

VirtualBoxからもTrueになった#

ここまで来たら、いよいよVirtualBox側から確認する。

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 13307

こちらも、

TcpTestSucceeded : True

になった。

最終的な経路はこうなった。

VirtualBox
↓
ホストPCのIPv4:13307
↓
Windows portproxy
↓
127.0.0.1:3307
↓
Docker Desktop / WSL2
↓
MySQLコンテナ:3306

ようやくVirtualBoxからDocker側へ橋が架かった。

しかしアプリ側にポート設定がない#

ネットワークが通ったところで、もう一つ問題が出た。

接続したいアプリ側に、MySQLのポートを指定する場所が見当たらない。

13307を指定できないなら、このままでは使えない。

そこでVirtualBoxから見える入口をMySQLのデフォルトポートである3306にする。

イメージとしては、

VirtualBox
↓
ホストPCのIPv4:3306
↓
Windows portproxy
↓
127.0.0.1:3307
↓
Docker Desktop
↓
MySQL:3306

これならアプリ側はホスト名だけ指定し、通常のMySQL接続として扱える。

13307は今回の切り分けには役立ったが、最終的にアプリの仕様に合わせるなら入口側のポートも考える必要がある。

今なら最初にどこを見るか#

今回、一番効いた確認はこれだった。

Terminal window
Test-NetConnection localhost -Port 3307

と、

Terminal window
Test-NetConnection <ホストPCのIPv4> -Port 3307

の比較。

前者がTrueで後者がFalseだった時点で、

「MySQLコンテナが動いていない」

とか、

「VirtualBoxのネットワークがおかしい」

という方向から一旦離れられる。

今回のような構成なら、最初から次の順番で見るとかなり早そう。

  1. docker psで公開ポートを確認
  2. Windowsからlocalhost:ポートを確認
  3. WindowsからWindows自身のIPv4:ポートを確認
  4. VirtualBoxからWindowsのIPv4:ポートを確認

この3地点を順番に見ると、

MySQL
↑
Docker Desktop / WSL2
↑
Windows
↑
VirtualBox

のどこまで通信できているかが分かる。

「Dockerでポート公開してるからOK」の一歩先#

今回最初に見ていたのは、

0.0.0.0:3307->3306/tcp

だった。

これを見て、

「3307公開されてる。じゃあVirtualBoxからホストIPの3307に行けばええな」

と思った。

普段ならそれで終わることも多い。

でも今回は、その間にWindows、Docker Desktop、WSL2という層がいた。

Docker Desktopは普段、この辺をかなり自然につないでくれる。

自然すぎて、存在を忘れる。

たぶん、家でパンイチで過ごすのと同じなんやと思う。

家の中だけなら何の問題もない。むしろ快適ですらある。

でも、その感覚のまま玄関を開けて宅配便を受け取ろうとした瞬間、

「あ、ここから外やった」

となる。

今回もそんな感じだった。

localhostの中だけで完結している間は、WindowsとWSL2とDocker Desktopの境界なんてほとんど意識しなくていい。

でもVirtualBoxという「外」と接触した瞬間、それまで意識していなかった境界が急に効いてくる。

Docker Desktopの0.0.0.0:3307を見て安心する前に、

「今、パンツ履いてたっけ?」

くらいの確認はした方がいい。

いや、パンイチなんやからパンツは履いてるか。