セキュアスカイによる安全なWebサイト運営のためのセキュリティ情報

エンジニアブログ
  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

WSL2, Windows Terminal, Docker Desktop for Windows のインストールメモ

WSL2, Windows Terminal, Docker Desktop for Windows のインストールメモ

こんにちは、SSTでWeb脆弱性診断用のツール(スキャンツール)開発をしている坂本(Twitter, GitHub)です。

最近、WSL2やDockerを触る機会がありました。
今まで触れていなかったので、これを機会に WSL2 の Ubuntu をインストールしたり、Windows Terminal を使ってみたり、 Docker Desktop for Windows (WSL2 バックエンド版) をインストールするなど挑戦してみました。
その時のメモを共有したいと思います。

重要: 2021年8月時点(正確には 8月16日~27日頃)の情報に基づいています。
今後のバージョンアップによっては本記事で紹介している内容が非推奨となる可能性があります。
本記事の内容を参考に作業する場合、参照先の記事を注意深く確認し、最新情報に基づいて作業内容を決定するようにしてください。

環境:

  • Windows 10 Pro (64bit)
    • バージョン 21H1 (OSビルド 19043.1165)
  • WSL2 : Ubuntu 20.04.3 LTS
  • Docker Desktop for Windows: 3.6.0

WSL2 と Ubuntu のインストールメモ

Microsoft のドキュメントに従い、手動インストールした。

基本的なWSL操作の紹介

wsl コマンドなどでの基本的な操作を紹介する。

Linux ディストリビューションの一覧表示(wsl –list)

WSLでインストールしたLinuxディストリビューションは wsl --list で一覧表示できる。
-v(--verbose) オプションを付けると起動/停止状態やWSLのバージョンも表示できる。

>wsl --list
Linux 用 Windows サブシステム ディストリビューション:
Ubuntu (既定)

>wsl --list -v
  NAME                   STATE           VERSION
* Ubuntu                 Running         2

Linux ディストリビューションのコマンド実行 (wsl -e)

-e command-line で Linux ディストリビューション中のコマンドを実行できる。
-d ディストリビューション名 で使用するディストリビューションを指定する。

>wsl -d Ubuntu -e cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
(...)

Linux ディストリビューションからホストのWindowsファイルシステムにアクセス

ホストのWindowsに接続されているドライブレター (C: , D: , E: , ... ) が /mnt/(ドライブレター) としてマウントされている。

/etc/os-release を Windows 上の C:\work\os-release にコピーする例:

>wsl -d Ubuntu -e /bin/cp /etc/os-release /mnt/c/work/os-release

>type C:\work\os-release
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
(...)

エクスプローラからWSLのファイルシステムを開く

Windows エクスプローラから \\wsl$ を開くと、Linuxディストリビューションのファイルシステムにアクセスできる。
\\wsl$\Ubuntu のように Linux ディストリビューション名を付与しても同様に開ける。

Linuxのターミナル環境から powershell.exe /c start. をしても、同様に開くことができる。*1

WSL の Ubuntu を再起動 (wsl –shutdown)

起動中の Linux ディストリビューションを終了するには wsl --shutdown コマンドを使う。
-d ディストリビューション名 で終了するディストリビューションを指定する。

>wsl -d Ubuntu --shutdown

WSL のディスクスペースと実体ファイル

Ubuntu を例に取ると、 df -h コマンドを実行してみるとルートパーティションとして /dev/sdb が 250GB のサイズで確保されている。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb        251G  1.5G  237G   1% /
(...)

ただしこれは Hyper-V ベースの仮想化マシン用のディスクであり、使った分だけ増えていく形式になっている。
そのため実際に250GBのディスクスペースを最初から消費するわけでは無い。

Microsoft Store 経由でインストールしたLinuxディストリビューションの実体ファイルを探すには、PowerShellから Get-AppxPackage -Name "*Ubuntu*" などで対応するLinuxディストリビューションのパッケージ情報を抽出し、PackageFullName 属性を調べる。

PS> Get-AppxPackage -Name "*Ubuntu*"
(...)
PackageFullName   : CanonicalGroupLimited.UbuntuonWindows_2004.2021.222.0_x64__79rhkp1fndgsc
(...)

C:\Users\<user-name>\AppData\Local\Packages\<PackageFullName属性>\LocalState に実体としての .vhd ファイルが存在する。

 

Ubuntu の vhd ファイルの実体

Ubuntu の vhd ファイルの実体

vhdのディスクスペースを圧縮(縮小)

WSLのvhdにおいて、基本的には使った分だけ実体の vhd ファイルのサイズは増えていく一方となるらしい。
そのため docker 等でたくさんイメージをDLしたりしていると、たとえ docker コマンド上でイメージを削除したとしても「容れ物」となるvhdは増えたままとなり、Windowsホストのディスク容量を圧迫する。

vhd の最適化や圧縮には、今回軽く調査した範囲では diskpart コマンドによる圧縮(compress) と optimize-vhd コマンドによる最適化があるらしい。

もし空き容量が不安になったら、上記資料を参考に対応してみたい。

WSL2 Ubuntu 上のプロセスはどう見えるか

WSL2 は Hyper-V アーキテクチャ上で動作するものの、「Hyper-V マネージャー」から「仮想マシン」としては見えない。
実際に「Hyper-V マネージャー」を起動すると、Ubuntu に相当する仮想マシンは見当たらなかった。

 

WSL2 の Ubuntu は Hyper-V マネージャー上の「仮想マシン」としては登録されない

WSL2 の Ubuntu は Hyper-V マネージャー上の「仮想マシン」としては登録されない

 

しかしVMであることは確かであり、WSL2向けにカスタマイズした Linux カーネルを使った軽量なVMとして動作しているようだ。

どれほど軽量なVMとなっているのか、Ubuntu上のbashからプロセス状況を見てみる。
wsl -d Ubuntu -e /bin/bash で起動したターミナルから、psコマンドと /proc を確認する。

$ ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0    896   580 ?        Sl   15:23   0:00 /init
root         6  0.0  0.0    896    84 ?        Ss   15:23   0:00 /init
root         7  0.0  0.0    896    84 ?        R    15:23   0:00 /init
msakamo+     8  0.0  0.0  10000  5140 pts/0    Ss   15:23   0:00 /bin/bash
msakamo+    52  0.0  0.0  10620  3296 pts/0    R+   15:28   0:00 ps auxww

$ ls -lF /proc
total 0
dr-xr-xr-x  9 root      root                    0 Aug 27 15:23 1/
dr-xr-xr-x  9 msakamoto msakamoto               0 Aug 27 15:47 57/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:24 6/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:24 7/
dr-xr-xr-x  9 msakamoto msakamoto               0 Aug 27 15:23 8/

見事に init/bin/bash しか動いていない。
Windowsホスト側のプロセスからはどう見えているか、Sysinternals の Process Explorer で確認してみたところ wsl および wslhost プロセスのセットとなっており、initやbashプロセスはWindows 上からは見えない。

 

Windows ホスト側からは wsl および wslhost プロセスのセットとして見える。

Windows ホスト側からは wsl および wslhost プロセスのセットとして見える。

 

ここでもう一つ wsl -d Ubuntu -e /bin/bash を起動する。
その中で ps コマンドと /proc の下を表示してみる。

$ ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0    896   580 ?        Sl   15:23   0:00 /init
root         6  0.0  0.0    896    84 ?        Ss   15:23   0:00 /init
root         7  0.0  0.0    896    84 ?        S    15:23   0:00 /init
msakamo+     8  0.0  0.0  10132  5148 pts/0    Ss+  15:23   0:00 /bin/bash
root        68  0.0  0.0    896    84 ?        Ss   15:54   0:00 /init
root        69  0.0  0.0    896    84 ?        R    15:54   0:00 /init
msakamo+    70  0.6  0.0  10000  4872 pts/1    Ss   15:54   0:00 /bin/bash
msakamo+    76  0.0  0.0  10620  3276 pts/1    R+   15:54   0:00 ps auxww

$ ls -lF /proc
total 0
dr-xr-xr-x  9 root      root                    0 Aug 27 15:23 1/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:24 6/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:54 68/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:54 69/
dr-xr-xr-x  9 root      root                    0 Aug 27 15:24 7/
dr-xr-xr-x  9 msakamoto msakamoto               0 Aug 27 15:54 70/
dr-xr-xr-x  9 msakamoto msakamoto               0 Aug 27 15:55 77/
dr-xr-xr-x  9 msakamoto msakamoto               0 Aug 27 15:23 8/

新規に起動したセッションの分だけ、initプロセスと /bin/bash プロセスが増えている。
また /proc の下には最初に起動したプロセスIDも見えている。
このことから、少なくとも同じファイルシステムをマウントしていることが分かる。

2つのbash上で uptime コマンドを実行し、kernel が認識している起動時間を確認する。

[最初に起動したbash]
$ uptime
 15:59:20 up 36 min,  0 users,  load average: 0.00, 0.00, 0.00

[2番目に起動したbash]
$ uptime
 15:59:16 up 35 min,  0 users,  load average: 0.00, 0.00, 0.00

どちらも35-36分稼働していることが分かる。
コマンド自体は15:59頃に実行しており、ここで先程の /proc の一覧のタイムスタンプを思い出すと、最初に起動したbashからのinitプロセスID相当が 15:23頃のタイムスタンプを表示しており、まさに35-36分程度経過していることが分かる。

試しに片方のbash上で VM内のファイルシステムに適当なテキストファイルを保存し、もう片方のbashから読み込めるか確認する。

[最初に起動したbash]
$ echo "hello" > hello.txt

[2番目に起動したbash]
$ cat hello.txt
hello

→ 問題なく読み込めており、仮想ディスクについても同じものを参照していることが分かる。

上記から、2つの wsl -d Ubuntu -e /bin/bash が単一のVMインスタンス中で動作していることが推測できる。

なお Windows ホスト側のプロセス状況を確認すると、wsl + wslhost のセットがもう1セット増えていた。

wslコマンドによるコマンド実行は以下のような仕組みになっているのではなかろうか?

  1. wslコマンドから wslhost コマンドに引き継ぎ。
  2. wslhost コマンドから Hyper-V を経由して、まだ仮想VMが起動していなければ起動する。
  3. wslhost コマンドから何らかの内部チャネルを経由して、WSL2用のkernelにプロセス起動を依頼。
  4. WSL2用のkernelプロセスがVM内でinitからのプロセスを起動し、標準入出力を wslhost コマンドに接続。
  5. wsl --shutdown コマンドが実行されたら wslhost 経由で Hyper-V の仮想VMインスタンスも終了。

その他、Ubuntu中のシステム情報を備忘録として以下にメモする。

$ uname -a
Linux (win側マシン名) 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ cat /proc/cmdline
initrd=\initrd.img panic=-1 pty.legacy_count=0 nr_cpus=4

$ free -m
              total        used        free      shared  buff/cache   available
Mem:          12621          67       12441           0         111       12350
Swap:          4096           0        4096

$ lsblk
NAME MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda    8:0    0  256G  0 disk
sdb    8:16   0  256G  0 disk /

$ mount
/dev/sdb on / type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)
tmpfs on /mnt/wsl type tmpfs (rw,relatime)
tools on /init type 9p (ro,relatime,dirsync,aname=tools;fmask=022,loose,access=client,trans=fd,rfd=6,wfd=6)
none on /dev type devtmpfs (rw,nosuid,relatime,size=6460024k,nr_inodes=1615006,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,noatime,gid=5,mode=620,ptmxmode=000)
(...)
C:\ on /mnt/c type 9p (rw,noatime,dirsync,aname=drvfs;path=C:\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8)

WSL2 のネットワークアクセス

デフォルトでWSL2 上のLinuxからインターネットにアクセス可能。

WSL2 上のLinuxとWindowsホストの間では、以下のように相互にネットワークアクセスが可能となっている。

  • Linux から Windows
    • Linux ディストリビューション中の /etc/resolv.conf 中の nameserver が Windows ホストを指しているので、そこに接続できる。
    • ip r で表示されるデフォルトゲートウェイのIPも同様に Windowsホストを指しているので、そこでもOK.
  • Windows から Linux
    • Linuxディストリビューション上で listen しているポート番号に対しては、Windowsホスト上から localhost 宛で接続できる。
    • ip a 等のコマンドで eth0 のIPアドレスを調べ、そこに接続することも可能。
  • 注意点
    • ip aip r で表示されるIPアドレスは、Windowsホストの再起動等のタイミングで変わる可能性がある。*2

Hyper-V のネットワーク設定との関連

WSL2は Hyper-V 上で動作するため、ネットワーク設定についても Hyper-V の仕組みに従う。

今回 WSL2 を有効化した結果、Hyper-Vの仮想スイッチマネージャー上に「WSL」という内部仮想スイッチが作成された。

 

「WSL」内部仮想スイッチ

「WSL」内部仮想スイッチ

 

Win10側には「WSL」内部仮想スイッチに接続されるネットワークアダプタが追加された。
ipconfig で確認すると IPv4アドレスとして 172.22.96.1 が設定されていた。*3

 

「WSL」内部仮想スイッチに接続されたWin10側ネットワークアダプタ

「WSL」内部仮想スイッチに接続されたWin10側ネットワークアダプタ

 

Hyper-Vにおける仮想スイッチの仕組みについては、プライベートで調べた資料を公開しているのでそちらを参照のこと。

一方のUbuntu 側は eth0172.22.110.112/20 が設定されていた。
デフォルトゲートウェイは 172.22.96.1 であり、これは Win10 側の「WSL」内部仮想スイッチに接続されたアダプタのIPv4と一致する。
/etc/resolv.conf もWSLにより自動生成されており、 nameserver としてWin10側のアダプタを参照している。

$ ip r
default via 172.22.96.1 dev eth0
172.22.96.0/20 dev eth0 proto kernel scope link src 172.22.110.112

$ ip a
(...)
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:a2:80:d6 brd ff:ff:ff:ff:ff:ff
    inet 172.22.110.112/20 brd 172.22.111.255 scope global eth0
       valid_lft forever preferred_lft forever
(...)

$ cat /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.22.96.1

今回追加されていた「WSL」内部仮想スイッチは、Hyper-V上のVM群を相互接続できるL2レイヤーの仮想スイッチとなっている。
これにより、WindowsとWSL2上のLinuxディストリビューションが相互に接続できる。

本来のHyper-Vの内部仮想スイッチはあくまでもスイッチ機能しかなく、DHCPやNAT機能は無い。
Hyper-Vから利用する際はスイッチに接続したアダプタにIPアドレスやゲートウェイ、DNSを手動で設定し、さらにインターネットに接続するためには WinNAT を利用したNAT設定が必須となる。
今回はそうした設定を一切行っておらず、管理者権限で起動した PowerShell 上から Get-NetNat コマンドを実行しても空の表示となり、WinNATを使っていないことを確認している。
にも関わらず、WSL2上のLinuxでは Windows ホストをデフォルトゲートウェイ/DNSに設定し、インターネットにアクセス可能となっている。
なぜそうなっているのか?

Hyper-Vには特殊な仮想スイッチとして “Default Switch” があり、簡易的なDHCPとNAT機能が組み込まれている。
“Default” と冠するだけあり、VMをこれに接続しておけばひとまずインターネットアクセス可能な状態となる。
これは HNS: HostNetworkingService が提供しており、PowerShell 上から Get-HnsNetwork でHNSが管理しているネットワーク情報を表示できる。
実行してみると、以下の通り “Default Swtich” に加え “WSL” の情報も表示された。

PS C:\WINDOWS\system32> Get-HnsNetwork

(...)
MacPools               : {@{EndMacAddress=00-15-5D-5B-EF-FF; StartMacAddress=00-15-5D-5B-E0-00}}
MaxConcurrentEndpoints : 0
Name                   : Default Switch
NatName                : ICS19A96A40-31C3-4723-8352-98A76B939D08
(...)
Subnets                : {@{AdditionalParams=; AddressPrefix=172.17.128.0/20; Flags=0; GatewayAddress=172.17.128.1; Health=; ID=97F742AF-3A4F-43AA-8095-CC030D
                         F320BA; IpSubnets=System.Object[]; ObjectType=5; Policies=System.Object[]; State=0}}
(...)
SwitchName             : Default Switch
(...)
Type                   : ICS
(...)

DNSServerList          : 172.22.96.1
GatewayMac             : 00-15-5D-A2-81-11
IsolateSwitch          : True
(...)
MacPools               : {@{EndMacAddress=00-15-5D-A2-8F-FF; StartMacAddress=00-15-5D-A2-80-00}}
Name                   : WSL
NatName                : ICS1E5A8747-2B37-47D2-9EDF-F1DA612016A4
(...)
Subnets                : {@{AdditionalParams=; AddressPrefix=172.22.96.0/20; Flags=0; GatewayAddress=172.22.96.1; Health=; ID=15AAF7D3-E010-4EC1-9C07-D7BF7FE0
                         D2C6; IpSubnets=System.Object[]; ObjectType=5; Policies=System.Object[]; State=0}}
(...)
Type                   : ICS
(...)

表示される項目に若干の差異はあるが、”Default Switch”, “WSL” ともにHNSが管理していることを確認できた。
これにより WSL2 に対しても簡易的なDHCP/DNS/デフォルトゲートウェイ/NAT機能を提供しているものと推測できる。 *4

なおHNSが管理する “Default Swtich” については、IPアドレスレンジが再起動等のタイミングで時々変わることが報告されている。
よって “WSL” についても同様にIPアドレスが変わることが想定され、実際 ASCII.jp:Windows Subsystem for Linux 2におけるネットワークの改良 (1/2) でも以下の通りIPアドレスが変わると紹介している。

こうした構成であるため、Build 18945より前のWSL2では、WSL2側のネットワークサービスに接続する際には、WSL2の仮想ネットワークインターフェース(eth0)のIPアドレスを指定する必要があった。しかし、仮想ネットワークに接続する仮想イーサーネットインターフェースのIPアドレスは、再起動するたびに毎回違ったIPアドレスが割り当てられていた。このため、接続の指定をスクリプトなどに記述しておくことが困難だった。

このため、Webアプリ開発時のアクセスなどで仮想マシンのIPアドレスを固定しておきたい場合は “Default Switch” は少々使いづらい点もあった。
そのような場合は自分で内部仮想スイッチを作成した上で、WinNATでNAT設定を行い Win10 ホストをインターネットアクセス用のデフォルトゲートウェイとし、IPアドレスやDNS設定を手動設定することができる。

WSL2に戻ると、WSL2の Hyper-V 仮想環境についてはユーザからネットワークアダプタを切り替える手段が提供されていない。
そのためHNSが管理する “WSL” 内部仮想スイッチに接続し、再起動等のタイミングでIPアドレスが変わることを許容するしかない。

面白いことに、Windows上からは localhost 経由で WSL2 のLinuxがlistenしているポート番号に接続できる。
これについては前掲のASCII.jpの記事に解説がある が、 Linux上で listen しているポートについて wslhost.exe 側でもWindowsホスト上でlistenするような連携機能が組み込まれており、一種のトンネリングが形成されている。

実際に WSL2 の Ubuntu 上で netcat をインストールし、TCP:8081番をlistenしてみる。

$ sudo apt install netcat
$ echo -e "HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello World" | nc -l 8081

Win10側で 管理者権限で PowerShell またはコマンドプロンプトを起動し、 netstat -p tcp -anb コマンドによりLISTENしているポート番号とそのアプリケーション名を表示してみる。

PS C:\WINDOWS\system32> netstat -p tcp -anb

アクティブな接続

  プロトコル  ローカル アドレス      外部アドレス           状態
(...)
  TCP         127.0.0.1:8081         0.0.0.0:0              LISTENING
 [wslhost.exe]

wslhost.exe が 8081 でlistenしていることを確認できた。

参考資料

Windows Terminal 入門メモ

これを機に Windows Terminal に入門。
既にたくさんのTIPSやハウツー記事が公開されていたので、大いに助けられた。
Microsoftの公式ドキュメントも一通り書かれていて、参考になる。

設定に苦戦したり工夫したところ:

  • 設定画面から「JSONファイルを開く」で settings.json を編集・保存しても、設定画面側の「保存」ボタンをクリックするとそちらの設定で上書き保存されてしまう。
    • 最初、settings.json をメモ帳で保存した後、それだけで設定が反映されるのかよく分からず設定画面側の「保存」もクリックして Windows Terminal を再起動 → もう一度 settings.json を開いたら、メモ帳で編集した設定が消えてしまっていて、何が起きたのか分からなかった。
    • settings.json を手で編集するなら、設定画面側の「保存」ボタンはクリックしちゃダメそう。
  • フォントの設定方法が、MSの公式ドキュメントを見ると font キーを作ってその中に face, size, weight のキー・値ペアで設定するような書き方をしていたが、それでは認識されなかった。
  • タブ操作やスクロールのショートカットキーをカスタマイズした。
    • keys が複数形で配列形式も受け付けてるので、試しにデフォルトキー + カスタマイズの2つを配列で指定してみたら、配列の場合は2つ以上の要素は受け付けてないみたいなエラーになってしまい、泣く泣く文字列の1パターンのみの登録にfallbackした。
  • Git for Windows の bash 環境もプロファイルに追加した。
  • フォントについてはプログラミングフォント 白源 (はくげん/HackGen) より HackGenNerd を使ってみた。

settings.json 抜粋:

{
(...)
    "actions":
    [
        {
            "command":
            {
                "action": "copy",
                "singleLine": false
            },
            "keys": "ctrl+insert"
        },
        {
            "command": "find",
            "keys": "ctrl+shift+f"
        },
        {
            "command": "paste",
            "keys": "shift+insert"
        },
        {
            "command": "closeTab",
            "keys": "ctrl+w"
        },
        {
            "command": "nextTab",
            "keys": "ctrl+pagedown"
        },
        {
            "command": "prevTab",
            "keys": "ctrl+pageup"
        },
        {
            "command": "duplicateTab",
            "keys": "alt+f2"
        },
        {
            "command": "scrollUpPage",
            "keys": "shift+pgup"
        },
        {
            "command": "scrollDownPage",
            "keys": "shift+pgdn"
        },
        {
            "command":
            {
                "action": "splitPane",
                "split": "auto",
                "splitMode": "duplicate"
            },
            "keys": "alt+shift+d"
        }
    ],
    "copyFormatting": "none",
    "copyOnSelect": false,
    "defaultProfile": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
    "showTabsInTitlebar": false,
    "initialCols": 200,
    "initialRows": 50,
    "profiles":
    {
        "defaults":
        {
            "historySize": 100000,
            "useAcrylic": true,
            "acrylicOpacity": 0.9,
            "fontFace": "HackGenNerd",
            "fontSize": 12,
            "fontWeight": "normal"
        },
        "list":
        [
(...)
            {
                "guid": "{56c49cd9-2e4e-48ca-a8c1-2090ac9c02c0}",
                "name": "Git Bash",
                "commandline": "C:\\Program Files\\Git\\bin\\bash.exe",
                "startingDirectory": "%USERPROFILE%",
                "icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
                "cursorShape": "filledBox",
                "hidden": false
            },
(...)
        ]
    },
    "schemes":
    [
(...)
    ],
    "trimBlockSelection": true
}

Docker Desktop for Windows (WSL2 backend) のインストールメモ

公式のドキュメントに従いインストールした。

インストール後はチュートリアルに従い、サンプルリポジトリの clone や Dockerfile のビルドとコンテナ起動など一通り動作確認した。

WSL2バックエンドやLinuxディストリビューションとの連携については、以下の通りデフォルトのままで進めてみた。

 

"Use the WSL 2 based engine" にチェック

“Use the WSL 2 based engine” にチェック

 

WSLとのIntegrationについては、デフォルトのみチェックして、Ubuntuとの連携はOFFにしておく。

WSLとのIntegrationについては、デフォルトのみチェックして、Ubuntuとの連携はOFFにしておく。

 

WSL2 backendの仕組み

Dockerはクライアント-サーバー形式で動作し、ローカル環境ではUNIX socketやHTTPでのREST APIで通信する。
これをWSL2でどう実現しているか、以下のblogに解説があった。

2019年10月の記事で、プレビュー版でのフィードバックを受けての設計レベルからの改善内容が紹介されている。
その中でWSL2でのアーキテクチャを理解するために、その前身となる Hyper-V を使ったアーキテクチャが紹介されているので、日本語で簡単にまとめてみる。

  1. Hyper-V バックエンドでは、LinuxKitベース *5 の仮想マシンを起動する。
  2. Docker や Kubernetes のサービス制御やエラー診断、ログ収集などの仕組みは別途 ISO ファイルにパッケージングしている : “docker-desktop” iso
  3. Docker Engine や Kubernetes の実行ファイル本体は “version-pack” iso にパッケージングしている。
    • Docker Enterprise Edition ではこれを入れ替えるなどして複数の Docker Engine/Kubernetes のランタイムバージョンを切り替え可能としていたらしい。
    • Docker Community Edition では単一バージョンのみサポートを想定。
  4. コンテナイメージや設定ファイルなどの保存用に、VHD(仮想ディスク)をattachしてVMを起動。
    • 上記のISOイメージを接続して内部からマウントし、ランタイム実行バイナリを起動する仕組みとなっている。
  5. Windowsクライアントとの通信用に、UNIXソケットを Windows の Named Pipes に変換するproxyを組み込む。

ISOイメージを2種類用意してそれぞれマウントし、ランタイムをロードするのは冗長に思える。
しかしパッケージングの観点に立つと、ISOイメージをまるごと入れ替えることでバージョンアップが可能となる。
バージョンアップの制御を Hyper-V 内部と切り離せるメリットを考慮して、このようなアーキテクチャになったものと推測する。

これをWSL2で実現するため、以下のようなアーキテクチャを実現した。

  1. WSL2バックエンドでは、Linux Kernel を取り除いた軽量な LinuxKit ベースのディストリビューションを使う : “docker-desktop” ディストリビューションで、”bootstraping” とも呼ばれる。
  2. WSL2のディストリビューションでは /mnt 経由でWindowsホストのファイルシステムにアクセスできるので、これを経由して ISO イメージを参照する。
    • Hyper-Vと同様、以下の二種類のISOイメージをロードする。
    • “docker-desktop” iso : Hyper-V版から Linux Kernel を取り除き、WSL2のディストリビューションに沿った構成にカスタムした内容。
    • “version-pack” iso : Docker/Kubernetesランタイムバイナリなので、Hyper-V版と同じ内容。
  3. 当時は WSL2 ディストリビューションのVMに複数のVHDをアタッチできなかったため、”docker-desktop-data” というデータストア専用のWSL2ディストリビューションを追加。
    • /mnt/wsl 経由で他のディストリビューションのファイルシステムを参照できたため、これを使って “docker-desktop” 環境から “docker-desktop-data” 環境のディスク領域をコンテナ/イメージ/ボリューム/設定ファイル等の保存に使う。
  4. UNIXソケットを Windows の Named Pipes に変換する Proxy は引き続き使用。
  5. 他のWSL2ディストリビューションと連携するため、API Proxy を組み込むようにした。
    • これによりコンテナから他のWSL2ディストリビューション上のファイルと bind-mount などでシームレスに連携できるようになった。

Hyper-V版との構成の違いは、大きく3つに整理できる。

  1. WSL2側で Linux Kernel を用意してくれたので、Docker側で準備する必要が無くなった。
  2. データストア用に別のWSL2ディストリビューション(VM)が追加された。
  3. 連携のため、他のWSL2ディストリビューションの構成に API Proxy が組み込まれる。

WSL2側で何が起こっているのか

WSL2で何が起こっているか確認し、アーキテクチャが実際にどのように動いているのか簡単に触りだけ見ていく。

wsl --list --all -v を確認すると、以下のように docker-desktop-datadocker-desktop というディストリビューションが追加されていた。
名前から docker-desktop-data が Docker社の記事にあるデータストア用のディストリビューションであり、 docker-desktop がISOイメージが接続されたランタイム環境であることが推測できる。

>wsl --list --all -v
  NAME                   STATE           VERSION
* Ubuntu                 Running         2
  docker-desktop-data    Running         2
  docker-desktop         Running         2

エクスプローラから \\wsl$ にアクセスすると、docker-desktopdocker-desktop-data のファイルシステムにもアクセスできる。

 

エクスプローラから docker-desktop や docker-desktop-data のファイルシステムにアクセスしている様子

エクスプローラから docker-desktop や docker-desktop-data のファイルシステムにアクセスしている様子

 

仮想VMにアタッチされている実体としてのディスク(vhd)については C:\Users\<user-name>\AppData\Local\Docker\wsl 以下に data\ext4.vhdxdistro\ext4.vhdx がある。
名前から data\ext4.vhdxdocker-desktop-data 側で distro\ext4.vhdxdocker-desktop 側と推測した。

データストア専用のためか、docker-desktop-data の中にはユーザがログインできるような shell が見当たらなかった。
docker-desktop には /bin/bash があったため、wsl -d docker-desktop -e /bin/bash で仮想VMの中に入り、psコマンドを実行してみる。

>wsl -d docker-desktop -e /bin/bash
bash-4.4# ps auxw
PID   USER     TIME  COMMAND
    1 root      0:00 /init
   14 root      0:00 /init
   15 root      0:00 /init
   16 root      0:03 wsl-bootstrap run --base-image /mnt/host/c/Program Files/Docker/Docker/resources/wsl/docker-for-wsl.iso --version-pack /mnt/host/c/Program Files/Docker/Docker/resources/docker.tar --cli-iso /mnt/host/c/Program Files/Docker/Docker/resources/wsl/docker-wsl-cli.iso
   17 root      0:00 /init
   18 root      0:00 /init
   24 root      0:00 /usr/bin/vpnkit-bridge --disable ssh-auth,osxfs-data,transfused,filesystem-event,filesystem-test,http-proxy-control --pid-file=/run/vpnkit-bridge.pid --addr=stdio guest
   38 root      0:00 unshare -muinpf --propagation=unchanged --kill-child=SIGTERM /usr/local/bin/wsl-bootstrap jump
   39 root      0:00 /sbin/init
   66 root      0:00 /usr/bin/memlogd -fd-log 3 -fd-query 4 -max-lines 5000 -max-line-len 1024
  195 root      0:03 /usr/bin/containerd
  221 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace services.linuxkit -id binfmt -address /run/containerd/containerd.sock
  270 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace services.linuxkit -id diagnose -address /run/containerd/containerd.sock
  292 root      0:00 /usr/local/bin/diagnosticsd
  317 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace services.linuxkit -id dns-forwarder -address /run/containerd/containerd.sock
  345 root      0:00 /usr/bin/dns-forwarder
  361 root      0:00 [sh]
  373 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace services.linuxkit -id docker -address /run/containerd/containerd.sock
  403 root      0:00 /usr/bin/docker-init /usr/bin/entrypoint.sh
  419 root      0:00 {entrypoint.sh} /bin/sh /usr/bin/entrypoint.sh
(...)
  557 root      0:00 nginx: master process nginx -g daemon off;
  581 root      0:00 /usr/sbin/rpc.idmapd
(...)
  589 root      0:01 /usr/local/bin/containerd --config /etc/containerd/containerd.toml
(...)
 1146 root      0:00 /init
 1147 root      0:00 /init
 1148 root      0:00 /bin/bash
 1149 root      0:00 ps auxw

→ docker 関連のプロセスが大量に起動していた。
wsl-bootstrap というプロセスのコマンドラインオプションに /mnt/host/c/... 経由で Windows ホスト側のISOイメージやtarファイルが指定されている。
ファイル名的に --base-image が “docker-desktop” 用のISOファイルで、 --version-pack が “version-pack” 用のISOをtarでアーカイブしたものと推測できる。

Process Explorer で Docker Desktop 関連のプロセスツリーを確認すると、wslプロセスを複数起動して、複雑な連携をしている様子を伺えた。

 

Docker Desktop によるwslプロセス群

Docker Desktop によるwslプロセス群

docker-desktop の中に戻って、試しに lsblk コマンドを実行してみると loopback デバイスをマウントしていることが分かる。

bash-4.4# lsblk
NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0   7:0    0   383M  1 loop /tmp/docker-desktop-root/mnt/host/wsl/docker-desktop/cli-tools
loop1   7:1    0 367.1M  1 loop /tmp/docker-desktop-root
sda     8:0    0   256G  0 disk
sdb     8:16   0   256G  0 disk /
sdc     8:32   0   256G  0 disk /tmp/docker-desktop-root/mnt/host/wsl/docker-desktop-data/isocache
sdd     8:48   0   256G  0 disk

bash-4.4# /sbin/losetup --list
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE                                                                                                       DIO LOG-SEC
/dev/loop1         0      0         1  1 /mnt/host/wsl/docker-desktop-data/isocache/f3332944d6665638348aaac7d05873ccc24b946bd44d7ef0776955f352e95023.iso   0     512
/dev/loop0         0      0         1  1 /mnt/host/wsl/docker-desktop-data/isocache/197ffa57d0c847a97a722c81d9d8fc88a609c4241cd3198d9c8cf1d13483a05d.iso   0     512

/mnt/host/wsl/docker-desktop-data/ 中のISOファイル、つまり docker-desktop-data のWSL2インスタンス内にあるファイルシステムを、docker-desktop インスタンス側でわざわざマウントしている。
これらのISOイメージの内容については調べきれていないが、 wsl-bootstrap のコマンドラインオプション --base-image--cli-iso で指定したISOイメージのキャッシュと推測した。

Ubuntuへの連携設定は無効化しているものの、以下の通り Ubuntuディストリビューション側にも mountポイントやプロセスが追加され、/usr/bin/docker コマンドが追加されている様子を確認した。

>wsl -d Ubuntu -e /bin/bash

$ which docker
/usr/bin/docker

$ ls -l /usr/bin/docker
lrwxrwxrwx 1 root root 48 Aug 27 16:24 /usr/bin/docker -> /mnt/wsl/docker-desktop/cli-tools/usr/bin/docker

$ lsblk
NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0   7:0    0   383M  1 loop /mnt/wsl/docker-desktop/cli-tools
loop1   7:1    0 367.1M  1 loop
sda     8:0    0   256G  0 disk
sdb     8:16   0   256G  0 disk /mnt/wsl/docker-desktop/docker-desktop-proxy
sdc     8:32   0   256G  0 disk /mnt/wsl/docker-desktop-data/isocache
sdd     8:48   0   256G  0 disk /

$ mount
(...)
/dev/sdc on /mnt/wsl/docker-desktop-data/isocache type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)
none on /mnt/wsl/docker-desktop/shared-sockets/guest-services type tmpfs (rw,nosuid,noexec,noatime,mode=755)
none on /mnt/wsl/docker-desktop/shared-sockets/host-services type tmpfs (rw,nosuid,noexec,noatime,mode=755)
/dev/sdb on /mnt/wsl/docker-desktop/docker-desktop-proxy type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)
/mnt/host/wsl/docker-desktop-data/isocache/197ffa57d0c847a97a722c81d9d8fc88a609c4241cd3198d9c8cf1d13483a05d.iso on /mnt/wsl/docker-desktop/cli-tools type iso9660 (ro,relatime,nojoliet,check=s,map=n,blocksize=2048)
(...)
tmpfs on /mnt/wsl/docker-desktop-data/tarcache/entries/docker.tar/e2aa150008fd9bf3010195a9d2d1e76501050083b8718cafd1e8c8433f427f11/containers/services/docker/tmp type tmpfs (rw,relatime)
overlay on /mnt/wsl/docker-desktop-data/tarcache/entries/docker.tar/e2aa150008fd9bf3010195a9d2d1e76501050083b8718cafd1e8c8433f427f11/containers/services/docker/rootfs type overlay (rw,relatime,lowerdir=/containers/services/docker/lower,upperdir=/containers/services/docker/tmp/upper,workdir=/containers/services/docker/tmp/work)

$ ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0    904   576 ?        Sl   16:24   0:00 /init
root        44  0.0  0.0    896    80 ?        Ss   16:24   0:00 /init
root        45  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        47  0.0  0.0    896    80 ?        S    16:24   0:00 /init
root        48  0.0  0.2 1243004 34196 pts/1   Ssl+ 16:24   0:00 /mnt/wsl/docker-desktop/docker-desktop-proxy --distro-name Ubuntu --docker-desktop-root /mnt/wsl/docker-desktop
root        54  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        63  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        65  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        67  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        74  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        76  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        78  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        85  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        87  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        89  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        96  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root        98  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root       101  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root       103  0.0  0.0      0     0 ?        Z    16:24   0:00 [init] <defunct>
root       105  0.0  0.0    896    80 ?        S    16:24   0:00 /init
msakamo+   106  0.0  0.3 764248 47124 pts/0    Ssl+ 16:24   0:00 docker serve --address unix:///home/msakamoto/.docker/run/docker-cli-api.sock

“/var/lib/docker” はどこにあるのか?

Docker Desktop for Windows をインストール後、チュートリアルを進めた。
その中で image や volume 等が作成されたので、inspectコマンドで詳細を確認してみたところ、以下のように /var/lib/docker ディレクトリ以下に実体ファイルがあるように表示された。

> docker inspect 0b3ecb52054c
[
    {
        "Id": "sha256:0b3ecb52054c317d686a87a48d0167883b664ca3832cd0c2af427e6bb9260443",
(...)
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/q3ho3atfgu2rim7j9k53w5fna/diff:/var/lib/docker/overlay2/5ec3dc34677911cf5c4ebc831b3297f683baf1be15dd928c9e39b429cccb3494/diff:/var/lib/docker/overlay2/229e3010669cb902e81594f36f8019dda230b5da1a288687dbbd747d05a1a197/diff:/var/lib/docker/overlay2/649c550842d220103c19968c58b95807f19b1cd750a78254e0facd281a2cd428/diff:/var/lib/docker/overlay2/631e3af9ca673c8b91f9feb066cc9f86b22dbdd28329d7cb6531748f76f8b1ea/diff:/var/lib/docker/overlay2/6e7a2a826f7b913b6305898ed3ddcb568ed1e960902ad21eeefce51b0aa243e9/diff:/var/lib/docker/overlay2/e495b56fca8882cbd4b0bbe9097ef15719ba788a1791ef8e7d7898ce70a0f2cc/diff",
                "MergedDir": "/var/lib/docker/overlay2/x9294xqyo266hyp9aeml2liti/merged",
                "UpperDir": "/var/lib/docker/overlay2/x9294xqyo266hyp9aeml2liti/diff",
                "WorkDir": "/var/lib/docker/overlay2/x9294xqyo266hyp9aeml2liti/work"
            },
            "Name": "overlay2"
        },
(...)
    }
]

> docker volume inspect 2391cdc604a4c83c04dfe1b2778c9c35855370f2cead3b9646b52e41bb495e53
[
    {
        "CreatedAt": "2021-08-26T08:04:09Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/2391cdc604a4c83c04dfe1b2778c9c35855370f2cead3b9646b52e41bb495e53/_data",
        "Name": "2391cdc604a4c83c04dfe1b2778c9c35855370f2cead3b9646b52e41bb495e53",
        "Options": null,
        "Scope": "local"
    }
]

ところが \\wsl$\docker-desktop にも \\wsl$\docker-desktop-data にも、さらには \\wsl$\Ubuntu にも、つまりどのWSLディストリビューションのファイルシステムにも /var/lib/docker ディレクトリが存在しなかった。
一体 /var/lib/docker はどこにあるのか?

結論から書くと \\wsl$\docker-desktop-data\version-pack-data\community\docker/var/lib/docker の中身と一致していた。

前述の通り WSL2 バックエンド版では docker-desktop-data ディストリビューションのVHDをデータストアとして使用している。
保存先としては確かにそこで合ってはいるが、 一体どこでパス名が変換されているのか、執筆時点では調査しきれず、不明点として残っている。

この問題についてはWSL側での issue をはじめとする以下の参考資料に教えてもらった。

“version pack” はどうなったのか

Hyper-V/WSL2 バックエンド版両方のアーキテクチャで考慮していた “version-pack” は Docker Enterprise/Community Edition間での差別化機能として解説されていた。
ところが 2019年11月に Docker Enterprise と関連ビジネスと Mirantis売却してしまう。

Docker社の公式ブログでも 記事に設定されたtagとして残っており、キャッシュとして記事のリンクが残っている。
ただし記事自体は削除されたのか、リンクをクリックしてもブログのTOPページにジャンプするだけとなっている。

まとめ

  1. WSL2 を導入し、UbuntuディストリビューションをインストールしてWSL2ならではの特徴を調べた。
  2. Windows Terminal をインストールし、使いやすいようカスタマイズした。
  3. Docker Desktop for Windows をインストールし、WSL2バックエンドの仕組みについて調査した。

WSL2バックエンドの Docker Desktop は構成が複雑なため、このまま使い続けても大丈夫か正直悩むところはある。
今後機会があれば WSL2 の Ubuntu に直接 Docker をインストールしたり、VirtualBox上でCentOS系の仮想VMを立ち上げてその中で Docker をインストールするなどいくつかバリエーションを試してみて、最も安定して使用できる組み合わせを検証してみたい。

その他参考資料

*1:Linux環境のPATH環境変数を見てみると “/mnt/c” に読み替えられて Windows 側のPATH設定が引き継がれていた。なお cmd.exe だと UNC 形式のパスに対応していないためエクスプローラで開けない。powsershell.exeならOK。

*2:一度Windowsホストを起動すれば、シャットダウン/再起動までの間は変わらない

*3:執筆時の筆者環境でそうなっていただけであり、上述の通りWindowsホストの再起動等のタイミングで一定レンジの中でランダムに変わる。

*4: 実際の設定状況から、Win10ホストをDNS/デフォルトゲートウェイとしてDHCPで応答するような設定になっているものと思われる。

*5: コンテナサービスに必要な機能に絞り込んだ Linux サブシステム。概要については https://www.publickey1.jp/blog/17/dockerlinuxkitlinux_subsystemdockercon_2017.html など参照。

  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

この記事の筆者

筆者:SST-blog