
写这篇东西之前我先说一句实在话K8sKubernetes这套东西劝退绝大多数人的不是它有多难而是它概念太多、太散官方文档基本是一本字典你翻完还是不知道该怎么下手。我自己当年学的时候也差点被Control Plane、Node、Pod、Service这些名词搞到怀疑人生。直到后来我换了个角度去看它发现K8s本质上就是一个操作系统只不过它管的不再是CPU、内存和文件而是一堆跑在云上的容器。这么一想很多东西一下就通了。这篇内容就是围绕“K8s是云原生时代的操作系统”这个类比展开的。我会先帮你把整个类比逻辑讲清楚再把K8s最核心的几个概念拆碎然后带你把一个最小可用的集群从零装起来中间会穿插我实际踩过的坑尤其那个“The API server is not healthy after 4m0.00747357s”的经典报错最后整理一份能直接用的排查手册。适合刚接触K8s、正在备考CKA、或者看完一堆理论还是不敢在生产环境动手的同学我保证你看完能少走很多弯路。1. 为什么说K8s是云原生时代的操作系统1.1 从操作系统的四大职责看K8s我们平时用的Windows、Linux往深了说操作系统就干四件事管理进程、调度资源、提供存储访问、控制权限边界。K8s干的事本质上是一模一样的只是把管理的对象换成了容器。先看进程管理。你在电脑上敲一个命令启动程序操作系统负责把它变成进程给它分配PID监控它的状态挂了之后清理掉。K8s里的Pod就是那个进程kubelet就是负责拉起和清理Pod的那个“内核小助手”。只不过它监控的粒度不是一个进程而是一组容器组成的Pod。你在电脑上CtrlC杀掉一个进程在K8s里就是kubectl delete pod。一一对应。再看资源调度。操作系统拿到一个程序要跑得先看CPU和内存够不够不够就排队够就给它分配时间片。K8s的kube-scheduler干的活完全一样每个新Pod要创建时它先遍历所有节点看哪台机器的CPU、内存剩余空间够满足亲和性要求就直接把这个Pod调度过去。你在电脑上开着软件管理器看内存占用K8s里就是kubectl top nodes。再看存储。操作系统把磁盘抽象成文件系统你不需要关心数据落在哪个扇区只管挂载目录就行。K8s把存储抽象成Volume和PV/PVCPod里的容器只管把数据写到挂载路径底层是本地盘、云硬盘还是NFS对应用透明。最后看权限。操作系统的用户、组、文件权限对应到K8s里就是RBAC、ServiceAccount和NetworkPolicy。谁能看、谁能改、谁能删一条条配清楚跟Linux下chmod一个思路。1.2 这个类比的价值在哪里很多人学K8s是被困在“记组件”这一步的。今天记一个Control Plane明天记一个etcd后天就忘。但如果你心里装着一幅“操作系统”的图K8s的每个组件就都有了自己的位置kube-apiserver就是系统调用接口所有命令、查询、变更都必须经过它它管所有入口。etcd就是注册表和配置中心整个集群的“记忆体”都放在这里面。kube-scheduler就是任务调度器决定新Pod跑在哪台机器上。kube-controller-manager就是系统守护进程不停检查当前状态和目标状态是不是一致不一致就调。这幅图一旦建立起来你后面看任何K8s架构文档都有坐标感不会迷路。更重要的是你会很快明白K8s到底帮你解决了什么问题单机上跑容器Docker就够一旦容器多到几十上百个部署、扩容、故障恢复、服务发现全成了体力活这时候才需要一个“集群操作系统”来统一管。2. K8s核心组件拆解一张图捋清控制面和工作节点2.1 控制平面组件逐个讲K8s集群分两拨角色控制平面Control Plane和工作节点Worker Node。控制平面就是“大脑”负责决策工作节点就是“手脚”负责执行。kube-apiserver是大脑里的“门卫总线”。整个集群所有的操作不管是用户通过kubectl发来的还是内部组件之间互相通信的全部要走它。它负责认证、鉴权、校验请求、然后把状态变更写入etcd。一句话apiserver是K8s里唯一允许碰etcd的组件。平时排查看日志集群有问题先看apiserver日志因为它链路上所有请求都要经过。etcd是K8s的“记忆体”。它是一个分布式的键值数据库整个集群的期望状态都存在这里包括你声明要跑几个副本、Deployment长什么样、Secret的内容、节点信息全在里面。如果你把etcd的数据清了整个集群就等于失忆了说白了就等于电脑丢了操作系统里的文件系统索引啥都起不来。所以生产环境etcd一定要做备份而且尽量和apiserver一起放独立节点上。kube-scheduler负责“排座位”。它盯着所有新建的、还没分配节点的Pod通过各种调度策略资源要求、节点标签、污点容忍选择最合适的节点。注意它只决定Pod跑哪台机器不负责真正启动Pod。kube-controller-manager是“纠偏器”。它里面跑了一堆控制器每个控制器都在做一件事实时比对期望状态和当前状态有差距就调整。比如你想在Deployment里跑3个副本但现在只有2个ReplicaSet控制器就会立刻去创建第3个。这个机制是整个K8s自愈能力的核心来源。cloud-controller-manager这个组件不是必装的它负责把K8s和云厂商的API对接起来比如在公有云上自动创建负载均衡器、存储卷。自建机房或者跑在虚拟机里通常不用管它。2.2 工作节点的三个核心部件工作节点上主要有三块每一块都直接决定了集群能不能正常运转。第一块是kubelet相当于每台服务器上的“常驻管家”。它负责接收apiserver下发的Pod定义然后调用容器运行时把Pod真正拉起来还要持续上报节点和Pod的状态。你排查问题的时候kubectl describe node看到一堆NotReady十有八九就是kubelet挂了或者是它的配置出了问题。第二块是kube-proxy负责维护节点上的网络转发规则。Service后面的Pod IP一直在变kube-proxy就盯着Service定义把访问Service ClusterIP的流量转发到后端某个健康的Pod上。它实现方式有iptables模式和IPVS模式生产环境建议用IPVS规则多的时候性能更稳。我在一台机器上部署Redis三主三从时开了几百条iptables规则明显感觉转发延迟有点不对换成IPVS之后就顺了。第三块是容器运行时Container Runtime真正干活的人。以前大家默认是Docker但K8s 1.24之后就默认用containerd了。containerd轻量kubelet通过CRI直接和它通信不再需要Docker那一整套守护进程。你平时还是可以用docker build打镜像但集群里运行容器的活儿默认都是containerd在干。这里有个常见的坑你机器上装了Docker但kubelet连不上containerd的CRI socket初始化就会直接报错。2.3 一次Pod创建请求的完整旅行光记组件没感觉我带你走一遍流程。你输入kubectl run nginx --imagenginx这条命令送到kube-apiserver。apiserver先做认证鉴权再校验这个Pod的配置然后把Pod的期望状态写入etcd。scheduler一直在watch apiserver发现有新的Pod没分配节点立刻按策略挑一个最合适的节点Pod定义里写上nodeName。接着那台节点上的kubelet也通过watch拿到了这个Pod就通知containerd拉镜像、创建容器。容器启动成功后kubelet再上报状态给apiserverapiserver再写回etcd。整个过程全部是异步的从你敲命令到Pod Running正常情况下也就几秒钟。这套链路你只要记住了后面遇到“Pod一直Pending”“创建了Pod一直显示ContainerCreating”这类问题顺着链路排查就能快速定位。3. 最小集群实操从Rocky Linux到第一个Pod跑起来3.1 环境规划别贪多学K8s我强烈建议用自己的虚拟机搭一套完整的三节点集群别用单机的Minikube糊弄自己。原因很简单单机版暴露不了网络插件的问题、跨节点Pod通信的问题而这些恰恰是生产环境最容易炸的地方。我自己平时练习用的是一台内存16G的笔记本跑三台2核4G的虚拟机非常稳。系统推荐Rocky Linux 9兼容性和稳定性都很好。硬件充裕就两台master一台worker紧张就一个master一个worker最小可用集群只需要两个节点。需要注意master节点默认是不调度Pod的单worker模式下要取消这个限制命令是kubectl taint nodes --all node-role.kubernetes.io/master-后来版本改成了control-plane具体看版本提示踩过一次就记住了。节点规划参考如下k8s-master 192.168.10.10系统盘40G内存4G承担控制平面全部组件。k8s-worker1 192.168.10.11系统盘40G内存4G跑业务Pod。两台机器同一网段关闭防火墙、SELinux和swap保证kubelet和kubeadm初始化不被卡住。3.2 初始化前的三件套换源、装运行时、改内核参数整个K8s安装最磨人的不是kubeadm init那条命令本身而是它之前的准备。准备好了init一次过没准备好init完各种花式报错。第一步配置yum源。国内直接访问默认源太慢我习惯先把系统源和K8s的yum源都换成国内的。装K8s要用的三个包是kubelet、kubeadm、kubectl版本一定要固定不能随便升级。第二步装containerd。直接用yum install containerd.io装完执行containerd config default /etc/containerd/config.toml生成默认配置。这里关键的一步是把Sandbox镜像地址里的k8s.gcr.io改成registry.aliyuncs.com/google_containers。这个镜像名就叫pause是用来给Pod提供网络命名空间和PID命名空间共享的地址不对后面所有Pod都起不来。我见过不少新手init成功了结果Pod一直ContainerCreating日志里就挂着拉pause镜像失败原因就在这里。第三步改内核参数。这里有两条核心配置net.bridge.bridge-nf-call-iptables1和net.ipv4.ip_forward1。前者保证K8s的iptables规则能作用于桥接流量后者让容器网络能正向转发。不配好这两个集群内部DNS和Service访问大概率有诡异的问题。配置完sysctl --system生效。3.3 kubeadm init的参数和完整过程环境准备妥当在master节点上执行初始化kubeadm init \ --apiserver-advertise-address192.168.10.10 \ --image-repository registry.aliyuncs.com/google_containers \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.28.2三个参数各有讲究apiserver-advertise-address就是让其他节点通过这个IP访问到你master的apiserver必须填master实际IP不能写127.0.0.1。image-repository刚才提到过国内替换镜像源的关键不配的话会去拉谷歌的镜像直接卡死。pod-network-cidr这是给Pod网段留的地址空间我用的10.244.0.0/16是因为后面要装FlannelFlannel默认就用这个段。如果你打算用Calico可以换成192.168.0.0/16这就是为什么我看网上教程参数各不相同的原因。init过程大概两三分钟期间kubeadm会拉一堆镜像、生成证书、初始化etcd和控制平面组件。看到“Your Kubernetes control-plane has initialized successfully”这行输出就是成功了。成功之后按提示执行mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config然后把生成的加入指令在worker节点上跑一遍注意那条命令里token有有效期默认24小时过期就得用kubeadm token create重新生成。3.4 经典报错实录The API server is not healthy after 4m0.00747357s这个报错如果你去搜会发现有无数人遇到过原因也五花八门。它出现在kubeadm init中途或者结束前表面意思是apiserver组件在四分钟等待后没有变健康。但真正的问题往往不在apiserver本身而在它起不来或者起来后又退出的底层原因。我实际复现过几种常见的第一种容器运行时没配好。kubelet和containerd的cgroup driver不一致kubelet直接报错apiserver根本起不来。解决方法是确认containerd的SystemdCgroup参数改成true然后重启containerd和kubelet。第二种镜像没拉下来。虽然你指定了国内的registry但某些镜像版本不匹配或者网络慢kubelet超时。这时候非常建议先手动跑一下kubeadm config images pull把镜像提前拉完再执行init成功率高很多。第三种CPU内存不足。apiserver是控制平面里最吃资源的组件节点内存低于2G基本起不来。曾经为了省钱开一台1G内存的轻量服务器试初始化apiserver反复OOM等多久都是Not Healthy。排查时就用三连招先systemctl status kubelet看进程活了没再journalctl -u kubelet -n 100看kubelet到底卡在哪最后看容器日志crictl ps -a和crictl logs三条命令能覆盖掉绝大多数原因。别一看到apiserver unhealthy就慌它是“受害者”不是“凶手”。3.5 网络插件Flannel和Calico的选择控制平面起来了集群还是哑的因为节点间的Pod网络还没建好。这里必须装CNI插件我用Flannel比较多。Flannel的安装非常简单kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml装完等一分钟用kubectl get pods -n kube-flannel确认三个Flannel Pod都Running了然后kubectl get nodes应该就是Ready。Flannel的优点是轻量、配置少缺点是性能一般、不支持NetworkPolicy。跑正式环境、对网络隔离有要求就上Calico功能全很多但资源占用更大、排错也更复杂。新手练习阶段Flannel够用了。这里还有个小坑要么先装网络插件再执行join要么join后马上装总之别让worker节点等太久。不过这一步慢一点K8s会自动重试不用太焦虑。3.6 验证部署第一个Nginx并暴露端口集群Ready之后跑一个最基本的应用验证整个链路kubectl create deployment nginx --imagenginx kubectl expose deployment nginx --port80 --typeNodePort kubectl get svc nginxNodePort类型的Service会给nginx分配一个集群端口比如31713。这时候在浏览器里访问任意节点的IP加这个端口看到Nginx欢迎页就说明容器调度、集群网络、节点间通信全通了。这一步走通了恭喜你K8s基本体系你已经掌握了一半。4. 用操作系统的思维理解K8s核心对象4.1 Namespace就是多用户隔离K8s里的Namespace你用操作系统的“多用户”去理解最贴切。Linux里多个用户共用一台机器彼此的文件互不可见权限受控。K8s的Namespace就是逻辑上的隔离边界开一个Namespace等于开一个独立的小房间里面的Pod、Deployment、Service互相独立删除一个Namespace会把里面所有资源一起清掉。它同时也是权限控制的边界。集群管理员给不同团队创建一个Namespace再配合RBAC分配不同账号只允许操作自己的Namespace这已经是云原生微服务团队的标准协作方式了。练习时你可以创建新的Namespace试试kubectl create ns dev kubectl create deployment nginx --imagenginx -n dev注意-n参数以后你查Pod不带-n就默认看default里的结果发现“Pod怎么不见了”其实就在别的Namespace里。这个坑我踩到过很多次尤其是接手别人的集群时。4.2 Deployment和StatefulSet无状态和有状态的区别Deployment管理的Pod是“一次性的牛马”挂了一个立刻拉一个新的IP变了也无所谓适合跑无状态应用比如Web后端、API服务。它通过ReplicaSet控制副本数实现滚动更新和快速回滚。日常开发中90%的场景都用Deployment这也是为什么你看到的所有K8s入门教程都拿它举例。但有状态应用就要用StatefulSet典型就是Redis集群、MySQL主从、Kafka。这些应用每个实例需要稳定的身份和独立的存储重启后名字不能变、数据不能丢。StatefulSet给每个Pod一个稳定网络标识比如redis-0、redis-1并且每个Pod都绑定单独的PVCPod挂掉重建后名字和存储都保持原样。这就是为什么搭Redis集群时不会有人用Deployment。K8s对此有一个形象的叫法Deployment管的是“牲畜”挂了大不了再养一头StatefulSet管的是“宠物”有名字有身份得精心照顾。4.3 Service集群内部的稳定门牌号Pod的IP是不稳定的这在容器世界里是常态。但你的应用被别的服务访问时总不能用“碰运气”的方式去连着看IP变没变。Service就是解决这个问题的它给一组Pod提供一个稳定的虚拟IP和DNS名字。你可以把它理解成操作系统的“端口映射表”客户端访问Service的ClusterIP流量通过kube-proxy转发给后端某个健康Pod。K8s里的服务发现也依赖ServicePod只要通过服务名就能找到对方。比如Redis客户端的配置文件里写redissvc而不是某个IP那么后端Pod不管怎么漂移客户端不受影响。这几天网上有人问K8s和Docker到底啥关系其实一个最简单的总结就是Docker是帮你打集装箱的K8s是管理所有集装箱的港口系统。Service就是港口里的调度线路让货物能准确找到要上的船。5. 多维度对比K8s和Docker的真实分工5.1 两者根本不是同一层的东西这个问题我几乎每周都会看到新人问。一句话Docker和K8s解决的问题不同Docker解决的是单个容器怎么打包、怎么运行K8s解决的是海量容器怎么调度、怎么编排、怎么自愈。你拿Docker Compose在一台机器上编排几个服务还行一旦服务数量上到几十机器数量上到十几但又没有K8s统一调度的时候运维就是噩梦节点挂了上面的容器全灭手动恢复纯靠运气扩容全靠熬夜。K8s的出现就是把这些机械性工作全部自动化。5.2 K8s 1.24之后的变化Docker没死很多人听到“K8s移除Docker支持”理解为Docker没用了这是误解。K8s只是不再直接用Docker来运行容器而是改用containerd作为默认容器运行时。但是Docker镜像标准依然存在containerd照样能拉取Docker构建的镜像你日常docker build流程照旧只是运行容器的那一步换成containerd了。对你实际的影响就是K8s集群里不需要安装完整版Docker只需要containerd你常用的docker ps命令也不再用得换成crictl ps。习惯这个变化之后你会发现containerd更轻、更稳定故障率比Docker daemon低一些对生产环境反而是个好消息。6. 常见问题排查与速查手册6.1 高频报错速查表我把自己和身边同事踩过得高频问题整理成了一张表你排查时可以直接参照现象常见原因排查命令解决办法Pod一直Pending节点资源不足或调度约束不满足kubectl describe pod xxx 看Events检查节点资源、去掉无谓的nodeSelector、取消污点Pod一直ContainerCreating镜像拉不下来或者CRI配置错误kubectl describe pod xxxjournalctl -u kubelet确认镜像地址可访问检查containerd配置Pod CrashLoopBackOff应用启动即退出或启动命令错误kubectl logs pod名看日志、调整livenessProbe/startupProbe节点NotReadykubelet挂掉、网络插件异常、资源不足kubectl get nodessystemctl status kubelet先看kubelet服务状态再看CNI插件Pod状态API server not healthy见3.4节原因多种journalctl -u kubeletcrictl ps -a逐一排沙、先保证CRI配置正确Service访问不通kube-proxy异常或网络插件断kubectl get svc检查节点规则重启kube-proxy Pod、重装CNI插件6.2 三大必会排查命令排查K8s问题就像看家庭医生用这套流程能覆盖大多数病症。第一招是kubectl get events列出一个资源对象的最近事件流。比如Pod起不来Events里会写明白卡在什么环节是调度失败、镜像拉取失败还是探针失败直接定位节点。第二招是kubectl describe它比get更详细会把对象的完整配置、状态、条件、关联事件全部打印出来。Pod Pending时describe一下一目了然地看到被哪个条件卡住了。第三招是kubectl logs看应用日志这是判断“容器起来了但里面出了毛病”的唯一手段。查看过去Pod崩溃前的日志要加-p参数。这三个命令是需要形成肌肉记忆的比背组件更有用。6.3 三个被忽略的系统级坑防火墙和SELinux是新手最容易忽略的。K8s组件之间的通信端口非常多apiserver的6443、kubelet的10250各节点之间都要互访。你要么全关防火墙要么仔细打放行规则二选一没有中间态。SELinux如果不关kubelet经常没有权限读写某些文件表现为你配什么都对Pod就是起不来。不管什么教程装上系统后的第一件事就是setenforce 0和sed -i改config文件这是经过无数人验证的。Swap也必须关。K8s的设计前提是内存调度由它自己管swap存在会让scheduler的判断失真容器内存Limit也失去了意义。如果你没关swapkubeadm init或join时往往会直接失败或者在运行中报内存异常。装完系统第一时间swapoff -a并注释掉/etc/fstab里的swap行。6.4 我的建议先跑起来再深入理解给新手一条靠谱的学习路线先按照上面的步骤把集群装起来把Nginx、Redis集群这些常见的应用跑一遍跑起来之后再回头看官方文档这时候你的脑子就不再是空转的而是带着问题在找答案。我见过太多人一上来就拿着三百页文档往死里啃啃完转头就忘。K8s这东西用了才会理解理解了才用得稳。我个人现在的习惯是先把Pod调度的基础概念和图景建立好再深入到控制器源码、调度策略、网络插件原理这些深水区去。装集群这件事现在熟练到从零到Ready也用不到半小时都是那道坎过去了才有的底气。到时候你再回头看“K8s是云原生时代的操作系统”这句话应该会心一笑这不就是一台自己会修、会扩容、会自我恢复的巨型云上电脑嘛。