DockerイメージをWindows10へ移植
前回DebianOS環境で、公式のhttpdイメージにPHPやSSLを加えたカスタムDockerイメージを作成しました。その環境はDebianだったのですが、これをWindows10で動くDocker Desktop環境に移植できるかを試してみます。
Windows10にDockerをインストールする
DockerをWindwos環境にインストールします。要件は、BIOSレベルでハードウェア仮想化ができること、アーキテクチャが64ビット、4G以上のメモリです。
まずはBIOSやUEFIで仮想化が有効になっているか確認します。
画面遷移や設定名は一様でないと思いますが、筆者の環境では、起動時F2ボタンを押下→Advanced→Proceccer Configuration→Intel(R) Virtualization Technologyとなっていました。
次に、Windows10に戻って設定画面の検索バーに「Windowsの」と入力してください。「Windowsの機能の有効化または無効化」というサジェストがでてくるので、それを開き「Linux用 Windowsサブシステム」と「仮想マシンプラットフォーム」にチェックを入れて確定させます。
これらの作業はPowerShellかコマンドプロンプトから「wsl --install」とすることでも可能になりましたが、こちらを実行すると一緒にUbuntuまで付いてきます。
最後にDocker Desktopをインストールします。
公式サイトによるとWindows10 HOMEバージョンとそれ以外のバージョンで違いがあるそうです。
執筆時点ではどちらも同じ内容になっていると見受けられましたが、念のためバージョンにあった方のリンクからたどって「Docker Desktoop for Windows」をダウンロードしインストールしてください。
あと、Dockerが通信しようとする際、ファイアウォールに遮断されることがあるので、「Windows Defender ファイアウォールが新しいアプリをブロックしたときに通知を受け取る」設定をがOFFになっているようならONにしておきます。
それか、あまり行儀はよくないのかもしれませんが、Docker Desktopの稼働が確認できるまではファイアウォール自体をOFFにするのも方法かもしれません。
イメージのエクスポートとインポート
Docker Desktopを無事インストールすることができたらイメージをDebianからエクスポートしてWindowsにインポートします。
まず、DebianにあるDockerのカスタムイメージのエクスポートをします。前回作成した公式のhttpdイメージにPHP7.3とSSLを加えたカスタムイメージ「httpd-org:1.2」を例にしています。
エクスポートはdocker save イメージ名 -o 保存ファイル名とします。
エクスポートしたファイルをDocker DesktopをインストールしたWindows上のディスクにコピーします。ここではd:¥docker¥httpd-org1_2.imgにコピーしました。
インポートはdocker load イメージ名 -i 保存ファイル名とします。Docker DesktopをインストールするとPowerShellやコマンドプロンプトでdockerコマンドが実行できるようになっています。
PS> cd d:¥docker PS> docker load -i httpd-org1_2.img
WSL2の設定
ここからはDockerをWindows10上で動かす際の設定になります。
Windows10のDockerはWSL(Windows Subsystem for Linux)の上で動いています。
wsl --list --verboseでWindows Subsystemのディストリビューションの一覧を表示させてみると、そこにdocker-desktopが存在しています。
PS> wsl --list --verbose NAME STATE VERSION * docker-desktop-data Stopped 2 Ubuntu-20.04 Stopped 1 docker-desktop Stopped 2
正確には違うのかもしれませんが、このdocker-desktopはdocker-engineを動かすための仮想OSだととらえると、この後の話が理解しやすいと思います。
何をいいたいのかというと、前回Debian上にDocker環境を構築した際は-pオプションでポートを結びつけることで、ホストOSのアドレスを使ってコンテナのWebサービスを公開できましたが、WindowsのDocker-Desktop環境ではそれができません。-pオプションで実行した際のポートの結びつけがWindowsの内側にある仮想OSに対して実行されるからです。
WindowsのIPアドレスを使ってDockerコンテナを公開するには、Windowsのポートフォワード機能を利用して、Windowsとその中間OSを結び付ける必要があります。
たとえば、docker run -p 8000:80 httpd とした際のポートフォワードの流れは次のようになります。
大元のホストOSであるWindowsの8001番ポートに来た通信を、中間OSの8000番に転送します。中間OSは8000番に来た通信をDockerコンテナ内の80番に転送します。
わかりやすいようにポート番号を変えていますが、それぞれの環境でポートが他で使われていなければすべて80にしても成立します。
説明が長くなりましたが、Windows10でポートフォワードを設定をします。PowerShellかコマンドプロンプトから操作します、どちらの場合も管理者権限が必要です。
まず、中間OSのIPアドレスをipconfigで取得します。「イーサネット アダプター vEthernet (WSL):」というラベルがついているものが該当のアドレスです。これはDocker Desktopが起動していないと表示されません。
PS> ipconfig
イーサネット アダプター イーサネット:
...
IPv4 アドレス . . . . . . . . .: 192.168.1.1
...
イーサネット アダプター vEthernet (WSL):
...
IPv4 アドレス . . . . . . . . .: 172.19.50.1
...
GUNMA GIS GEEK:「WSL2で起動したサーバーに外部の端末からアクセスする」を参考にポートフォワードを設定をします。
書式は、「netsh.exe interface portproxy add v4tov4 listenaddress=WindowsのIP listenport=Windowsのポート connectaddress=中間OSのIP connectport=中間OSのポート」となりますが、先の例の場合だと次のようになります。
PS> netsh.exe interface portproxy add v4tov4 listenaddress=192.168.1.1 listenport=8001 connectaddress=172.19.50.1 connectport=8000
他、設定を確認するには「netsh.exe interface portproxy show v4tov4」、削除するには「netsh.exe interface portproxy add v4tov4 listenaddress=WindowsのIP listenport=Windowsのポート」とします。
中間OSのIPアドレスを固定する
さらに注意が必要なのは、デフォルトだと起動のたびに中間OSのIPアドレスが変わることです。WindowsのWSLについて回る問題のようで、github「microsoft/WSL」でも議論されていますが、執筆時点ではスマートな解決方法は存在していないようです。
その中で使えそうなものは、起動時に次のコマンドを実行するというものでした。このコマンドをファイル化しておき中間OSの仮想ネットワークが構築された後に実行すると、指定したアドレスでもアクセスが可能になるので起動のたびにポートフォワードの宛先を変更する必要がなくなります。
PS> netsh interface ip add address "vEthernet (WSL)" 192.168.12.34 255.255.255.0
PowerShellのスクリプトを組んでみました。これをWindowsのタスクでログイン時に管理者権限のpowershellで実行します。vEthernetインターフェースが立ち上がるのを待って、IPアドレスを追加するものです。ポートフォワードの設定は一度設定すれば消えないようになっているのでここで設定するIPアドレスに合わせて事前に設定しておきます。
wait-docker.ps
$ret;
$loopBreaker=100;
while ($ret -eq $null) {
$ret = get-netadapter 'vEthernet (WSL)'
if ($loopBreaker -lt 0) {
exit
} else {
echo $loopBreaker;
}
$loopBreaker--;
sleep 1
}
netsh interface ip add address "vEthernet (WSL)" 192.168.12.34 255.255.255.0
Docker Desktopが起動して「vEthernet (WSL)」インターフェースが作成されないと設定を入れられないので、それをwhileのループで待っています。初回時と、インターフェースがない時$retにはnullが入ります。中身の判断はしていませんが、インターフェースが作成されるとオブジェクトが$retに入りループを抜け、先ほど紹介したnetshのコマンドに移ります。
$loopBreakerの初期値とsleepの値で、インターフェースの起動を待うける時間のタイムアウトを決められます。この例だと100*1で、起動後約100秒間のうちに「vEthernet (WSL)」インターフェースが起動しなかったらあきらめて終了することになります。
ここで設定するアドレスは、ポートフォワードの設定と一致させる条件の他、実際のネットワークとして使っていないプライベートネットワークにする必要もあります。
参考にさせていただいたサイトの皆様、ありがとうございました。