解决 Docker "Unimplemented: unknown service containerd.services.leases.v1.Leases" 错误
事故现场:一次崩溃的滚动升级
作为一名 Arch Linux 用户,我习惯性地执行了 sudo pacman -Syu 将系统更新到最新。然而更新后,所有 Docker 容器都无法启动,命令行抛出如下错误:
Error response from daemon: Unimplemented: unknown service containerd.services.leases.v1.Leases
failed to start containers: a1000b-sdk-fad-2.3.0.4
检查版本信息,得到的却是两个看起来都很新的组件:
$ containerd --version
containerd github.com/containerd/containerd/v2 v2.3.0 2976f38ccbfcda5ef1364d63d60b0a304e4bf94a.m
$ docker version
Server: Engine:
Version: 29.4.2
...
$ docker info | grep -i "containerd version"
containerd version: N/A
docker info 中居然显示 containerd version: N/A!显然 Docker 已经无法正确识别 containerd 的服务了。
错误根因:版本“新”的错觉与 API 断裂
很多人第一反应是:Docker 29.x 太老了?或者是 containerd 2.x 太新了?
实际上,containerd 2.x 是“主动变新”的一方,Docker 29.x 还在使用旧接口。
关键点在于:
- containerd 从 1.5 开始引入了
leases(资源租约)服务,对应的 gRPC 服务名为containerd.services.leases.v1.Leases,主要用于镜像层的生命周期管理(避免垃圾回收过早删除正在使用的快照)。 - containerd 2.0 是一个包含破坏性变更的大版本,它彻底重命名或移除了旧版的
leases.v1.Leases服务(改用新的服务结构)。 - Docker Engine 29.x(以及当前的整个 29 系列)在启动容器和拉取镜像时,仍然会调用旧版的
leases.v1.Leases接口,因为它的代码尚未适配 containerd 2.x 的新 API。
因此,当 Arch Linux 将 containerd 独立滚动升级到 2.x 后,Docker 29.x 尝试调用一个已经不复存在的 gRPC 服务,containerd 返回 Unimplemented,容器启动失败。docker info 中的 N/A 则是 Docker daemon 连接 containerd 失败后给出的默认显示。
一句话总结:containerd 2.x 是新版本,但 Docker 29.x 还在用旧版 containerd 的 API,两者出现了严重的版本脱节,导致 Docker 完全无法工作。
解决方案:降级 containerd 并锁定版本
由于 Docker 官方尚未发布支持 containerd 2.x 的版本,目前最稳妥、社区验证有效的方法是将 containerd 降级到 1.7.x(建议 1.7.22),然后锁定其版本,阻止后续滚动更新自动升级。
选择正确的降级版本
从 Arch Linux Archive 查看 containerd 历史版本发现,官方存档中 1.7.x 系列的最后一个版本是 containerd-1.7.22-1-x86_64.pkg.tar.zst(发布日期 2024-09-09)。该版本与 Docker 29.4.2 完全兼容,可以作为降级目标。
⚠️ 注意:存档中并不存在 1.7.25 等更高版本,不要选错。
方法一:使用 downgrade 工具(推荐)
对于 Arch Linux 用户,社区工具 downgrade 是最方便的降级助手,它能自动搜索本地缓存、归档站和镜像,并解决下载时可能出现的 SSL 兼容问题。
# 安装 downgrade
sudo pacman -S downgrade
# 执行降级
sudo downgrade containerd
在出现的交互式列表中,选择 1.7.22-1 版本,确认后工具会自动完成下载和安装。
方法二:手动下载并安装(绕过 SSL 错误)
如果因 OpenSSL 版本过高导致 pacman -U 从 archive 下载失败(出现 TLS connect error),可以使用 curl 临时跳过证书校验,然后本地安装。
# 手动下载包(-k 忽略证书错误,确保网络可信)
curl -k -O https://archive.archlinux.org/packages/c/containerd/containerd-1.7.22-1-x86_64.pkg.tar.zst
# 本地安装
sudo pacman -U containerd-1.7.22-1-x86_64.pkg.tar.zst
锁定 containerd 版本
降级完成后,必须立即编辑 /etc/pacman.conf,找到 IgnorePkg 所在行,修改为:
IgnorePkg = containerd
这样即使之后执行 pacman -Syu,containerd 也会被忽略,不会再被自动升级到 2.x。
重启 Docker 并验证
# 重启服务(containerd 会在 docker 之前被自动启动)
sudo systemctl restart docker
# 检查 containerd 版本是否正确识别
docker info | grep -i "containerd version"
# 期望输出: containerd version: 1.7.22
# 测试启动之前出问题的容器
docker start a1000b-sdk-fad-2.3.0.4
此时容器应该能够正常启动,错误彻底消失。
其他可能相关的环境问题
虽然绝大多数案例都是版本不兼容导致的,但以下情况也可能引发类似错误,排查时可以作为参考:
- XFS 文件系统的
ftype=0:containerd 2.x 的 overlayfs 快照器要求 XFS 在格式化时带有ftype=1,否则会加载失败并伴随Unimplemented或插件错误。如果问题出现在 XFS 分区上,可以检查/var/lib/containerd所在分区的ftype设置。 - 服务启动顺序错误:更新后仅重启了 Docker 而未重启 containerd,导致旧进程残留,这时 Docker 仍会尝试连接旧的 socket,也可能触发相同的错误。重启整个服务栈即可解决。
总结:滚动更新中的依赖陷阱
Arch Linux 的滚动更新策略让我们能够迅速获得最新的上游软件,但也意味着不同的软件包可能以不同的节奏跨越大版本。containerd 2.x 的发布就是一个典型例子:它带来了架构重构,但下游的 Docker 尚未完成适配,直接升级就会导致整个容器栈瘫痪。
本次故障的修复路径清晰且经过大规模验证:
- 将 containerd 降级至最后一个兼容的 1.7.x 版本(如 1.7.22)。
- 锁定该包防止再次误升级。
- 等待未来 Docker 官方宣布支持 containerd 2.x 后,再解除锁定进行升级。
对于其他发行版的用户(如 Ubuntu),如果是通过 Docker 官方仓库安装的 Docker,通常 containerd 会作为 Docker 的依赖被一起锁定版本,不会突然升级到 2.x。但如果手动安装了新版的 containerd,同样会出现此问题,解决方案也是降级 containerd。
希望这篇博客能帮助你在面对同样的错误时快速定位问题,恢复 Docker 的正常运行。如果你在修复过程中遇到其他特殊情况,欢迎在评论区交流或查阅 Arch Wiki 及 Docker 官方文档获取最新信息。