资讯动态

kubelet如何调用containerd?Kubernetes操作管理核心解析

发布时间:2026/9/9 9:41:24 来源:尧图企业网站定制
最近有个刚转到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通过PLEGPod Lifecycle Event Generator机制周期性地检测自己管理的Pod状态。发现新Pod需要创建后kubelet会调用CRI插件暴露的gRPC接口向containerd发送RunPodSandbox请求。这一步先创建一个“沙箱”也就是Pod的基础设施容器——我们常说的pause容器。pause容器是整个Pod里最先启动的容器它负责持有网络命名空间和PID命名空间其他业务容器再“加入”这个沙箱从而共享网络和进程空间。这是Kubernetes里一个非常重要的设计Pod是逻辑单位pause容器是物理底座。沙箱到位后kubelet继续通过CRI接口调用CreateContainer和StartContainer让containerd去创建真实的业务容器。containerd收到请求后会把任务通过内部的task API交给运行时实现最常见的是runc。这里还有一个关键进程容易被忽略containerd-shim。containerd为每个容器都会拉起一个shim进程它像一个“保姆”负责维持容器进程的存活、转发信号、上报状态。即使containerd主进程重启已经运行的容器也不会受影响靠的就是shim。1.3 节点上的实体进程与常用排查命令理解了逻辑链路我们再落到实体上。在一台健康的Kubernetes节点上你执行ps -ef | grep -E kubelet|containerd通常能看到以下几类进程kubeletKubernetes的节点代理负责管理Pod生命周期。containerdCRI运行时的主守护进程常驻在后台。containerd-shim或containerd-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 listcrictl则不需要指定因为它默认连接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节点4C8Gworker节点4C8G每台机器50G系统盘。如果你的业务需要挂数据盘再单独准备数据盘不要一开始就把数据放到系统盘上后面扩容很痛苦。操作系统我推荐Ubuntu 22.04 LTS内核5.15以上这个组合在兼容性上比较省心。系统参数是很多新手忽略的一环。Kubernetes依赖overlay和br_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之类的镜像地址。改完之后重启containerdsudo systemctl restart containerd sudo systemctl enable containerd然后安装kubeadm、kubelet、kubectl指定版本安装避免装到最新版导致后面版本错乱。安装完先不急着kubeadm init先执行kubeadm config images pull验证镜像是否能拉下来这一步能提前暴露网络问题。master节点初始化命令我一般写成这样sudo kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --image-repositoryregistry.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/.kube、sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config、sudo 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各组件HealthyDNS是否可用kubectl run test --imagebusybox --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类型集群外部访问不到。我习惯把它改成NodePortkubectl -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 --clusterrolecluster-admin --serviceaccountkubernetes-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: 20ResourceQuota负责控制“整个命名空间的总用量”而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 --verbslist --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共享网络命名空间的基石。问题2kubelet 和 containerd 之间是什么关系kubelet是Kubernetes的节点代理负责和集群控制面通信管理Pod生命周期containerd是容器运行时按CRI规范执行容器的创建、启动、停止。两者之间通过gRPC通信接口是CRIsocket默认在/run/containerd/containerd.sock。一句话kubelet是“交警”containerd是“引擎”谁也不能取代谁。问题3Pod一直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状态。问题4Deployment、StatefulSet、DaemonSet 分别适合什么场景Deployment适合无状态应用强调副本数、滚动更新、快速回滚StatefulSet适合有状态应用强调稳定的网络标识、稳定的存储PVC模板、有序的启停DaemonSet适合每个节点都需要的守护型服务比如日志采集、监控探针。回答时如果能顺便说一句“StatefulSet的Pod名是有序的比如web-0、web-1因为它的网络身份和存储身份都相对固定”就更具说服力。问题5Service 的负载均衡是怎么实现的Service本身有三种模式ClusterIP、NodePort、LoadBalancer。最常见的ClusterIP模式下kube-proxy负责把发往Service VIP的流量转发到后端Pod。转发模式有iptables和IPVS两种IPVS支持更丰富的调度算法性能也更好。回答时说清“Service是虚拟IPkube-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参数不一致节点NotReadyCSI的allowVolumeExpansion没开PVC扩不了容。这些问题没有一个需要“神级操作”才能解决全靠排查时能不能沉住气一步步验证。所以我也建议刚上手的朋友别急着在生产环境追求自动化、GitOps先在测试集群里把一次从节点到Pod、从Pod到存储、从存储到网络的完整链路亲手走通踩过几个真实的坑你对Kubernetes操作管理的理解就会上一个台阶。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价