
1. 先搞明白crictl 在整个容器世界里到底站在哪先说个很多人遇到过的场景以前用 Docker 的时候排查节点问题习惯性敲docker ps、docker logs一切都很顺手。但一到了标准的 Kubernetes 环境里节点上明明跑着容器docker ps却什么都看不到或者机器上压根没装 Docker。这时候如果你只知道 Docker 这一套命令就会很慌。实际上Kubernetes 从 1.24 版本开始就彻底移除了对 Docker 作为运行时dockershim的内置支持节点上真正负责“跑容器”的是实现了 CRIContainer Runtime Interface标准的运行时比如 containerd 和 CRI-O。而crictl就是专门用来跟这些 CRI 兼容运行时对话的命令行工具。这篇文章要聊的 CRI 规范下 crictl 常用操作命令解析与国内镜像源配置就是围绕这个场景展开的目标是让运维和开发同学在纯 containerd 节点上也能像以前用 docker 一样熟练排查问题、管理镜像。需要先对齐一个观念crictl不是docker的替代品而是“Kubernetes 视角下的容器调试工具”。它操作的核心单元是 Pod、容器和镜像但它并不具备“创建容器”这种能力——创建容器的动作由 kubelet 通过 CRI 接口触达运行时crictl只是一个“观察和干预”的客户端。所以你可以把它理解为一把外科手术刀专门用来在 K8s 节点上做诊断和应急处理而不是用来“造容器”。1.1 crictl、ctr、docker 三者的分工别搞混先说ctr。ctr是 containerd 自带的原生 CLI功能很底层能操作 containerd 里的镜像、容器、任务等但它不理解 Kubernetes 的 Pod 概念也不关心沙箱sandbox和业务容器的区别。你在ctr里看到的很多容器其实是 Kubernetes 的 pause 沙箱容器用ctr去操作它们很容易把节奏打乱。crictl则不一样。它连接的是 containerd 的 CRI 插件也就是那个监听的 gRPC socket通常叫containerd.sock它是从“Pod 和容器”的角度去理解和呈现信息的。crictl ps -a里看到的容器会分成sandbox和container两类前者是每个 Pod 的“壳”后者才是真正运行业务的容器进程。这一层抽象非常关键Kubernetes 里一个 Pod 对应一个 sandbox 容器pause 容器再加上一个或多个业务容器。至于docker现在的节点上已经很少需要它了。如果你的 K8s 集群节点还在用 Docker Engine 作为 runtime那么你看到的容器确实可以用docker ps查看但这种情况要么是历史遗留要么是走了 cri-dockerd 这类适配器。新部署的集群基本都是一水儿的 containerd。”1.2 一个必须养成的习惯先看版本拿到任何节点我建议你第一件事不是急着敲命令而是先确认crictl和运行时的版本是否匹配crictl version这个命令会同时打印出crictl客户端的版本和它所连接的运行时版本。如果两者版本差距太大比如 crictl v1.28 配 containerd v1.26某些功能可能会表现异常比如crictl exec进不了容器、crictl ps字段解析不对等。我见过最典型的情况是crictl 版本太老无法解析新运行时返回的字段导致crictl ps明明节点上有容器却显示为空。所以养成这个习惯能帮你少走很多弯路。2. 高频命令速查从 docker 习惯平滑迁移到 crictl说实话crictl的命令风格和docker非常相似很多时候就是把docker换成crictl直接敲。下面我挑最常用的命令按“查看容器—管理镜像—看日志—进容器—拿详情”这条线来逐一过一遍。每条命令我都会给实际效果和使用场景不是单纯罗列参数。2.1 查看容器ps 命令的两个细节crictl ps crictl ps -a crictl ps -a --state Exitedcrictl ps默认只显示“正在运行”的容器这跟docker ps很像。但注意一个细节crictl ps的输出里会有一列叫CONTAINER这一列显示的是容器 ID是沙箱容器和业务容器各自的 ID不是 Kubernetes 的 Pod 名称。所以当你想根据 Pod 名称去查容器的时候需要用-a参数列出所有容器然后结合--name筛选crictl ps -a --name nginx还有一个很实用的组合同时显示容器对应的 Pod 名称和命名空间。用下面的方式格式化输出crictl ps -o wide-o wide会多显示 Pod 的 UID、沙箱 ID 等信息。如果你想只看某一类容器可以结合--label过滤比如crictl ps -a --label io.kubernetes.container.namenginx这个 label 是 kubelet 在创建容器时自动打上去的用起来非常准。2.2 镜像管理pull、images、rmi镜像相关命令几乎就是把 docker 的关键词平移过来crictl pull nginx:1.25 crictl images crictl rmi nginx:1.25crictl images列出的镜像分为两类来源一类是 kubelet 通过 CRI 拉取的另一类是你手动crictl pull拉进来的。这两者在 containerd 里是同一个镜像存储所以crictl pull的镜像kubelet 之后要调度时也能直接用。不过要注意crictl pull默认走的是 containerd 的 CRI 插件配置和本文后面要聊的镜像源配置有直接关系先留个悬念。另外清理节点上无用镜像时别一上来就crictl rmi --all万一有镜像正在被容器引用删除会报错。更安全的做法是配合crictl images -o wide看引用关系或者直接用crictl rmi加上具体镜像 ID 来删。2.3 日志与执行logs 和 exec 的边界crictl logs container-id crictl logs -f container-id查看容器标准输出日志时crictl logs和docker logs用法几乎一致。需要注意一点crictl logs获取的是运行时里保存的日志包含被 kubelet 重定向过的日志内容。如果业务进程把日志打到文件里而没有写到 stdout那crictl logs是看不到的这时候还得靠crictl exec去容器里翻文件。进入容器执行命令crictl exec -it container-id sh这里有个容易踩的坑crictl不像 docker 那样有 attach 和 exec 同时组合的便利exec 只负责执行命令。而且exec 进入的是 k8s 业务容器不是 pause 沙箱。你想进 pause 沙箱比如抓包、看 namespaces不能用 exec得用下一节讲的crictl attach或者直接对沙箱 ID 执行 nsenter。2.4 查详情inspect 是排障第一利器crictl inspect container-id这条命令返回的是 JSON内容非常丰富容器状态、PID、挂载点、注解、label、运行时 spec 都能看到。排障时我最常用的字段是.status.pid和.info.runtimeSpec——前者告诉你怎么找到容器的宿主机 PID方便nsenter进去后者展示该容器创建时的运行时 spec 细节。更常见的场景是查“容器为什么退出”crictl inspect container-id | jq .status.state, .info.exitCode通过 exitCode 和 state 基本能定位大半问题。再配合crictl logs看业务日志排障路径就完整了。2.5 拿资源占用statscrictl stats这个命令类似docker stats会按容器展示 CPU、内存、网络 IO 和磁盘 IO。因为它是实时从运行时 cgroup 里读的数据所以定位节点上某个容器内存飙高时非常有效。要注意的是stats 里的容器 ID 可能更多是沙箱容器你可以用crictl ps的结果交叉对照或者直接看业务容器那一行。3. 国内镜像源配置从拉取失败到 Pod Running 的完整链路很多同学在配好 K8s 集群后第一次部署应用时往往会遇到ImagePullBackOff或ErrImagePull。在干净的 containerd 节点上原因大概率只有一个默认拉取docker.io上的镜像时网络环境不理想或者根本连不上 Docker Hub。这时候就需要给运行时配置国内镜像加速源。这个章节围绕“国内镜像源配置”展开我要先帮你理清配置入口然后给出一份可以直接改的配置示例再讲清楚验证方法以及常见的新坑。3.1 先搞清楚镜像源配置落在哪crictl.yaml 还是 config.toml这是个高频误区。crictl自己有一个配置文件/etc/crictl.yaml里面配置的是“crictl 客户端如何连接运行时”的信息runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false pull-image-on-create: false它管的是“往哪连”不是“镜像从哪拉”。你就算在这个文件里写一堆 mirror 也没用crictl 拉镜像时真正执行拉取动作的是 containerd 的 CRI 插件。所以镜像源配置的真正落点是 containerd 的配置文件最常见的位置是/etc/containerd/config.toml和/etc/containerd/certs.d。在较老版本如 1.x中配置方式是在[plugins.io.containerd.grpc.v1.cri.registry]下加mirrors而在 containerd 2.x 中配置方式逐渐迁移到/etc/containerd/certs.d目录以 hosts.toml 的方式管理但整体思路一致给某些 registry 指定多个 endpoint 作为镜像源。下面我会先讲带mirrors的经典方式兼容性最好再补充新版方式。3.2 经典配置方式config.toml 中的 registry.mirrors找到config.toml中 CRI 插件所在的段通常是[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://docker.mirrors.ustc.edu.cn]这段配置的含义是凡是拉取docker.io下的镜像先走https://docker.mirrors.ustc.edu.cn这个 endpoint。如果这个 endpoint 拉不到或者失败containerd 会依次尝试 endpoint 列表里的下一个。注意这里的关键是mirrors.docker.io这一段它把“所有来自 docker.io 的镜像拉取动作”都代理到了国内镜像源。假如你在 kubelet 的 yaml 里写的是image: nginx:1.25它实际上会被解析成docker.io/library/nginx:1.25这样就会命中这段配置。如果你用的是腾讯云、阿里云等加速器可以把 endpoint 替换成对应地址比如[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://mirror.ccs.tencentyun.com, https://docker.mirrors.ustc.edu.cn]我在实际环境里配过多套来源经验是不要把 endpoint 列表写得太长两个最好三个以内可接受。因为如果第一个镜像源返回了一个 404比如该镜像源没有同步最新镜像containerd 不会继续试下一个而是直接报错。长列表并不一定提高成功率反而拖慢了失败时的反馈速度。3.3 containerd 2.x 的 hosts.toml 方式新版配置的演进如果你用的 containerd 版本是 2.x会发现上面的mirrors配置虽然还能用被标记为弃用但官方更推荐的方式是使用 hosts.toml。做法是在/etc/containerd/certs.d/docker.io/hosts.toml里写server https://docker.io [host.https://docker.mirrors.ustc.edu.cn] capabilities [pull, resolve]这种方式的优点是除了配置镜像源还可以按 host 单独控制“该 endpoint 能拉取、能解析、还是能推送”。对于只做拉取镜像的节点来说我们把 capability 限定为[pull, resolve]就够了可以避免某些意外推送操作。不过需要提醒的是新版 containerd 里如果certs.d目录下已经有某个 registry 的 hosts.toml而config.toml里又存在旧的mirrors配置那么实际生效的是 hosts.toml旧配置会被忽略。所以改配置前最好先确认你的 containerd 到底用的是哪一套体系否则很容易出现“我改了配置但没生效”的困惑。3.4 一份可靠的操作流程从修改到验证下面给出一套我反复使用过的操作流程备份原配置cp /etc/containerd/config.toml /etc/containerd/config.toml.bak修改config.toml找到[plugins.io.containerd.grpc.v1.cri.registry.mirrors]段没有就直接追加。如果你已有certs.d体系建议走 hosts.toml 方式。重启 containerdsystemctl restart containerd验证配置是否加载成功crictl info在返回的 JSON 里找registry相关字段看mirrors的 endpoint 是否出现在配置输出中。这一步能非常直观地确认“配置确实被运行时加载进来了”。实际拉一次镜像验证crictl pull docker.io/library/nginx:1.25如果返回很快说明镜像源已生效。如果卡住很久多半是镜像源不通可以换成另一个 endpoint 再试。3.5 配置镜像源之后常见的新坑第一个坑镜像源同步延迟。国内镜像源不是 Docker Hub 的实时代理它是定时同步的。刚发布的镜像 tag镜像源里可能还没有。我之前遇到过用户指定nginx:latest但镜像源同步到的还是几天前的版本导致 Pod 里行为不符合预期。解决方案是重要应用别依赖latest要么用精确 tag要么在镜像源后台手动触发同步。第二个坑私有镜像仓库不能滥用这个机制。mirrors配置是按docker.io这个 registry 维度匹配的它只管这个 registry。如果你的镜像在自建仓库registry.example.com那需要单独加一段[plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.example.com] endpoint [https://registry.example.com]或者在新版 hosts.toml 体系里建/etc/containerd/certs.d/registry.example.com/hosts.toml。否则即使你配置了 docker.io 加速器你的私有仓库镜像还是会走默认直连如果私有仓库没有走公网那拉不下来也正常。第三个坑改完配置后kubelet 已经缓存了旧镜像列表。某些情况下即使配置了镜像源节点上还缓存着旧的、损坏的镜像kubelet 可能不会重新去拉。遇到这种情况直接删掉旧镜像再触发一次重新调度crictl rmi old-image-id kubectl delete pod pod-name --force这样 kubelet 会重新走一次拉取流程配上新镜像源后就顺了。4. 交互式操作与排障边界不要只会 ps 和 logs第三节主要解决“镜像拉不下来”的问题。但镜像拉下来之后排障才刚开始。这一节聊聊crictl的交互式操作和它的一些边界感。很多人把crictl仅仅当作docker的平替遇到 Pod 没起来只会crictl ps -acrictl logs二连。但实际排障时crictl还有几个高级姿势非常有用。4.1 attach 沙箱进入 Pod 网络的快捷方式crictl attach sandbox-container-id注意attach 的对象是沙箱容器也就是 pause 容器。这个命令可以进入 Pod 的“网络命名空间”视角有什么用当你怀疑 Pod 网络有问题比如 DNS 解析不了、网络策略挡了流量你可以在沙箱容器里用cat /etc/resolv.conf看 DNS 配置检查容器网卡信息。不过实际操作中我更多直接用crictl inspect拿到沙箱 PID 后走nsenter这样更灵活SANDBOX_PID$(crictl inspect sandbox-id | jq -r .info.pid) nsenter -t $SANDBOX_PID -n bash进入之后就能看到 Pod 内的网卡、路由、 DNS 配置再结合节点侧的ip route做对比很多网络问题一下子就清楚了。4.2 排障参考拿到 Pod 容器列表后按这条链路走一个比较稳健的排查路径是这样的先用kubectl describe pod pod看事件确认是ImagePullBackOff还是CrashLoopBackOff或者Running但 Ready 不健康。如果镜像问题直接看 crictl 配置和镜像源配置按第四节的处理。如果是容器反复重启用crictl ps -a --name 关键容器名找到对应容器 ID然后:crictl logs container-id crictl inspect container-id | jq .status.state, .status.exitCode如果需要看容器内文件用crictl exec -it container-id sh进入后排查连 shell 都起不来比如启动脚本崩了可以用crictl inspect拿 PID 后走nsenter进容器的命名空间。这条链路是有顺序讲究的先看 K8s 层面的事件再缩小到容器运行时最后用 crictl 的工具钻进去。反之如果你一上来就crictl ps -a大概率会被一堆 sandbox 容器弄得迷失方向。4.3 crictl 的边界哪些动作别指望它首先crictl run并不是让你像docker run那样自由创建容器。它确实有crictl run和crictl runp但这属于偏底层的测试动作不是日常操作。kubelet 才是真正的容器创建者运维阶段不建议通过crictl去创建任何容器容易破坏 kubelet 对 Pod 状态的管理。其次crictl stop/rm可以停止和删除容器但你用docker stop的习惯放到crictl上很容易出问题——比如你删掉了一个正由 kubelet 管理的业务容器kubelet 会立即重建它。如果你的意图是强制杀容器正确的动作是在 K8s 层kubectl delete pod或者手动驱逐节点而不是直接用crictl去删。还有一点crictl不支持 docker 的-v挂载查看也没有docker inspect那样解析完整 JSON 的便捷选项但可以配合jq。它更实用的场景是“作为只读诊断工具”而不是“作为完整的容器生命周期管理器”。这点想明白后你在节点上操作就会克制很多也就不会闯祸。4.4 最后再说点实际经验用 crictl 这几年我印象最深的一个经验是它和 docker 的语法太像了导致很多人不去看它的细节配置出问题就以为“crictl 不好用”。其实 crictl 的设计目标非常纯粹——它是为了 CRI 运行时服务的调试器。你越理解 CRI 的模型Pod 分 sandbox container用起来就越顺手。另外一个体会是配置国内镜像源的时候别一味追求“最优加速源”。每个镜像源的稳定性和同步速度在不同地区、不同时间段表现并不同。自己拿几个常用镜像实际拉一遍计时对比才是靠谱的做法。毕竟 Pod 能不能 Running不看你配置了多豪华的源而看它能不能每次都快速、稳定地把镜像拉下来。