掛載 WSL2 的目錄到 Podman 中

TL;DR

cat <<-EOF | sudo tee /etc/systemd/system/mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
[Unit]
Description=WSL Instances

[Mount]
What=/
Where=/mnt/wsl/instances/${WSL_DISTRO_NAME}
Type=none
Options=defaults,bind,X-mount.mkdir

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
podman-remote run \
  -v /mnt/wsl/instances/${WSL_DISTRO_NAME}/`pwd`:/app \
  docker.io/library/busybox ls /app

前情提要

Podman[1] 是一個由 Redhat[2] 主推的 Container[3] 工具,如果有用過容器的讀者們應該對於與其類似的 Docker[4] 不陌生,Podman 就是一個這樣的容器管理工具。

與 Docker 不同的是,Podman 另外引入了 Pod[5] 的概念,以及具有預設支援 rootless 及去 daemon 化等特色。

Podman Desktop

Podman 團隊在各平臺上推出了桌面管理工具 —— Podman Desktop[6],類似於 Docker Desktop 提供視覺化的管理功能外,也能協助使用者安裝 Podman 至系統中並提供各種設定及擴充功能。

有興趣從 Docker 跳船的讀者可以參考 Podman Desktop 提供的跳船指南: https://podman-desktop.io/docs/migrating-from-docker

Windows 下的 Podman

在 Windows 上,Podman 會在 Windows 中安裝 podman.exe,協助你在 Host 的 Command line 中輕鬆存取 Podman,例如我們可以打開一個 PowerShell(或 CMD),執行 podman version,即可看到類似下方截圖的結果:

大家可以注意到,雖然我們可以在 Windows 下面執行 Podman 指令,但實際上的 Podman Engine 卻是執行在 Linux 上的,這是因為 Podman 目前只能管理 Linux Container,因此 Podman 決定將 Podman Engine 跑在 WSL2 中,如果有安裝 Windows Terminal 的話,可以從自動產生的下拉選單中發現一個新的 WSL2 的 Distro。

並且 Podman 會幫你做好與 Windows Host 的整合,例如可以直接掛載 Windows 的目錄或檔案進容器中,例如:

PS C:\Users\Davy\podman-test> echo "Hi Podman from Windows" > hello.txt
PS C:\Users\Davy\podman-test> podman run --rm -v .\hello.txt:/hello.txt docker.io/library/busybox cat /hello.txt
Trying to pull docker.io/library/busybox:latest...
Getting image source signatures
Copying blob sha256:ec562eabd705d25bfea8c8d79e4610775e375524af00552fe871d3338261563c
Copying config sha256:65ad0d468eb1c558bf7f4e64e790f586e9eda649ee9f130cd0e835b292bbc5ac
Writing manifest to image destination
��Hi Podman from Windows
PS C:\Users\Davy\podman-test>

(請忽略那兩個看不見的 BOM 符號……)

也就是說,我們可以隨意地編寫 PowerShell 腳本(或 CMD 批次檔)來操作 Podman!😏

WSL2 下的 Podman

那麼,通常會使用 Podman 的話,應該會對 WSL2[7] 也不陌生吧!

於是,我們可能會想,既然 Windows Host 都能如此順暢地與 WSL2 中的 Podman 整合了,那麼原本就在 WSL2 的其他 Distro 應該可以更好地整合吧……嗎?

很可惜,Podman Desktop 只丟了一個頁面[8]告訴你,你要自己整合。

整合方式大致就是在 WSL2 中安裝一個 Podman,並將 podman-machine-default 提供的 Podman socket 接上這個 Podman,方法如下:

安裝 Podman / Podman Remote

Podman Desktop 推薦安裝 podman-remote[9] 作為你在 WSL2 中使用的 Podman CLI,安裝方式也很簡單,到 containers/podman 的 Release 頁面中下載適合的 podman-remote 執行檔後放入 $PATH(例如 /usr/local/bin/usr/bin) 中,並將 podman alias 成 podman-remote 或直接 ln -s podman-remote podman

接下來就可以嘗試在 WSL2 中執行看看 podman(或 podman-remote)了!

$ podman-remote ps
Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM
Error: unable to connect to Podman socket: Get "http://d/v5.1.2/libpod/_ping": dial unix /mnt/wsl/podman/podman-machine-default.socket: connect: connection refused

咦?為什麼不會動呢? 啊,原來是忘記將 podman-remotepodman-machine-default 中的 Podman 連結在一起了,還需要下面的設定才行:

$ podman-remote system connection add --default \
  podman-machine-default-root \
  unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-root.sock
# usermod --append --groups 10 $(whoami)

設定完成之後重新登入 Shell(或重新打開一個新的 Terminal),再執行一次 podman-remote 看看:

$ podman-remote ps
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES
$ podman-remote run --rm quay.io/podman/hello
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob sha256:81df7ff16254ed9756e27c8de9ceb02a9568228fccadbf080f41cc5eb5118a44
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
!... Hello Podman World ...!

         .--"--.
       / -     - \
      / (O)   (O) \
   ~~~| -=(,Y,)=- |
    .---. /`  \   |~~
 ~/  o  o \~~~~.----. ~~
  | =(X)= |~  / (O (O) \
   ~~~~~~~  ~| =(Y_)=-  |
  ~~~~    ~~~|   U      |~~

Project:   https://github.com/containers/podman
Website:   https://podman.io
Desktop:   https://podman-desktop.io
Documents: https://docs.podman.io
YouTube:   https://youtube.com/@Podman
X/Twitter: @Podman_io
Mastodon:  @Podman_io@fosstodon.org
$

如此一來就可以在 WSL2 中操作與 Windows 摸到的同一個 Podman 了!

掛載 WSL2 中的路徑至 Podman

就算按照步驟將 WSL2 與 Podman 整合起來之後,你還會發現一些小小小問題 —— 例如掛載不到 WSL2 中的目錄……

~/podman-example$ echo "Hello from WSL2" > hello.txt
~/podman-example$ cat `pwd`/hello.txt
Hello from WSL2
~/podman-example$ podman-remote run --rm \
  -v `pwd`/hello.txt:/hello.txt \
  docker.io/library/busybox \
  cat /hello.txt
Error: statfs /home/davy/podman-example/hello.txt: no such file or directory
~/podman-example$

咦? 奇怪? 為什麼他會跟我說這個測試用的檔案不存在,明明直接 cat 會動的呀……

而且在 Docker 裡面如果這樣子做的話是會動的,為什麼在 Podman 中不行呢?

原來是在 Docker Desktop 中,對於掛載 WSL2 Distro 中的路徑時,Docker 會轉而向一個輔助工具求助,這個工具會協助 Docker 存取其他 WSL2 中的資料[10]。因為 WSL2 中各個 Distro 的資料是各自獨立的,所以 Docker/Podman 的 WSL2 預設沒有辦法直接存取其他 Distro 的資料,此時會導致掛載路徑時會只能在自己的 WSL2 中搜尋,我們可以來做個實驗:

我們嘗試將 /home 掛載到 Podman container 中,在此之前我們先來確認在兩個 WSL2 中的檔案狀態:

user@podman-machine-default:~ $ ls /home/
user

davy@Ubuntu:~ $ ls /home/
davy

接下來我們開始嘗試掛載:

davy@Ubuntu:~ $ podman-remote run --rm -v /home/:/the-home docker.io/library/busybox ls /the-home
user
davy@Ubuntu:~ $

可以看到,podman-remotepodman-machine-default 中的 /home 掛載到了容器中,而非我們期望的 WSL2 Distro 中的 /home

也就是說,--volume/-v 參數,不論在那個 WSL2 Distro,對應到的都是 podman-machine-default 中的路徑。

那麼,我們該如何掛載 WSL2 中其他 Distro 的路徑呢?

WSL2 的神奇共用目錄

在 WSL2 中,有一個神祕的路徑是全 WSL2 Distro 共享的,那就是 /mnt/wsl,Podman 也是藉由將 Podman Engine 的 socket 放在這個地方來讓其他 Distro 可以存取[11]。WSL2 將這個神奇目錄用在存放與全 Distro 共享的設定等資料,大家可以嘗試下面的指令:

$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 20 Jul 17 22:11 /etc/resolv.conf -> /mnt/wsl/resolv.conf
$ ls -l /mnt/wsl/resolv.conf
-rw-r--r-- 1 root root 213 Jul 13 09:12 /mnt/wsl/resolv.conf
$ cat /mnt/wsl/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 10.255.255.254
search local
$

可以看到其實所有 Distro 的 /etc/resolv.conf[12],都是指向 /mnt/wsl/resolv.conf,而這個檔案會被 WSL2 自動地更新,WSL2 藉由這個方式來讓所有的 Distro 共用相同的設定。

於是,我們可以透過這個神奇的小地方來把我們要分享的東西放在這裡,再讓 podman-machine-default 來這裡存取即可!

解決方案

既然知道了方法,那麼我們就往下來執行,最簡單的方式就是利用 mount 指令將 WSL2 各個 Distro 的根目錄分享到 /mnt/wsl 下面,如此一來 podman-machine-default 就可以存取到裡面的內容了![13]

$ echo $WSL_DISTRO_NAME
Ubuntu
# mount -t bind -o defaults,bind,X-mount.mkdir / /mnt/wsl/instances/${WSL_DISTRO_NAME}
$ ls /mnt/wsl/instances/
Ubuntu
$ ls /mnt/wsl/instances/Ubuntu/home/
davy
$ podman-remote run --rm \
  -v /mnt/wsl/instances/${WSL_DISTRO_NAME}`pwd`/hello.txt:/hello.txt \
  docker.io/library/busybox \
  cat /hello.txt
Hello from WSL2
$

終於成功啦!🎉

systemd.mount

目前主流版本的 WSL2 引擎都已經支援 systemd[14][15] 了,於是我們可以再多做一點事情 —— 自動掛載。

由於前面我們是手動使用 mount 指令來進行掛載,在每次 WSL2 重新啓動的時候都會復原,因此我們希望在 WSL2 啓動的時候就幫我們把這個 mountpoint 掛載上去,我們可以透過 systemd.mount 來幫我們完成這個任務。

/etc/systemd/system/ 下建立一個新的檔案 mnt-wsl-instances-${WSL_DISTRO_NAME}.mount,內容如下(此例中以 Ubuntu 取代 ${WSL_DISTRO_NAME},請讀者自行依照自己的環境作更換):

[Unit]
Description=WSL Instances

[Mount]
What=/
Where=/mnt/wsl/instances/${WSL_DISTRO_NAME}
Type=none
Options=defaults,bind,X-mount.mkdir

[Install]
WantedBy=multi-user.target

接下來執行下列指令來啓用這個 mount:

# umount /mnt/wsl/instances/${WSL_DISTRO_NAME}
$ ls /mnt/wsl/instances/${WSL_DISTRO_NAME}

# systemctl daemon-reload
# systemctl enable --now mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
Created symlink /etc/systemd/system/multi-user.target.wants/mnt-wsl-instances-Ubuntu.mount → /etc/systemd/system/mnt-wsl-instances-Ubuntu.mount.
$ ls /mnt/wsl/instances/Ubuntu/home/
davy
$

這次終於是大功告成了!🎊

結語

其實原本不是要寫 Podman 相關的內容的…… 但剛好遇到要使用 Podman 的情景就想說順便把這件事情寫下來好了,原本是打算之後再跟大家分享的,現在提前先分享給大家 :)

Podman 在環境整合這方面真的還沒有做的 Docker 好,但還算在能搞懂原理的情況下自行 Hack,使用上或許沒有這麼方便,不過我個人還是覺得 Podman 還算是好用且方便的。


  1. 詳見官方網站: https://podman.io/ ↩︎

  2. 亦稱紅帽,主力開發及維護開源生態的軟體公司,其發行的 Redhat Enterprise Linux 常用於各種生產環境,詳見其官方網站: https://www.redhat.com/ ↩︎

  3. 此處專指 Linux Container,可參見維基百科《Containerization》條目 ↩︎

  4. 容器管理工具,詳見其官方網站: https://www.docker.com/ ↩︎

  5. 先出現與 Kubernetes 中的概念,為一組運算資源的最小管理單位,包含網路、掛載點等,詳見 K8s 的概念說明 ↩︎

  6. Podman 的桌面管理工具,詳見官方網站: https://podman-desktop.io/ ↩︎

  7. Windows Subsystem Linux 2,一個深度整合 Linux 執行環境於 Windows 中的解決方案,設計說明請參閱: https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2 ↩︎

  8. 請見: https://podman-desktop.io/docs/podman/accessing-podman-from-another-wsl-instance ↩︎

  9. 僅有遠端操作 Podman Engine 功能的閹割版 Podman CLI,使用說明詳見: https://docs.podman.io/en/latest/markdown/podman-remote.1.html ↩︎

  10. 原文節錄 "To make bind-mounts a seamless experience, we introduced a docker api proxy similar to the one we use for enabling bind mounts of Windows files that translates paths relative to the user distro into a path to the same file accessible from the LinuxKit container.",全文請見 https://www.docker.com/blog/new-docker-desktop-wsl2-backend/ ↩︎

  11. Podman 將 socket 放在 /mnt/wsl/podm an-sockets/podman-machine-default/podman-root.sock 來與其他 Distro 共用 ↩︎

  12. Linux 中用來設定 DNS 資訊的設定檔,詳見 man 5 resolv.conf 或這裡的存檔: https://man7.org/linux/man-pages/man5/resolv.conf.5.html ↩︎

  13. $WSL_DISTRO_NAME —— 此一環境變數會記錄當下環境的 WSL2 Distro 名稱 ↩︎

  14. 在 WSL v0.67.6+ 中可以啓用 systemd,詳見官方部落格: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/ ↩︎

  15. "systemd is a suite of basic building blocks for a Linux system. It provides a system and service manager that runs as PID 1 and starts the rest of the system.",節錄自官方網站: https://systemd.io/ ↩︎