跳到主要内容

没有 docker daemon 的节点,怎么让 docker 命令照常工作?

· 阅读需 4 分钟

KubeKey 搭的 k8s 集群,节点上装的是 containerd + nerdctl,没有 docker daemon。平时操作没问题,但一旦要跑别人的脚本、CI 或者凭肌肉记忆敲 docker 命令时就傻眼了——docker: command not found。

这篇记录我写的一个 60 行 bash shim:把 docker 伪装成 nerdctl 的转发器,以及部署过程中踩到的一个 CentOS 7 专属的隐蔽坑。

一、为什么需要 shim​

两种方案对比:

方案优点缺点
装完整 docker daemon完全兼容与 containerd 抢资源、双运行时、kubelet 不认 docker 的容器
docker→nerdctl shim零依赖、纯转发、不动运行时少量命令(swarm/plugin)不支持

k8s 节点上装 dockerd 是反模式——kubelet 用的是 containerd socket,docker 起容器跟 k8s 完全是两套命名空间视图。而 nerdctl 本身就是 containerd 的"docker 兼容 CLI",命令参数 90% 对齐 docker。所以缺的只是一个壳:让 docker 这个命令名存在,把参数原样转发给 nerdctl。

二、核心设计​

# 命名空间参数:k8s 集群容器在 k8s.io,用户容器在 default
NS_ARGS=()
if [[ -n "${DOCKER_NAMESPACE:-}" ]]; then
NS_ARGS=(-n "$DOCKER_NAMESPACE")
fi

cmd="${1:-}"
shift || true

case "$cmd" in
swarm|plugin|builder) # nerdctl 无对应,明确报错
echo "docker-shim: '$cmd' 不支持" >&2; exit 1 ;;
compose) # docker compose -> nerdctl compose
exec "$NERDCTL_BIN" compose "$@" ;;
buildx) # docker buildx build -> nerdctl build
exec "$NERDCTL_BIN" "${NS_ARGS[@]}" build "$@" ;;
*)
exec "$NERDCTL_BIN" "${NS_ARGS[@]}" "$cmd" "$@" ;;
esac

三个关键决策:

1. 命名空间用环境变量注入

nerdctl 有命名空间概念:集群容器在 k8s.io,手动跑的容器在 default。docker 没有这个概念,所以 shim 里用 DOCKER_NAMESPACE 环境变量控制,默认 default,需要看集群容器时:

export DOCKER_NAMESPACE=k8s.io
docker ps # 列出 k8s 集群的容器
docker images # 列出集群镜像(165 个)

2. 命令映射表

  • docker compose → nerdctl compose(v2.3.5 自带 Compose v2)
  • docker buildx build → nerdctl build(走 buildkitd)
  • docker swarm/plugin/builder/system → 明确报"不支持",而不是把错误吞掉

3. 不支持的命令要"响亮的失败"

shim 最忌讳静默吞错。swarm 这类 nerdctl 没有的命令,直接 exit 1 并打清楚提示,让脚本作者知道自己依赖了不存在的功能,而不是在半夜被玄学错误坑。

三、那个隐蔽的坑:bash 4.2 + set -u​

第一版脚本我写了 set -u(未定义变量报错,好习惯),结果部署上去:

[root@VM-0-14-centos ~]# docker
/usr/local/bin/docker: line 74: NS_ARGS[@]: unbound variable

NS_ARGS 明明在脚本开头初始化成了空数组 NS_ARGS=(),怎么会 unbound?

根因:CentOS 7 自带 bash 4.2。bash 4.4 之前有个著名 bug——set -u 下,空数组的 "${arr[@]}" 展开会报 unbound variable。网上流传的 workaround ${arr[@]+"${arr[@]}"} 在 bash 4.2 下同样无效(该修复 4.4 才合入)。

# 看起来没问题,bash 4.2 下照样炸
set -u
arr=()
echo "${arr[@]+"${arr[@]}"}" # bash 4.2: unbound variable

修法:shim 的本质就是 exec 转发,用 set -u 收益极小,直接降级为 set -o pipefail。空数组展开在无 set -u 时天然安全。

这个坑提醒我:写跨机器部署的脚本,永远要按目标机器最老的 bash 版本验证,不能拿自己开发机的 bash 5.x 想当然。

四、部署与验证​

# 1. 脚本传到节点
install -m 0755 docker-shim.sh /usr/local/bin/docker

# 2. 验证矩阵
docker # nerdctl help
docker ps # default 命名空间,无报错
DOCKER_NAMESPACE=k8s.io docker images # 165 个集群镜像
docker compose version # nerdctl Compose v2.3.5
docker buildx build --help # 映射到 nerdctl build
docker swarm init # 明确报"不支持"

全部通过。之后 docker login registry.d7kj.com 这类操作在 worker 上也能直接用了。

五、小结​

  • 节点只有 nerdctl 时,一个 60 行 shim 就能让 docker 生态的命令全部可用,不动运行时、零依赖
  • 命名空间差异用环境变量桥接,k8s.io 和 default 一键切换
  • 不支持的子命令要响亮失败,不要静默
  • 教训:跨机器脚本按最老 bash 验证,set -u + 空数组在 bash 4.2 是雷区

完整脚本已放进我的 Hermes skill,以后给新节点装就是一条命令的事。