Samba が動く Docker Desktop のコンテナで、ボリューム内のファイルを見る

Pocket

本記事は、 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 番以外 のアドレスをリッスンしておけば、問題を回避できるはずだ。

ループバックアダプターのインストール

  1. Win+R で「ファイルを指定して実行」を開き、 hdwwiz.exe を実行する
  2. ハードウェア追加ウィザードで、「一覧から選択したハードウェアをインストールする」を選択する
  3. ネットワークアダプタ → Microsoft → Microsoft KM-TEST Loopback Adapter を選択
  4. ネットワーク接続の一覧 (Win+R で ncpa.cpl を実行) を開き、 今作成した 仮想NIC のプロパティを開く。
  5. TCP/IPv4 のプロパティを開いて、 適当な固定 IPアドレスとサブネット (例として 192.168.254.2) を設定する
     
  6. 管理者権限でターミナル (コマンドプロンプトや PowerShell) を立ちあげ、 netsh コマンドでポートプロキシを立ちあげる。
    • 以下は、 仮想NIC の固定 IP に 192.168.254.2 を設定し、プロキシ先のポートを 38445 番にする場合。
      netsh interface portproxy add v4tov4 listenport=445 listenaddress=192.168.254.2 connectport=38445 connectaddres=127.0.0.1

  7. 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;
  • netsh で IPv6 のサポートを追加する
  • LanmanServer サービスの起動を遅延させる:
    • 管理者権限で PowerShell を立ちあげ、 LanmanServer サービスの起動を「手動」にして起動を遅延させる。
      Set-Service LanmanServer -StartupType Manual;
    • 設定の → アカウント → サインイン オプション → 「更新または再起動の後にサインイン情報を使ってデバイスのセットアップを自動的に完了します。」 の on, off を切り替えてみる。
    • ただし、 off にすると OS 再起動時のログイン速度が遅くなる。
  • コントロール パネル → ネットワークとインターネット → ネットワークと共有センター → 共有の詳細設定 で、 仮想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日目 は… 今のところ空いているようだ。
どなたか書いてみてはいかがだろう?


  1. 2021年現在、 PowerShell v7 系列だと Get-WmiObject コマンドが動かないため。 

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください