kubelet如何调用containerd?Kubernetes操作管理核心解析
2026/9/9 9:41:17 网站建设 项目流程

最近有个刚转到Kubernetes方向的同事问我:kubelet到底是怎么一层层找到containerd的?中间又是谁在真正创建容器?这个问题问得特别好。很多人用Kubernetes做日常部署、服务暴露、滚动更新,都玩得很熟,但一旦往底层深挖,或者往上层看项目怎么管理,就有点含糊。其实这正是“Kubernetes操作管理”的核心:向下要懂调用链路,向上要懂生命周期,两头一打通,运维才算真正掌控了集群,而不是只会执行命令。

这篇文章我就顺着这条主线展开。先讲kubelet与containerd的实体调用链路,从CRI接口到shim进程逐层拆开;再给一套可以直接照着做的集群部署流程,以及Dashboard安装和项目生命周期管理的完整实操;最后把我踩过的“磁盘管理控制台视图不是最新状态”这类真实故障和几个高频面试题一并整理出来。无论你是刚入行的运维新人,还是已经扛着生产集群的资深工程师,这篇文章都能给你一些值得收藏的细节。

1. 从原理到实体:Kubelet 到底是怎么调用 containerd 的

1.1 为什么 Kubernetes 可以抛弃 Docker,却离不开 containerd

要理解调用链,得先理解为什么现在Kubernetes默认的容器运行时是containerd,而不是Docker。早期Kubernetes确实是通过Docker来管理容器的,当时中间隔了一层叫dockershim的适配器。Docker本身是一个很重的“全家桶”,包含客户端、守护进程、容器运行时、镜像管理一堆东西。而Kubernetes真正需要的其实只是一个“能在Linux上把容器跑起来”的运行时,并不需要Docker那一整套上层体验。

后来Docker项目被拆分,核心运行时部分独立成containerd,并捐给了CNCF基金会。containerd设计得更轻、更专注,它对外提供标准gRPC接口,并且原生内置了CRI插件(Container Runtime Interface,容器运行时接口)。也就是说,containerd本身就实现了Kubernetes定义的CRI规范,不需要额外的适配层。Kubernetes在1.24版本里正式移除了dockershim,从此“Kubernetes + containerd”成为默认组合。

这个背景对运维来说不是可有可无的“历史课”,它直接决定了你在排查问题时的思维框架。如果你还停留在“容器就是Docker管理”的认知,那遇到节点上只有containerd进程、没有dockerd进程的情况就会一头雾水。

1.2 一条 Pod 从请求到容器进程的完整调用链

我用一句话概括整条链路:

kubelet → CRI gRPC 接口 → containerd → containerd-shim → runc → 容器进程

展开来说,当你在集群里执行kubectl apply创建Deployment,或者某个控制器触发了Pod重建,Apiserver会把Pod写入etcd。负责调度器的组件发现有一个新的Pod处于Pending状态,就选一个合适的节点绑定上去。接下来,真正和容器运行时打交道的,是节点上的kubelet组件,它是Kubernetes在节点上的“代言人”。

kubelet通过PLEG(Pod Lifecycle Event Generator)机制周期性地检测自己管理的Pod状态。发现新Pod需要创建后,kubelet会调用CRI插件暴露的gRPC接口,向containerd发送RunPodSandbox请求。这一步先创建一个“沙箱”,也就是Pod的基础设施容器——我们常说的pause容器。pause容器是整个Pod里最先启动的容器,它负责持有网络命名空间和PID命名空间,其他业务容器再“加入”这个沙箱,从而共享网络和进程空间。这是Kubernetes里一个非常重要的设计:Pod是逻辑单位,pause容器是物理底座。

沙箱到位后,kubelet继续通过CRI接口调用CreateContainerStartContainer,让containerd去创建真实的业务容器。containerd收到请求后,会把任务通过内部的task API交给运行时实现,最常见的是runc。这里还有一个关键进程容易被忽略:containerd-shim。containerd为每个容器都会拉起一个shim进程,它像一个“保姆”,负责维持容器进程的存活、转发信号、上报状态。即使containerd主进程重启,已经运行的容器也不会受影响,靠的就是shim。

1.3 节点上的实体进程与常用排查命令

理解了逻辑链路,我们再落到实体上。在一台健康的Kubernetes节点上,你执行ps -ef | grep -E "kubelet|containerd",通常能看到以下几类进程:

  • kubelet:Kubernetes的节点代理,负责管理Pod生命周期。
  • containerd:CRI运行时的主守护进程,常驻在后台。
  • containerd-shimcontainerd-shim-runc-v2:每个运行中的容器对应一个,数量和你容器数量基本一致。
  • runc或它的子进程:实际容器进程的父进程。

排查容器状态时,建议直接用crictl工具,它是专门为CRI定制的命令行工具。我常用的几个命令:

# 查看节点上的Pod列表(CRI视角) crictl pods # 查看所有容器 crictl ps -a # 查看容器详细信息 crictl inspect <container-id> # 查看容器日志 crictl logs <container-id>

很多新手在这里会遇到一个非常典型的坑:用ctr命令去看容器,结果什么都看不到,就以为containerd没在运行。其实ctr是containerd自己的客户端,它需要指定命名空间。Kubernetes管理的容器都放在k8s.io命名空间下,所以正确的查看命令是:

ctr -n k8s.io containers list

crictl则不需要指定,因为它默认连接kubelet使用的CRI socket,也就是unix:///run/containerd/containerd.sock。如果你在配置里改了containerd的socket路径,crictl也要跟着改,否则会连接失败。这是我在一次重构运行时配置时踩过的坑,后面故障排查部分还会再说。

2. 一套可以直接落地的 Kubernetes 集群部署流程

2.1 部署前的环境规划:版本、硬件、系统参数一次搞定

我见过太多人一上来就跟着网上教程敲命令,结果装到一半各种报错,本质是没理解每一步在干什么。部署Kubernetes不是装个软件那么简单,它涉及内核模块、系统参数、容器运行时和网络插件四个层面的协同。

版本选择上,我的经验是“稳定优先,但不追最新”。以当前主流版本线为例,Kubernetes 1.28到1.30之间,建议选择1.28.x或1.29.x的较新patch版本,因为经过多个patch迭代后,已知问题基本被修干净了。containerd建议选1.7.x,这是目前兼容性最好、社区维护最活跃的版本线。在动手之前,去官方查一下Kubernetes和containerd的版本兼容矩阵,这个动作花不了两分钟,但能避免后面很多莫名其妙的问题。

硬件上,测试环境最少三台机器:一台master,两台worker。配置不需要太高,我这边的参考是master节点4C8G,worker节点4C8G,每台机器50G系统盘。如果你的业务需要挂数据盘,再单独准备数据盘,不要一开始就把数据放到系统盘上,后面扩容很痛苦。操作系统我推荐Ubuntu 22.04 LTS,内核5.15以上,这个组合在兼容性上比较省心。

系统参数是很多新手忽略的一环。Kubernetes依赖overlaybr_netfilter两个内核模块,还需要开启IPv4转发。我用以下命令一次性搞定:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system

我见过不少集群部署失败,最后排查下来就是br_netfilter没加载,导致节点之间通信异常,Pod状态反复Pending或CrashLoopBackOff。所以这一步真的不要跳过。

2.2 containerd 配置与 kubeadm 部署实录

containerd安装好之后,默认没有配置文件,需要先初始化一份默认配置,然后改两个关键参数。

sudo containerd config default | sudo tee /etc/containerd/config.toml

打开配置文件,重点看两个地方。第一个是SystemdCgroup,必须改成true。Kubernetes默认使用systemd作为cgroup驱动,如果containerd还用cgroupfs,两个驱动不一致,kubelet会直接报错,Pod根本起不来。第二个是sandbox_image,默认是registry.k8s.io/pause:3.x。在国内网络环境下,这个镜像很可能拉不下来。我通常把它换成registry.aliyuncs.com/google_containers/pause:3.9之类的镜像地址。

改完之后重启containerd:

sudo systemctl restart containerd sudo systemctl enable containerd

然后安装kubeadm、kubelet、kubectl,指定版本安装,避免装到最新版导致后面版本错乱。安装完先不急着kubeadm init,先执行kubeadm config images pull验证镜像是否能拉下来,这一步能提前暴露网络问题。

master节点初始化命令我一般写成这样:

sudo kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --pod-network-cidr=10.244.0.0/16 \ --image-repository=registry.aliyuncs.com/google_containers

这里有个细节提一下:--pod-network-cidr必须和后续选择的CNI网络插件匹配。比如你用Flannel,就填10.244.0.0/16;用Calico,可以填192.168.0.0/16。如果填错了,CNI插件和kubelet的网段对不上,节点会一直处于NotReady状态。

初始化完成后,按提示执行mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config。然后装CNI插件,以Calico为例:

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/calico.yaml

等待所有Pod变成Running后,用kubeadm token create --print-join-command生成join命令,在worker节点上执行,就能把worker节点加进来了。

2.3 集群部署完成后的快速验证清单

很多新手装完集群,看到kubectl get nodes全是Ready就以为大功告成,其实还有几个隐藏点值得验证一下。

验证项命令预期结果
节点状态kubectl get nodes全部Ready
核心组件运行状态kubectl get pods -n kube-system全部Running或Completed
组件健康kubectl get cs'kubectl get componentstatuses各组件Healthy
DNS是否可用kubectl run test --image=busybox --rm -it -- nslookup kubernetes.default正常解析
跨节点Pod互通在两个不同节点部署Pod互ping ClusterIP网络通

其中DNS和跨节点通信这两个验证特别重要。DNS有问题,后面Service的域名解析会全部失败;跨节点Pod不能互通,说明CNI插件没有正常工作。尤其是Calico的BGP模式,如果节点之间防火墙没放行BGP端口,Pod能创建,但跨节点通信就是不通。这类问题用kubectl get pods -n kube-system看时,Calico的Pod状态可能还是Running,但实际已经半瘫了。

3. 安装 Dashboard 与项目生命周期管理完整实操

3.1 Kubernetes Dashboard 安装与访问遇到的坑

Dashboard是官方提供的Web UI,虽然生产环境很多团队不直接用它,但测试环境和给开发同事展示资源状态时,它非常方便。安装很简单:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v3.0.0-alpha0/deploy/recommended.yaml

如果你安装的是v2.x版本,对应的YAML地址要换成对应分支。安装完查看Pod是否正常:

kubectl get pods -n kubernetes-dashboard

默认情况下,Dashboard的Service是ClusterIP类型,集群外部访问不到。我习惯把它改成NodePort:

kubectl -n kubernetes-dashboard patch svc kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}' kubectl -n kubernetes-dashboard get svc

然后用https://<节点IP>:<NodePort>访问。这里有个很容易忽略的坑:Dashboard默认要求HTTPS,所以浏览器访问时一定要加https://,否则页面会直接拒绝连接或者报代理错误。

登录Dashboard需要token或kubeconfig。推荐创建一个专门的管理员用户,用token登录。命令如下:

kubectl create serviceaccount admin-user -n kubernetes-dashboard kubectl create clusterrolebinding admin-user --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:admin-user # 获取token kubectl -n kubernetes-dashboard create token admin-user

这里我再提一个很多人不知道的点:Dashboard里如果看不到CPU和内存的趋势图,不要怀疑Dashboard坏了,而是集群里没装metrics-server。Dashboard的监控图表依赖metrics-server采集指标,装完后图表才会显示数据。安装它也很简单:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

有条件的可以检查一下metrics-server的Pod日志,它连接Apiserver时如果因为证书校验失败报错,可以在部署文件里配置--kubelet-insecure-tls参数。这是测试环境很常见的兼容性问题。

3.2 项目生命周期管理:从命名空间创建到资源回收

Kubernetes里的“项目”通常对应一个Namespace。在我看来,一个项目的完整生命周期管理,至少包含六个阶段:创建命名空间、配置配额、绑定权限、部署业务、监控告警、下线回收。很多团队只用前两个阶段,后几个全凭自觉,结果项目失控时根本收不回来。

创建命名空间很简单:

kubectl create namespace project-alpha

但“创建”只是开始。如果项目之间有资源争抢的问题,你就得用ResourceQuota把命名空间的资源上限框住。下面是我生产环境里实际用过的示例:

apiVersion: v1 kind: ResourceQuota metadata: name: project-alpha-quota namespace: project-alpha spec: hard: requests.cpu: "4" requests.memory: 8Gi limits.cpu: "8" limits.memory: 16Gi persistentvolumeclaims: 10 pods: "20"

ResourceQuota负责控制“整个命名空间的总用量”,而LimitRange负责控制“单个Pod的默认资源”。两者配合使用,才能防止某个项目里出现一个Pod把所有资源都吃掉的极端情况。LimitRange的示例:

apiVersion: v1 kind: LimitRange metadata: name: project-alpha-limitrange namespace: project-alpha spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 100m memory: 128Mi type: Container

配置了LimitRange之后,如果项目里的Pod没有显式声明资源,系统会自动套用默认值,避免裸奔。

权限绑定这块,不要图方便直接给所有人绑定cluster-admin。推荐的做法是给项目成员创建单独的ServiceAccount,或者绑定限定到命名空间的Role与RoleBinding。举例来说,开发人员一般只需要在project-alpha里有编辑权限,那就创建一个Role,而不是ClusterRole。

3.3 项目下线回收与 Terminating 状态卡住的处理

项目下线的“最后一公里”往往最考验运维。很多人直接在kubectl delete namespace project-alpha之后就去干别的了,结果过一会儿回来发现命名空间一直卡在Terminating状态,非常常见。

为什么删不掉?核心原因是命名空间里还有资源没有被清理干净。常见的有这几种:有PV或PVC没释放、有自定义资源(CRD实例)还在、有异常的Webhook配置拦截删除请求。

我的排查套路是先看命名空间下还有哪些资源:

# 查看命名空间下所有资源 kubectl api-resources --verbs=list --namespaced -o name | xargs -n1 kubectl get --show-kind --ignore-not-found -n project-alpha

如果发现确实有资源残留,逐个处理。如果看不到明显的残留,但是命名空间还是卡在Terminating,我一般用以下方式兜底:先尝试kubectl get namespace project-alpha -o json,查看spec.finalizers字段。有时候finalizer被某个控制器挂住了,一直没释放。一个应急方案是把finalizer清掉强制删除,但操作前一定要确认业务数据已经备份。

提示:强制删除命名空间是最后手段,不要一遇到卡住就用。如果项目里还有Consul、Prometheus这些有状态服务,直接清finalizer可能把数据卷也搞丢。

项目生命周期里还有一条容易被忽略的线:数据。有状态服务下线时,PVC里的数据要不要留?PV的回收策略是Retain还是Delete?这些在上线前就应该定清楚。我的建议是,对于有保留价值的项目,下线前先对PV数据做快照或备份,再把PV的回收策略改成Retain,最后才执行命名空间删除。顺序反了,数据就再也找不回来了。

3.4 多项目隔离的实践:NetworkPolicy 不能只靠 Namespace

关于项目隔离,我一直想强调一个容易误解的点:Kubernetes默认的扁平网络模型下,不同Namespace的Pod是可以互相访问的。Namespace在资源隔离上有作用,但在网络层面,它不提供任何隔离。

要实现项目之间的网络隔离,必须依赖NetworkPolicy。它要求CNI插件支持,比如Calico就默认支持。举个例子,如果我只允许project-alpha的Pod访问project-beta里带role: db标签的Pod,其他流量全部拒绝,可以这样写:

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-alpha-to-beta namespace: project-beta spec: podSelector: matchLabels: role: db ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: project-alpha

这中间有个细节:namespaceSelector匹配的是命名空间本身的标签,不是Pod的标签。很多新手在这里写错,结果策略怎么都不生效。

如果你的团队对网络隔离有硬性要求,我建议在上线第一个项目前就把Calico的NetworkPolicy方案设计好,后面补策略容易出事故,因为默认拒绝和默认放行的切换期最容易误伤业务流量。

4. 高频故障排查实录与面试题实战

4.1 磁盘管理控制台视图不是最新状态:一次真实的磁盘扩容排查

标题这句话看起来像是Windows磁盘管理的错误提示,但它指向的问题本质我在Linux上同样遇到过:系统没有感知到底层磁盘容量已经变化。有次我需要给Kubernetes的worker节点扩容数据盘,在宿主机管理控制台把虚拟磁盘从100G扩到200G后,登录节点执行lsblk,发现设备容量还是100G。Windows上会直接弹“磁盘管理控制台视图不是最新状态”,Linux则表现为SCSI设备缓存未刷新。

处理方式其实很简单,手动刷新SCSI设备:

# 查看设备路径 ls /sys/class/scsi_device/ # 触发重新扫描 echo 1 > /sys/class/scsi_device/0:0:0:0/device/rescan # 然后查看磁盘容量 lsblk

如果系统已经识别到200G,但分区还是100G,接下来需要扩分区再扩文件系统。以/dev/vdb为例:

# 扩展分区 sudo growpart /dev/vdb 1 # 扩展文件系统(xfs和ext4命令不同) sudo xfs_growfs /mount/point # sudo resize2fs /dev/vdb1

在Kubernetes场景里,这一步做完之后还远远没结束。如果你用的是静态PV,需要在StorageClass或PV里同步调整容量;如果用的云厂商CSI插件,通常它会自动感知底层磁盘扩容并调用CSI扩展,但前提是StorageClass里配置了allowVolumeExpansion: true。自建裸金属集群就没这么智能了,扩容完文件系统后,可能会有Pod因为文件系统大小变化而被kubelet重新挂载。通常建议在业务低峰期操作,并且提前确认对应PVC的使用状态。

这个案例给我的教训是:排查问题要“由底向上”。先确认物理层容量,再确认分区和文件系统,最后才看Kubernetes层。如果一上来就查PVC和PV状态,方向就反了。

4.2 Kubernetes 面试高频题实战精讲

接下来整理几个我面试里经常问的Kubernetes问题,这里直接给出回答思路,重点在逻辑完整而不是背答案。

问题1:创建一个Pod的完整流程是什么?

管理面:kubectl → Apiserver(写入etcd)→ scheduler 选择合适的节点 → kubelet 感知到新Pod → 调用containerd的CRI接口创建pause沙箱和业务容器。注意有两个细节:Apiserver和etcd之间是有发布订阅机制的,scheduler和kubelet都通过watch机制获取事件,而不是轮询;pause容器先于业务容器创建,是Pod共享网络命名空间的基石。

问题2:kubelet 和 containerd 之间是什么关系?

kubelet是Kubernetes的节点代理,负责和集群控制面通信,管理Pod生命周期;containerd是容器运行时,按CRI规范执行容器的创建、启动、停止。两者之间通过gRPC通信,接口是CRI,socket默认在/run/containerd/containerd.sock。一句话:kubelet是“交警”,containerd是“引擎”,谁也不能取代谁。

问题3:Pod一直Pending,你从哪些方向排查?

这是最容易拉开差距的实际问题。我的排查顺序固定如下:

# 先看事件 kubectl describe pod <pod-name> # 再看节点资源 kubectl top nodes kubectl describe node <node-name>

事件信息里如果出现0/3 nodes are available,那基本就是资源不足、节点有污点、或者selector不匹配这几种情况,逐个排除。资源不足就检查节点CPU和内存余量;污点就用kubectl taint nodes --all查看,必要时用容忍度解决;selector不匹配就检查Pod的label和节点的label。还有一个容易被忽略的:如果describe里显示FailedScheduling但没有任何具体原因,很可能是集群里没装scheduler插件或者Apiserver和scheduler之间通信异常,先查kube-system里的scheduler Pod状态。

问题4:Deployment、StatefulSet、DaemonSet 分别适合什么场景?

Deployment适合无状态应用,强调副本数、滚动更新、快速回滚;StatefulSet适合有状态应用,强调稳定的网络标识、稳定的存储(PVC模板)、有序的启停;DaemonSet适合每个节点都需要的守护型服务,比如日志采集、监控探针。回答时如果能顺便说一句“StatefulSet的Pod名是有序的,比如web-0、web-1,因为它的网络身份和存储身份都相对固定”,就更具说服力。

问题5:Service 的负载均衡是怎么实现的?

Service本身有三种模式:ClusterIP、NodePort、LoadBalancer。最常见的ClusterIP模式下,kube-proxy负责把发往Service VIP的流量转发到后端Pod。转发模式有iptables和IPVS两种,IPVS支持更丰富的调度算法,性能也更好。回答时说清“Service是虚拟IP,kube-proxy通过iptables或IPVS规则实现转发,EndpointSlice实时维护后端Pod列表”就能覆盖大半考点。

4.3 运维排障的通用方法论:先把范围压到最小

最后再分享一个通用的排障思路。处理Kubernetes问题,最忌讳的是在不了解整体链路的情况下盲目操作。我个人的排障顺序是:先看事件,再看日志,最后看配置。

事件能告诉你“系统以为发生了什么”,日志能告诉你“组件实际做了什么”,配置能告诉你“意图和现实之间的偏差在哪里”。比如Pod一直CrashLoopBackOff,先kubectl describe pod看Last State和Exit Code,再kubectl logs -f --previous查看上一次退出前的日志,最后检查Deployment的配置和镜像标签是否真的存在于仓库里。三步走完,绝大多数问题都能定位到具体层。

我自己在实际操作中比较深的体会是,Kubernetes的坑大多是“配置不一致”引起的。containerd的cgroup驱动和kubelet不一致,集群起不来;CNI的Pod网段和init参数不一致,节点NotReady;CSI的allowVolumeExpansion没开,PVC扩不了容。这些问题没有一个需要“神级操作”才能解决,全靠排查时能不能沉住气一步步验证。所以我也建议刚上手的朋友,别急着在生产环境追求自动化、GitOps,先在测试集群里把一次从节点到Pod、从Pod到存储、从存储到网络的完整链路亲手走通,踩过几个真实的坑,你对Kubernetes操作管理的理解就会上一个台阶。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询