本記事は、 docker Advent Calendar 2021 2日目 の記事だ。
昨日は、 @subretu氏 の DockerfileにおけるCMDとRUNの挙動 だった。
docker のコンテナやボリュームの中身を見る際に、 ホスト PC から SMB ファイル共有で参照できると都合が良い。
と言うことで以前、以下のような記事を作成したが、 Docker Desktop では利用できない問題があった。
今回は、それを解消する手段について紹介したい。
先に断っておくが、私自身でいくつかの PC で試したところ、上手くいく PC とダメな PC がハッキリと分かれてしまい、現時点ではどのような条件だとダメなのかハッキリわかっていない。
何故 Docker Desktop では Samba にアクセスできないのか
Windows の SMB over TCP/IP アクセス機能は、接続先が 445 番ポートでないと利用できない制限がある。
ところが、 Docker Desktop で 445 番ポートをリッスンしようとしても、 ホスト OS 側の LanmanServer
サービス ("Server" と言う表示名のもの) が常に localhost の 445 番ポートを占有しているため、ポートをリッスンできずに失敗してしまう。
さらに、この LanmanServer
サービスは、 Docker Desktop サービスがこれに依存しているため、 このサービスを止めることもできない。
このため、なんとか localhost の 445 番ポート以外をリッスンさせてる方法を考えなくてはならない。
ループバックアダプターと portproxy を使う
結論から言うと、 "ループバックアダプター" と "portproxy" という Windows 組み込みの機能を利用する。
ループバックアダプターとは、一言で言うと 仮想的なネットワークアダプタ (NIC) だ。
この 仮想NIC に 適当なIPアドレス を割り当てて、 その 445 番ポートにアクセスすると、 localhost の 445 番以外 のアドレスに portproxy させる ことができる。
Docker Desktop 側で その 445 番以外 のアドレスをリッスンしておけば、問題を回避できるはずだ。
ループバックアダプターのインストール
- Win+R で「ファイルを指定して実行」を開き、
hdwwiz.exe
を実行する
- ハードウェア追加ウィザードで、「一覧から選択したハードウェアをインストールする」を選択する
- ネットワークアダプタ → Microsoft → Microsoft KM-TEST Loopback Adapter を選択
- ネットワーク接続の一覧 (Win+R で
ncpa.cpl
を実行) を開き、 今作成した 仮想NIC のプロパティを開く。
- TCP/IPv4 のプロパティを開いて、 適当な固定 IPアドレスとサブネット (例として
192.168.254.2
) を設定する
- 管理者権限でターミナル (コマンドプロンプトや PowerShell) を立ちあげ、 netsh コマンドでポートプロキシを立ちあげる。
- PC を再起動 する
これで準備完了だ。
SMB で使ってみる
冒頭に紹介した、以前の記事の Dockerfile を使って、 SMB を立ちあげてみる。
この Dockerfile のあるディレクトリをカレントディレクトリにして、以下を実行する。
docker build -t simple-samba .
docker run --rm -p 38445:445 simple-samba
実用的には、 docker run の -v
オプションなどで、適当なボリュームをマウントする感じになるだろう。
そして、コンテナを起動した状態で ループバックアダプター の 仮想NIC に設定したアドレス (上記の例だと \\192.168.254.2\
) に Windows エクスプローラー からアクセスできれば成功だ。
うまく動かない場合
うまく動かない場合、 portproxy を提供する iphlpsvc
(IP Helper) サービス が、 445 ポートをリッスンできているか調べてみよう。
netstat
コマンドで、 192.168.254.2:445 をリッスンしているプロセスID を調べる。
そして、その プロセスID が何のサービスなのか、 Windows PowerShell v5.11 Get-WmiObject Win32_Service から確認してみよう。
うまく動作していれば、以下のように iphlpsvc
サービス であることが確認できるだろう。
PS > netstat -ano | findstr 445
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:8445 0.0.0.0:0 LISTENING 13944
TCP 192.168.254.2:445 0.0.0.0:0 LISTENING 4016
TCP 192.168.254.2:60665 192.168.254.2:445 ESTABLISHED 4
PS > Get-WmiObject Win32_Service | ? ProcessId -EQ 4016;
ExitCode : 0
Name : iphlpsvc
ProcessId : 4016
StartMode : Auto
State : Running
Status : OK
もし、 192.168.254.2:445 をリッスンしているプロセスID が 4 だった場合、 OS の LanmanServer
サービスに関係するカーネルプロセスがこのポートをふさいでしまっているため、 portproxy が正しく動作していないと言うことになる。
PS > netstat -ano | findstr 445
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:8445 0.0.0.0:0 LISTENING 13944
TCP 192.168.254.2:445 192.168.254.2:60665 ESTABLISHED 4
TCP 192.168.254.2:60665 192.168.254.2:445 ESTABLISHED 4
これは、おそらく portproxy を機能させる iphlpsvc
サービスが機能する前に、 LanmanServer
サービスが 445 ポートを塞いでしまっているからだと考えられる。
確実ではないと思われるものの、 iphlpsvc
サービスが先に機能するかもしれない対策を、いくつか紹介する。
いずれも、設定後 PC の再起動が望ましい。
- Win+R で「ファイルを指定して実行」を開き、
services.msc
を実行して、 「サービス」 の管理コンソールを立ちあげ、 "IP Helper" (iphlpsvc
) のスタートアップが「自動」 になっていることを確認する。- 管理者権限で PowerShell を立ちあげ、 以下のコマンドを実行するのでも OK
Set-Service iphlpsvc -StartupType Automatic;
- 管理者権限で PowerShell を立ちあげ、 以下のコマンドを実行するのでも OK
- netsh で IPv6 のサポートを追加する
- 管理者権限でターミナル (コマンドプロンプトや PowerShell) を立ちあげ、 netsh コマンドで以下のように実行する。
netsh interface ipv6 install
- 理由がよくわからないが、これで上手くいく場合があるらしい。
portforwarding - NETSH port forwarding from local port to local port not working - Stack Overflow
- 管理者権限でターミナル (コマンドプロンプトや PowerShell) を立ちあげ、 netsh コマンドで以下のように実行する。
LanmanServer
サービスの起動を遅延させる:- 管理者権限で PowerShell を立ちあげ、
LanmanServer
サービスの起動を「手動」にして起動を遅延させる。Set-Service LanmanServer -StartupType Manual;
- 設定の → アカウント → サインイン オプション → 「更新または再起動の後にサインイン情報を使ってデバイスのセットアップを自動的に完了します。」 の on, off を切り替えてみる。
- ただし、 off にすると OS 再起動時のログイン速度が遅くなる。
- 管理者権限で PowerShell を立ちあげ、
- コントロール パネル → ネットワークとインターネット → ネットワークと共有センター → 共有の詳細設定 で、 仮想NIC が属するプロファイル (ゲストまたはパブリック) で、 「ファイルとプリンターの共有を無効にする」 を選択する。
- 当然、ゲストまたはパブリックネットワークへ繋いだときに、ファイル共有ができなくなる。
どうしても 仮想NIC の 445 ポートが PID 4 に奪われる場合、 iphlpsvc
サービスと LanmanServer
サービスの起動順とかを見てみると良いかもしれないが…
確認したところで何か具体的な対策があるわけでは無いが…
$procs = Get-WmiObject Win32_Service -Filter "Name='iphlpsvc' or Name='LanmanServer'";
Get-WmiObject win32_process -Filter (($procs.ProcessId | %{ "ProcessId='$_'" }) -join ' or ') | select Name,CreationDate,ProcessId,@{Name='Service'; Exp={$procs | ? ProcessId -eq $_.ProcessId | select -exp Name}};
おわりに
後半、動作しない場合の対策についてつらつらと書いてしまったが、ちゃんと動きさえすればなかなか便利なはずだ。
明日、 docker Advent Calendar 2021 の 3日目 は… 今のところ空いているようだ。
どなたか書いてみてはいかがだろう?
-
2021年現在、 PowerShell v7 系列だと
Get-WmiObject
コマンドが動かないため。 ↩