前言

随着近几年微服务的兴起,加上云原生的蓬勃发展,大家已经对 Docker 不在陌生。Docker Desktop 是一个可以让大家在非 Linux 系统上,使用 Docker 进行开发调试的工具,非常容易上手,安装也非常简单。在苹果 m1 芯片出来后,Docker Desktop 第一时间发布了 beta 版本,也在前一段时间升级成了正式版。但是使用过程中还是有一些问题,例如运行一些 x86 的镜像会出错。最近 Docker 公司改变了收费模式,虽然说是不影响个人/小企业开发者,但谁叫咱是大公司,还是要看看替代方案的。

Podman 是什么

Podman 是一个 RedHat 公司发布的开源容器管理工具,初衷就是 docker 的替代品,在使用上与 Docker 的相似,但又有着很大的不同。它与 Docker 的最大区别是架构。Docker 是以 C/S 架构运行的,我们平时使用的 docker 命令是只一个命令行前端,它需要调用 dockerd 来完成实际的操作,而 dockerd 默认是一个有 root 权限的守护进程。Podman 不需要守护进程,直接通过 fork/exec 的形式启动容器,不需要 root 权限。

关于 Docker 和 Podman 的比较,可以进一步查看:

使用 Podman

在 mac 上使用 homebrew 安装 Podman 非常简单。同 Docker Desktop 一样,在 mac 上 podman 也需要虚拟机才能运行容器。podman 使用 qemu 创建 Linux 的虚拟机,所以需要一并安装,命令如下。由于国内网络问题,要有足够的耐心。

➜ brew install podman qemu

对于 m1 的芯片,目前 qemu 正式版本还没有支持苹果的 Hypervisor.framework 功能,如果想要 hvf 来加速虚拟机运行,就需要编译带 patch 的 qemu 版本。当然也可以用别人做好的,如下命令

# Apple silicon
➜ brew install simnalamburt/x/podman-apple-silicon

安装好了这两个工具后,需要先初始化虚拟机,才能继续使用。因为要下载 coreos 的镜像,又是漫长的等待。

➜ podman machine init
Downloading VM image: fedora-coreos-34.20210901.dev.0-qemu.aarch64.qcow2.xz [>---------------------------------------] 17.5MiB / 563.0MiB

初始化完成后,还需要打开虚拟机

➜ podman machine start
INFO[0000] waiting for clients...
INFO[0000] listening tcp://0.0.0.0:7777
INFO[0000] new connection from  to /var/folders/78/svdq05m94rq29_rt9z6p_n8m0000gn/T/podman/qemu_podman-machine-default.sock
Waiting for VM ...
...

关于虚拟机还有如下命令

# 查看虚拟机状态
➜ podman machine list
NAME                     VM TYPE     CREATED       LAST UP
podman-machine-default*  qemu        2 hours ago  Currently running

# 停止虚拟机
➜ podman machine stop

# 删除虚拟机
➜ podman machine rm

# 登录进虚拟机内部
➜ podman machine ssh
Connecting to vm podman-machine-default. To close connection, use `~.` or `exit`
Warning: Permanently added '[localhost]:51569' (ECDSA) to the list of known hosts.
Fedora CoreOS 34.20210901.dev.0
Tracker: https://github.com/coreos/fedora-coreos-tracker
Discuss: https://discussion.fedoraproject.org/c/server/coreos/

Last login: Mon Sep  6 13:28:43 2021 from 192.168.127.1
[core@localhost ~]$

如果启动没有报错,那么就可以使用 podman 替代 docker 命令了,下列常用的命令都可以运行。还可以通过 alias docker=podman 命令无缝替换。

➜ podman pull my-image:latest
➜ podman run my-image:latest --name my-container
➜ podman ps
➜ podman rm my-container

遇到的问题

在使用的过程中碰到两个诡异的问题

  1. podman machine init 执行失败,打印 Error: exit status 1 。如果忽略它,podman machine start 时会报如下日志
INFO[0000] waiting for clients...
INFO[0000] listening tcp://0.0.0.0:7777
INFO[0000] new connection from to /var/folders/78/svdq05m94rq29_rt9z6p_n8m0000gn/T/podman/qemu_podman-machine-default.sock
Waiting for VM ...
qemu-system-aarch64: -drive file=/Users/aaa/.local/share/containers/podman/machine/qemu/podman-machine-default_ovmf_vars.fd,if=pflash,format=raw: Could not open '/Users/aaa/.local/share/containers/podman/machine/qemu/podman-machine-default_ovmf_vars.fd': No such file or directory
Error: dial unix /var/folders/78/svdq05m94rq29_rt9z6p_n8m0000gn/T/podman/podman-machine-default_ready.sock: connect: connection refused
ERRO[0003] cannot receive packets from , disconnecting: cannot read size from socket: EOF
ERRO[0003] cannot read size from socket: EOF

查了一番之后发现,在初始化虚拟机的时候,用到了dd命令。这个命令苹果版的和gnu版的实现不一致,苹果版的bs参数只接收小写单位,而gnu版的只接收大写的单位。而我用的是 gnu 版本的,所以执行到这里一定会错。随把它从 PATH 中移出去,问题就解决了。

// [https://github.com/containers/podman/blob/main/pkg/machine/qemu/options_darwin_arm64.go#L26](https://github.com/containers/podman/blob/main/pkg/machine/qemu/options_darwin_arm64.go#L26)
func (v *MachineVM) prepare() error {
    ovmfDir := getOvmfDir(v.ImagePath, v.Name)
    cmd := []string{"dd", "if=/dev/zero", "conv=sync", "bs=1m", "count=64", "of=" + ovmfDir}
    return exec.Command(cmd[0], cmd[1:]...).Run()
}
  1. 运行 podman run nginx 报错 Error: short-name resolution enforced but cannot prompt without a TTY

这个错误看起来很奇怪,得先了解什么 short-name 才能理解。它指的是镜像的名字,镜像名字前没有域名的就是短名字。docker 碰到这种情况会在前面加上 docker.io/ ,但是 podman 没有这个操作。据说是因为安全原因,避免拉错镜像,所以给它做了二次确认的功能,但是因为使用的控制台不支持,所以没有展示出来,直接失败了。这个功能还可以修改配置文件控制是否开启。

但是奇怪的是,执行 centos 不会有这样的提示,执行 nginx 就会报错。这是因为 podman 对一些系统级的镜像有特殊对待,它维护了一个镜像名到镜像仓库的对应关系。详见:

  1. podman run -p 不能暴露端口
➜ podman run --rm -it --publish 8000:80 docker.io/library/nginx:latest &
➜ curl http://localhost:8000

curl: (7) Failed to connect to localhost port 8000: Connection refused

这是 podman 转发端口端口的 bug,预计 v3.3.2 会修复。

有一个 workaround 的办法是,在执行的时候加上--network bridge 。例如:

➜ podman run --rm -it --publish 8000:80 --network bridge docker.io/library/nginx:latest

https://github.com/containers/podman/issues/11396

还有个办法是修改配置文件,文件路径是~/.config/containers/containers.conf,在[containers] 内加上rootless_networking = "cni"

总结

在 M1 上体验 podman 的过程不够平顺,还没有覆盖全部功能就已经碰到不少问题。除了本身 bug 不少外,它使用的 Coreos 镜像还是 beta 版,也存在不稳定的可能性。但是,Podman 的代码比较规整,能比较快的阅读并定位问题,社区对问题的修复速度也很快。背后还有红帽这样的大公司支撑,还是很有希望达到一个稳定状态。

关于运行速度,感觉很赞,没有完全虚拟机的笨重感。

Podman 相对于 docker 还多了一些的功能,比如 pod 功能,还需要进一步深度体验。%