在 macOS 中使用 Podman

Podman 是一個無守護程序與 Docker 命令兼容的下一代 Linux 容器工具,該項目由 RedHat 主導,其他的細節可以參考 Podman 使用指南,本文的重點不是這個。

Podman 一直以來只能跑在 Linux 系統上,macOSWindows 只能通過 CLI 遠程連接 Podman 的 API 來管理容器。事實上 Docker 也不支持 macOS 和 Windows,但 Docker 針對 Windows 和 macOS 推出了專門的客戶端,客戶端裏面集成了虛擬化相關的設置,通過嵌套一層虛擬化來支持 Docker。對於 Podman 來說,想要在 macOS 上運行也只能通過虛擬化來實現,網上也有不少方案,基本上都是通過 Virtualbox 來實現,都不太優雅。本文將介紹一種相對更優雅的方案,雖然不是很完美,但我已經盡力做到接近完美了。。

HyperKit 介紹

HyperKit 是一個具有 hyperisor 能力的輕量級虛擬化工具集,包含了基於 xhyve(The BSD Hypervisor)的完整 hypervisor。HyperKit 設計成上層組件諸如 VPNKitDataKit 的接口。xhyve 是 基於 bhyve 的 Mac OS X 移植版本,而 bhyve 又是 FreeBSD 下的虛擬化技術。。。

我們知道,Docker 在 Linux 上利用了 Linux 原生支持的容器方式實現資源和環境的隔離,直接利用宿主內核,性能接近原生。然而,在 macOS 上卻仍然需要虛擬化的技術。早期的 Docker 乾脆直接在開源的 VirtualBox 中構建虛擬機,性能低下。後期的 Docker 基於輕量化的虛擬化框架 HyperKit 開發,據說性能得到很大提升。

本文將介紹如何通過 HyperKit 來使用 Podman。方法也很簡單,先通過 Hyperkit 創建一個輕量級虛擬機,然後在虛擬機中安裝 Podman,並開啓 remote API,最後在本地通過 CLI 連接虛擬機中的 Podman。這和 macOS 中的 Docker 實現原理是一樣的,只不過 Podman 是沒有 Daemon 的,與 Docker 相比可以節省不少資源。

  1. 安裝 HyperKit

你可以自己下載源代碼編譯 HyperKit,但我不建議這麼做,不同的 macOS 版本會遇到各種各樣的錯誤。我這裏推薦兩種超級簡單的方法:

  1. 直接通過安裝 Docker 來獲得 HyperKit,因爲 Docker Desktop on Mac 就是基於 HyperKit 實現的,所以安裝 Docker Desktop on Mac 就能夠獲得完整的 HyperKit 運行環境。整個過程會非常順暢和簡單。安裝完 Docker 之後可以永遠不用打開 Docker,直接使用 HyperKit 就好。或者你可以直接卸載 Docker,卸載之前先把 hyperkit 二進制文件備份出來,因爲卸載 Docker 也會刪掉 hyperkit 二進制文件。

  2. 直接通過安裝 Multipass 來獲得 HyperKit。Multipass 是 Canonical 公司(Ubuntu)開發的基於不同操作系統內建原生 Hypervisor 實現的工作站。由於 Windows(Hyper-V),macOS(hyperkit)和 Linux(KVM)都原生支持 hypervisor,這樣通過 multipass shell 命令就能夠在一個 shell 中實現創建運行 Ubuntu 虛擬機。在 macOS 平臺,默認的後端是 hyperkit,需要 macOS Yosemite (10.10.3) 以上版本並且需要安裝在 2010 以後生產的 Mac 設備。安裝方法很簡單:

    $ brew cask install multipass

    安裝好了之後可以在 /Library/Application Support/com.canonical.multipass/bin/ 目錄下找到 hyperkit 二進制文件。

  3. 創建虛擬機


你可以直接通過 hyperkit 來創建虛擬機,但參數比較複雜,有興趣的自己研究吧。我推薦直接通過 multipass 來創建,命令特別簡單:

$ multipass launch -c 2 -d 10G -m 2G -n podman

第一次啓動虛擬機的時候會去拉去鏡像,國內網速可能會很慢。

查看已經啓動的虛擬機:

$ multipass list
Name                    State             IPv4             Image
podman                  Running           192.168.64.2     Ubuntu 20.04 LTS

進入虛擬機:

$ multipass shell podman
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-52-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Nov  8 19:30:29 CST 2020

  System load:  0.0                Processes:               119
  Usage of /:   13.4% of 11.46GB   Users logged in:         0
  Memory usage: 11%                IPv4 address for enp0s2: 192.168.64.2
  Swap usage:   0%


0 updates can be installed immediately.
0 of these updates are security updates.


Last login: Sun Nov  8 17:38:31 2020 from 192.168.64.1
ubuntu@podman:~$
  1. 安裝 Podman

在虛擬機中安裝 Podman:

ubuntu@podman:~$ . /etc/os-release
ubuntu@podman:~$ echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | ubuntu@podman:~$ sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
ubuntu@podman:~$ curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | sudo apt-key add -
ubuntu@podman:~$ sudo apt-get update
ubuntu@podman:~$ sudo apt-get -y upgrade
ubuntu@podman:~$ sudo apt-get -y install podman
  1. 建立 Podman Socket

Podman 依賴於 systemd 的 socket activation 特性。假設 Daemon B 依賴於 Daemon A,那麼它就必須等到 Daemon A 完成啓動後才能啓動。socket activation的思想就是:Daemon B 啓動時其實並不需要 Daemon A 真正運行起來, 它只需要 Daemon A 建立的 socket 處於 listen 狀態就 OK 了。而這個 socket 不必由 Daemon A 建立, 而是由 systemd 在系統初始化時就建立。當 Daemon B 發起啓動時發起連接,systemd 再將 Daemon A 啓動,當 Daemon A 啓動後,再將 socket 歸還給 Daemon A。

Podman 會通過 podman.socket 先創建一個處於監聽狀態的 socket 文件 /run/podman/podman.sock,當有進程向該 socket 發起連接時,systemd 會啓動同名的 service:podman.service,以接管該 socket。先看看 podman.socket 和 podman.service 長啥樣:

ubuntu@podman:~$ sudo systemctl cat podman.socket
# /lib/systemd/system/podman.socket
[Unit]
Description=Podman API Socket
Documentation=man:podman-system-service(1)

[Socket]
ListenStream=%t/podman/podman.sock
SocketMode=0660

[Install]
WantedBy=sockets.target

ubuntu@podman:~$ sudo systemctl cat podman.service
# /lib/systemd/system/podman.service
[Unit]
Description=Podman API Service
Requires=podman.socket
After=podman.socket
Documentation=man:podman-system-service(1)
StartLimitIntervalSec=0

[Service]
Type=notify
KillMode=process
ExecStart=/usr/bin/podman system service

設置開機自啓 podman.socket,並立即啓動:

ubuntu@podman:~$ sudo systemctl enable podman.socket --now

確認 socket 是否正處於監聽狀態:

ubuntu@podman:~$ podman --remote info
host:
  arch: amd64
  buildahVersion: 1.16.1
  cgroupManager: systemd
  cgroupVersion: v1
  conmon:
    package: 'conmon: /usr/libexec/podman/conmon'
    path: /usr/libexec/podman/conmon
    version: 'conmon version 2.0.20, commit: '
  cpus: 2
  ...
  1. 客戶端 CLI 設置

接下來所有的設置,如不作特殊說明,都在 macOS 本地終端執行。

Podman 遠程連接依賴 SSH,所以需要設置免密登錄,先生成祕鑰文件:

$ ssh-keygen -t rsa   # 一路回車到底

然後將本地的公鑰 ~/.ssh/id_rsa.pub 追加到虛擬機的 /root/.ssh/authorized_keys 文件中。

安裝 Podman CLI:

添加遠程連接:

$ podman system connection add ubuntu --identity ~/.ssh/id_rsa ssh://root@192.168.64.2/run/podman/podman.sock

查看已經建立的連接:

$ podman system connection list
Name     Identity                 URI
podman*  /Users/Ryan/.ssh/id_rsa  ssh://root@192.168.64.2:22/run/podman/podman.sock

由於這是第一個連接,所以被直接設置爲默認連接(podman 後面加了 *)。

測試遠程連接是否可用:

$ podman ps
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES

$ podman pull nginx:alpine
Trying to pull docker.io/library/nginx:alpine...
Getting image source signatures
Copying blob sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964
Copying blob sha256:9dd8e8e549988a3e2c521f27f805b7a03d909d185bb01cdb4a4029e5a6702919
Copying blob sha256:85defa007a8b33f817a5113210cca4aca6681b721d4b44dc94928c265959d7d5
Copying blob sha256:f2dc206a393cd74df3fea6d4c1d3cefe209979e8dbcceb4893ec9eadcc10bc14
Copying blob sha256:0ca72de6f95718a4bd36e45f03fffa98e53819be7e75cb8cd1bcb0705b845939
Copying config sha256:e5dcd7aa4b5e5d2df8152b9e58aba32a05edd9b269816f5d8b7ced535743d16c
Writing manifest to image destination
Storing signatures
e5dcd7aa4b5e5d2df8152b9e58aba32a05edd9b269816f5d8b7ced535743d16c

$ podman image ls
REPOSITORY                TAG     IMAGE ID      CREATED      SIZE
docker.io/library/nginx   alpine  e5dcd7aa4b5e  2 days ago   23.3 MB

現在我們就可以直接在本地用 podman 愉快地玩耍了!

如果你建立了多個連接,可用使用 –connection 參數指定遠程連接,或者使用 podman system connection default <NAME> 來設置默認的遠程連接。

最後,我們來看看 hyperkit 的內存佔用:

物理內存只佔用了 921M,如果你覺得這個內存佔用很多,不妨去對比下 Docker Desktop 的內存佔用。

總結

本文介紹了在 macOS 中使用 podman 的方法,通過 HyperKit 創建 Ubuntu 虛擬機運行 Podman,並建立 Podman Socket,然後客戶端通過 SSH 連接服務端的 Socket,以實現通過遠程連接來管理容器。

------- 他日江湖相逢 再當杯酒言歡 -------

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://fuckcloudnative.io/posts/use-podman-in-macos/