4. Docker替代工具
在早期的 Kubernetes 中,Docker 是运行容器的唯一选择,后来随着矛盾增加和对 Docker 公司的围剿,陆续出现了rkt、Containerd、CRI-O,并规划逐渐废弃对 Docker 的支持。
Kubernetes 1.19(2020年8月)中 Docker 标记为废弃,Kubernetes 1.20(2020年12月)Docker 正式废弃,如果集群要继续使用 Docker,需要部署 cri-dockerd 作为接入层。
Docker 本身有比较重的历史包袱,并不是运行 Pod 的最佳选择,而如今运行 Container 方面,也有了一些竞争对手,就是这里要谈到的 Containerd 与 Podman。
如果常用 Containerd 作为容器运行时,那就选择 nerdctl + Containerd 替代 Docker;如果是红帽工具链的重度使用者,以 CRI-O 作为容器运行时,那么可以选择 Podman(以及 Skopeo 和 Buildah),两者的依赖项并不冲突,有需要的话可以同时安装。
下面是在 64 位 X86 的 Debian 12 系统上使用 nerdctl + Containerd 与 Podman 替代日常 Docker 使用的示例。
Containerd 脱胎于 Docker 的 libcontainer,后期独立出来并捐献给了 CNCF,而 nerdctl 则是为 Containerd 开发的兼容 Docker 命令行的工具。这里使用 v1.7.6 版本完整包,它携带了 Containerd v1.7.16 与 BuildKit v0.12.5,由于我经常需要构建多架构镜像,因此还需要单独下载 BuildKit 的完整包,下面是操作步骤:
安装iptables
apt install -y iptables
下载并安装nerdctl
wget -c https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-full-1.7.6-linux-amd64.tar.gz
tar Cxzvvf /usr/local nerdctl-full-1.7.6-linux-amd64.tar.gz
下载并安装buildkit
wget -c wget https://github.com/moby/buildkit/releases/download/v0.12.5/buildkit-v0.12.5.linux-amd64.tar.gz
tar Cxzvvf /usr/local buildkit-v0.12.5.linux-amd64.tar.gz
启动containerd与buildkit守护进程
systemctl enable --now containerd
systemctl enable --now buildkit
最后验证下 containerd 服务是否正常
➜ ~ nerdctl info
Client:
Namespace: default
Debug Mode: false
Server:
Server Version: v1.7.16
Storage Driver: overlayfs
Logging Driver: json-file
Cgroup Driver: systemd
Cgroup Version: 2
...
nerdctl 兼容 Docker 命令行,我们可以直接创建一个软链接来使用,这样就不需要修改以前常用的脚本。
pushd /usr/local/bin && ln -sfv nerdctl docker && popd
nerdctl 提供了与 Docker 使用习惯一致的命令行来管理容器。
- 运行容器:
nerdctl run -d --restart unless-stopped --name nginx -p 8080:80 nginx:1.20-alpine - 列出所有容器:
nerdctl ps -a - 查看容器日志:
nerdctl logs -f --tail=100 nginx - 登录容器:
nerdctl exec -it nginx sh
nerdctl 默认使用 default 命名空间执行命令,如果我们正在使用 Containerd 作为集群的容器运行时,就可以指定 k8s.io 命名空间来管理由 Kubelet 创建出的容器,如下:
➜ ~ nerdctl namespace ls
NAME CONTAINERS IMAGES VOLUMES LABELS
k8s.io 94 96 0
moby 0 0 0
moby_history 0 0 0
➜ ~ nerdctl -n k8s.io container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
009113c869f2 docker.io/rancher/mirrored-pause:3.6 "/pause" 2 months ago Up k8s://harbor/harbor-portal-77b89cd6b4-fxmhn
00ef51f80d3b docker.io/linuxserver/bookstack:23.10.4 "/init" 2 months ago Up k8s://app/bookstack-0/bookstack
01bcb120bf75 docker.io/goharbor/harbor-portal:v2.5.3 "nginx -g daemon off;" 2 months ago Up k8s://harbor/harbor-portal-77b89cd6b4-fxmhn/portal
0fdf81cf75ae docker.io/rancher/mirrored-pause:3.6 "/pause" 2 months ago Up k8s://kube-system/traefik-7c865f98b8-fblbg
...
Containerd 与 Docker 在镜像管理方面最显著的区别是:
- 默认使用 OCI 镜像格式
- 支持多架构镜像
Containerd 本身不支持构建镜像,执行 nerdctl build 时实际上是访问 buildkitd 提交构建镜像的任务,与执行 docker buildx 命令在 buildkitd 容器中构建镜像类似。
对于单架构镜像而言,拉取镜像、构建镜像、推送镜像等操作,nerdctl 与 Docker 使用习惯一致,下面以一个 alpine 基础镜像为例,Dockerfile 如下:
ARG VERSION
FROM alpine:$VERSION
ENV TZ=Asia/Shanghai
RUN apk add --no-cache bash curl wget git make htop tzdata
CMD [ "bash" ]#
拉取 alpine:3.18,构建自定义镜像 wbuntu/alpine:3.18 并推送,如下:
➜ alpine git:(master) ✗ nerdctl pull alpine:3.18
docker.io/library/alpine:3.18: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:1875c923b73448b558132e7d4a44b815d078779ed7a73f76209c6372de95ea8d: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:d9a39933bee4ccb6d934b7b5632cdf8c42658f3cecc5029681338f397142af6e: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:8fd7cac70a4aaabb31d459b03e3534b14341c31429b678b650039ac45a606cfc: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 4.0 s total: 2.1 Ki (541.0 B/s)
➜ alpine git:(master) ✗ nerdctl build --build-arg VERSION=3.18 -t wbuntu/alpine:3.18 .
[+] Building 1.6s (7/7) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 164B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.18 1.3s
=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/alpine:3.18@sha256:1875c923b73448b558132e7d4a44b815d078779ed7a73f76209c6372de95ea8d 0.0s
=> => resolve docker.io/library/alpine:3.18@sha256:1875c923b73448b558132e7d4a44b815d078779ed7a73f76209c6372de95ea8d 0.0s
=> CACHED [2/2] RUN apk add --no-cache bash curl wget git make htop tzdata 0.0s
=> exporting to docker image format 0.2s
=> => exporting layers 0.0s
=> => exporting manifest sha256:94db150bc131b4e69ecdb90d610295c668b955cc50b0aa2ff9372a59106101de 0.0s
=> => exporting config sha256:a0f4cabbaf6c53a3d3a01606b1297ca990df75da42b72d67186173118199395c 0.0s
=> => sending tarball 0.2s
unpacking docker.io/wbuntu/alpine:3.18 (sha256:94db150bc131b4e69ecdb90d610295c668b955cc50b0aa2ff9372a59106101de)...
Loaded image: docker.io/wbuntu/alpine:3.18
➜ alpine git:(master) ✗ nerdctl push wbuntu/alpine:3.18
而多架构镜像有一些区别,在 pull 和 push 命令行中可以添加 --all-platforms 来拉取和推送所有架构的镜像,build 命令中可以为 --platform 指定多个架构,下面以我常用的一个 gost 镜像为例,buildkit 默认提供了一些环境变量用于区分架构,Dockerfile 如下:
FROM wbuntu/alpine:3.18
ARG VERSION
ARG TARGETARCH
COPY download.sh /usr/bin/download.sh
RUN /usr/bin/download.sh $VERSION $TARGETARCH
CMD ["/usr/bin/gost","-C","/etc/gost/config.json"]
使用 wbuntu/alpine:3.18 作为基础镜像,构建自定义镜像 wbuntu/gost:v2.11.5 并推送,如下:
➜ gost git:(master) ✗ nerdctl build --build-arg VERSION=v2.11.5 --platform linux/amd64,linux/arm64 -t wbuntu/gost:v2.11.5 .
[+] Building 5.4s (11/12)
[+] Building 5.5s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 225B 0.0s
=> [linux/arm64 internal] load metadata for docker.io/wbuntu/alpine:3.18 0.7s
=> [linux/amd64 internal] load metadata for docker.io/wbuntu/alpine:3.18 0.7s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [linux/amd64 1/3] FROM docker.io/wbuntu/alpine:3.18@sha256:f59c3e7dac85edc5ba6ca7e8b1e7f83a5547325d7c19e9a06acfa2715053a950 0.0s
=> => resolve docker.io/wbuntu/alpine:3.18@sha256:f59c3e7dac85edc5ba6ca7e8b1e7f83a5547325d7c19e9a06acfa2715053a950 0.0s
=> [linux/arm64 1/3] FROM docker.io/wbuntu/alpine:3.18@sha256:f59c3e7dac85edc5ba6ca7e8b1e7f83a5547325d7c19e9a06acfa2715053a950 0.0s
=> => resolve docker.io/wbuntu/alpine:3.18@sha256:f59c3e7dac85edc5ba6ca7e8b1e7f83a5547325d7c19e9a06acfa2715053a950 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 33B 0.0s
=> [linux/arm64 2/3] COPY download.sh /usr/bin/download.sh 0.0s
=> [linux/amd64 2/3] COPY download.sh /usr/bin/download.sh 0.0s
=> [linux/arm64 3/3] RUN /usr/bin/download.sh v2.11.5 arm64 3.5s
=> [linux/amd64 3/3] RUN /usr/bin/download.sh v2.11.5 amd64 1.9s
=> exporting to oci image format 1.2s
=> => exporting layers 0.6s
=> => exporting manifest sha256:cbc82e262f8c94507130b07afa6c0de9f9ce2b1f6e7865b1be7b7e4ce3dcd2d4 0.0s
=> => exporting config sha256:41b2d861771bb29bcf4662a781dc5a6869034495bed5db5ff788ea30eddb3a84 0.0s
=> => exporting manifest sha256:b0c5df8f7742fc137fe6727069b9da00729ba40c13acafe0335cdd552275da85 0.0s
=> => exporting config sha256:83b757d63b16256ec23290b41a5ca90279b18f22bc00195f336faaf0c464483a 0.0s
=> => exporting manifest list sha256:f778f4f19a6e7dc4adba9b8c704ad283b3fb62d9e5721920583d4639b7e77a09 0.0s
=> => sending tarball 0.6s
Loaded image: docker.io/wbuntu/gost:v2.11.5
➜ gost git:(master) ✗ nerdctl push --all-platforms wbuntu/gost:v2.11.5
index-v2.11.5@sha256:f778f4f19a6e7dc4adba9b8c704ad283b3fb62d9e5721920583d4639b7e77a09: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:b0c5df8f7742fc137fe6727069b9da00729ba40c13acafe0335cdd552275da85: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:cbc82e262f8c94507130b07afa6c0de9f9ce2b1f6e7865b1be7b7e4ce3dcd2d4: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:41b2d861771bb29bcf4662a781dc5a6869034495bed5db5ff788ea30eddb3a84: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:83b757d63b16256ec23290b41a5ca90279b18f22bc00195f336faaf0c464483a: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 22.7s
本地也可以直接查看多架构镜像,目前显示上还有一些局限性,PLATFORM 列不支持展示多架构,,如下:
➜ gost git:(master) ✗ nerdctl image ls
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
wbuntu/alpine 3.18 f59c3e7dac85 7 hours ago linux/amd64 25.2 MiB 9.9 MiB
wbuntu/alpine 3.18 f59c3e7dac85 7 hours ago linux/arm64 0.0 B 10.0 MiB
wbuntu/gost v2.11.5 f778f4f19a6e 2 hours ago linux/amd64 38.2 MiB 15.0 MiB
wbuntu/gost v2.11.5 f778f4f19a6e 2 hours ago linux/arm64 0.0 B 14.7 MiB
实际上显示的是 OCI 多架构镜像的 index 文件中最靠前的镜像架构:

nerdctl 提供了 compose 子命令来兼容 docker-compose,但还存在一些未实现的功能,例如:services.<SERVICE>.links、services.<SERVICE>.build.extra_hosts等,详细内容参考:docs/compose.md。
如果是国内用户,大多需要解决拉取镜像失败的问题,我维护了一个加速器,Containerd 的配置可以参考:常用容器引擎-Containerd
在我看来,作为开源软件界大腕的Red Hat 有一个习惯:如果无法主导一个重要开源项目,那就从头开发一整套替代品,再用于 Red Hat 产品体系。
Podman、Skopeo、Buildah、CRI-O 等都是在红帽的大手笔投入下开发的,CRI-O 意在争夺 CRI 接口的标准实现,Podman 则是 Docker 的替代品,在 Docker 公司推出面向桌面端的 Docker Desktop 后,Podman Desktop 应运而生,软件方面只有红帽能做到这种级别的碰瓷。
Debian 12 官方源中已经提供了 Podman,目前版本为 4.3.1,Debian 12 以前的系统中提供的是 3.X 版本,与 4.X 版本在命令行和参数上有一些差异,推荐使用 4.X 版本,使用 apt 命令即可安装,如下:
apt install -y podman skopeo buildah podman-docker podman-compose
其中 podman-docker 提供了兼容 Docker 命令行的工具,podman-compose 提供了兼容 docker-compose 的工具集。
Podman 最初的口号是配置一个 alias 即兼容所有 Docker 常用操作:alias docker=podman(已安装 podman-docker 时无需配置),如下:
➜ ~ alias docker=podman
➜ ~ docker version
Client: Podman Engine
Version: 4.3.1
API Version: 4.3.1
Go Version: go1.19.8
Built: Thu Jan 1 08:00:00 1970
OS/Arch: linux/amd64
➜ ~ podman pull alpine:3.18
Trying to pull docker.io/library/alpine:3.18...
Getting image source signatures
Copying blob 73baa7ef167e skipped: already exists
Copying config 8fd7cac70a done
Writing manifest to image destination
Storing signatures
8fd7cac70a4aaabb31d459b03e3534b14341c31429b678b650039ac45a606cfc
➜ ~ podman run --rm alpine:3.18 date
Thu Jul 18 21:43:29 UTC 2024
➜ ~ podman-compose version
['podman', '--version', '']
using podman version: 4.3.1
podman-composer version 1.0.3
podman --version
podman version 4.3.1
exit code: 0
此外 Podman 也提供了用户命名空间、无特权模式(rootless)、无守护进程等特性,配合 skopeo、buildash 等有许多玩法。
不过作为 Docker 的替代工具方面,我还有以下关注点:
- 重启策略:缺点,受限于无守护进程模式,使用
--restart-policy=unless-stopped时,在系统重启后不会自动重启容器(Docker 和 Containerd 没有这个问题),在 Podman 上有这个需求时,只能使用--restart-policy=always。 - 未设置默认 registry:缺点,默认只配置了常用镜像的 alias 来指向 docker.io 或 quay.io,可能会影响老脚本运行,需手动配置搜索镜像时访问的 registry 列表来优先引用 docker.io 的镜像。
- 多架构镜像支持不完善:缺点,缺少 buildkit 这种能利用模拟器构建多架构镜像的能力,需要使用老办法,在对应架构平台构建单架构镜像并推送到镜像仓库,然后使用 podman manifest create 命令引用多个单架构镜像创建多架构镜像,最后再推送到 registry。
- 支持挂载镜像:优点,执行
podman image mount imgID即可挂载容器镜像,无需运行容器,对于利用容器镜像同步文件的场景很有用。 - 容器工具共享存储与配置:优点,
/var/lib/contaners目录下的容器和镜像存储,以及/etc/containers下的配置对 Red Hat 的容器工具 skopeo、buildah 等共享,它们也是无守护进程模式,现在看大概是利用共享存储和文件锁实现的,配置文件也对 CRI-O 共享,但可能需要重启才能生效。
Red Hat 容器工具对镜像的灵活操作在 skopeo 上体现的淋漓尽致,它支持在两个 registry 之间直接拷贝容器镜像(无需拉取到本地)、指定镜像 manifest 类型(oci、v2s1、v2s2)、保持镜像 digest 一致等,目前在市面上仅此一份,连 OpenShift Container Platform 中引用容器镜像时都使用 sha256 而非 tag。
Podman 的配置文件也使用 TOML 格式,可以参考:常用容器引擎-Podman & CRI-O。