容器运行时实战:搞清 Docker/containerd/CRI-O/Podman 各自的分工


很多人以为"容器 = Docker",其实 Docker 只是"容器生态"里最出名的一层外壳:真正干"创建容器、拉起进程、管理命名空间与 cgroup"这些底活的,是底层的容器运行时(Container Runtime)。在 K8s 里这个分工更明显:kubelet 通过 CRI(Container Runtime Interface)和运行时对话,Docker、containerd、CRI-O 都可以是那个运行时。这篇讲清运行时解决什么、Docker 与 containerd 的关系、怎么选(containerd/CRI-O/Podman)、以及常见坑与边界。

一、容器运行时解决什么:把"容器"从概念变成可运行的进程

容器的本质是"用 Linux 内核能力隔离的普通进程":命名空间(namespace)给它独立的视图(进程/PID、网络、挂载点、用户、主机名),cgroup 限制它的 CPU/内存/IO,而"把镜像解开、搭好这些隔离、拉起进程、把日志交出去"这套底层动作,就是运行时干的。运行时是"镜像 → 运行中的容器"的翻译与执行者:它读镜像层、创建容器配置、调用内核接口把进程隔离地跑起来。理解了这层,你就明白为什么"Docker 会 CRI 化"、为什么 K8s 要抽象一个 CRI 接口——因为"跑容器的内核功夫"和"上层用容器的体验"本来就该分开。

它和上层工具的分工:Docker CLI/Compose 给你"好用的一层体验"(build/run/网络编排),底层实际调 containerd 等运行时;K8s 的 kubelet 直接通过 CRI 调运行时(不经过 Docker)。自托管选型时,"用什么运行时"往往不是直接选,而是"你用 Docker 还是 K8s 还是裸 containerd"顺带决定的——但搞清楚这层,排障与迁移时就不会一头雾水。

二、Docker 与 containerd 的关系:外壳与内核功夫

Docker 历史上自带的"底活"部分就是 containerd,后来 containerd 独立成 CNCF 项目并被 K8s 广泛用作默认运行时。现在的分工是:Docker(CLI + daemon)作为用户侧工具,containerd 作为实际执行容器的地方;K8s 甚至可以完全不用 Docker 那层,直接让 kubelet 通过 CRI 调 containerd。对自托管用户,最实用的认知是:你的 `docker run` 背后是 containerd 在执行;排容器故障时除了 `docker ps/logs`,还有 containerd 层面的 crictl 可查;而 K8s(含 K3s)默认就用 containerd。

# K3s 默认使用 containerd;查看/操作底层容器可用 crictl
crictl ps -a                          # 列出本节点所有容器(含 K8s 的)
crictl logs             # 看容器日志(与 docker logs 类似)
crictl images                         # 查看已拉取的镜像

# 如果是 K3s,crictl 通过 socket 工作:
#   export CONTAINER_RUNTIME_ENDPOINT=unix:///var/run/k3s/containerd/containerd.sock

常见误区是"只在 docker 层面看、以为容器没了问题就没了":容器还在、但上层编排(compose/K8s)与它失去连接、或镜像层异常,crictl/containerd 层面才能看到真相。对用 Docker 的人也值得记一句:`docker inspect`、`docker events`、以及 containerd socket 的健康,是"容器到底在不在正常跑"的三级证据。

三、怎么选:containerd / CRI-O / Podman 与无守护进程

运行时选型要分清场景。containerd:K8s 默认、最主流,通用、资料多,适合"K8s/K3s + 底层运行时"的大多数场景。CRI-O:专为 K8s CRI 设计、更精简,安全与轻量取向(Red Hat 系常用),适合"只要 K8s、不想带多余特性"的部署。Podman:无守护进程(daemonless)的容器工具,兼容 Docker CLI 体验,强调 rootless(非 root 运行)与 systemd 集成,适合"不想长驻 Docker daemon、想 rootless 跑容器"的自托管单机场景。Docker 本身在"单机自托管 + Compose 编排"仍是最顺手的入口,很多 K8s 之外的部署根本不用换。

# Podman 兼容 Docker CLI 的典型用法
podman run -d --name web -p 8080:80 nginx
podman ps -a && podman logs web
# rootless:普通用户直接跑容器,无需 root、无需常驻 daemon
# systemd 集成:podman generate systemd --new --name web > /etc/systemd/system/web-container.service

# 换运行时的成本提醒:换容器运行时 ≠ 换镜像,
# 但容器数据目录(如 /var/lib/containerd、/var/lib/docker)与网络配置
# 都要重新规划与迁移,别在没有备份预案时直接换。

选型判断一句话:K8s/K3s 就用 containerd(默认,少折腾);要极简 K8s 运行时选 CRI-O;单机、要 rootless/无 daemon、想 Docker 体验选 Podman;日常 Compose 自托管继续用 Docker 完全没问题。关键是"别为了换而换"——运行时是地基,换一次要动存量容器与数据目录,收益不明显就不动。

四、常见坑:rootless 边界、镜像存储目录、运行时与上层失联、安全

运行时层四个坑要尤其注意。一是"rootless 不是万能":Podman rootless 好用,但映射端口 <1024、部分网络特性、挂载权限有边界,配置前先想清"我需要的网络/挂载 rootless 是否支持"。二是"镜像/数据存储目录":不同运行时的存储路径不同(docker 在 /var/lib/docker,containerd 在 /var/lib/containerd),换运行时后旧镜像不会自动带过来,磁盘规划与迁移要提前想。三是"运行时与上层失联":daemon/CRI 通信 socket 异常、版本不匹配,会出现"镜像还在、容器在跑,但 docker ps/kubectl 看不到"——先看 socket 与运行时日志,别反复重启上层。四是"安全边界":容器与宿主共享内核,运行时漏洞影响所有容器,保持运行时与内核更新、配合非 root/只读/能力最小化(前面聊过的加固)才有意义。

# 排查"上层看不到容器"的思路
#   1) docker ps 没东西,但进程在?→ 查 docker/containerd daemon 状态与 socket
#   2) kubectl 报运行时错误 → kubectl describe pod 看 Events + crictl ps 对照
#   3) 存储盘要满?→ 看 /var/lib/docker 或 /var/lib/containerd 的占用与 inode
#   4) 版本升级后行为变化 → 先看运行时/CRI 版本兼容性说明

把它当排障 SOP:先确认"底层容器/进程到底在不在(crictl/ps)",再查"运行时与上层是否正常通信(socket/daemon)",然后看"存储与磁盘是否异常",最后才怀疑应用本身。运行时是"最底层的地基",它的状态决定上层一切"看得到/看不到"。

五、边界:运行时管"跑起来",管不了"编排、构建与数据"

运行时的边界要认清:它负责"把镜像跑成隔离进程",不负责"上层编排"(副本、滚动、调度是 K8s/Compose 的事)、不负责"镜像构建"(那是 buildkit/buildah 的事,虽然常伴随出现)、也不负责"容器数据的安全"(数据卷与备份仍是你的职责)。其次,运行时升级是"地基级变更",要在测试环境验证兼容(尤其 K8s 与 CRI 版本、内核特性依赖)再动生产。最后,运行时选型会锁定一部分生态习惯,改起来成本不小——一开始按场景选对,比事后迁移省得多。

把运行时放回自托管整体:它是"镜像到进程"的执行层,决定容器怎么被隔离地跑起来,也是排障时"最底层的证据来源"。落地要点:搞清 Docker(外壳)与 containerd(底活)的分工,K8s/K3s 里学会用 crictl 看底层;选型按场景——K8s 用 containerd、极简用 CRI-O、单机 rootless 用 Podman、日常 Compose 继续 Docker;防住 rootless 边界、存储目录迁移、运行时与上层失联、安全边界四坑;并认清它管"跑起来"、不管编排构建与数据安全。当你把"底下谁在跑、出问题去哪查、换它要动什么"都心里有数,容器这层才算真正被你把住了地基。

结语

容器运行时(containerd/CRI-O/Podman)的价值,是成为"镜像 → 隔离运行的进程"的执行层,也决定容器排障时你该去哪一层找证据。落地要点:分清 Docker 外壳与 containerd 底活,K8s/K3s 下用 crictl 看底层容器与日志;按场景选——K8s 用 containerd、要极简用 CRI-O、单机想 rootless/无 daemon 用 Podman、日常 Compose 继续用 Docker;防住 rootless 边界、存储目录不随运行时迁移、运行时与上层失联、安全边界四坑,并把运行时升级当"地基级变更"来演练;认清它管"跑起来"、不管编排构建与数据安全。把运行时这层地基摸清、选对、会查,自托管的容器栈才算真正站得稳、也迁得动。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Helm Chart 实战:把 K8s 应用部署从一堆 YAML 变成包管理

Webhook 通知与事件驱动实战:让告警实时到达并自动响应

评 论
更换验证码