1. 为什么这期笔记值得你停下来看完说实话Kubernetes下文统一叫 k8s这个生态实在太大了大到很多人学了半年还在“会敲命令但是不会排障”的阶段。我见过不少运维同行能把kubectl get pods背得滚瓜烂熟但一遇到 Pod 调度不上去、集群状态异常、网络不通这类问题立马就抓瞎。这期笔记我按照自己这几年在生产环境里实际踩坑、拆解、再复盘的经验把 k8s 最核心的几个模块重新梳理了一遍。不是那种“官方文档翻译版”也不是“照着安装文档一步步点下一步”的操作流水账而是把每个组件为什么存在、每个请求怎么流转、每条指令背后到底发生了什么讲清楚。适合三类人看刚入门 k8s、打算系统学习而不是零散抄命令的运维新手已经能跑起来一个集群但对调度、网络、存储等内部机制“知其然不知其所以然”的开发或运维准备面试、需要把 k8s 核心原理用大白话讲出来的求职者。另外要提前说一句这期虽然是“第5期”但我不会假设你已经看过前面几期。文章会尽量自包含遇到必须用到的背景概念我会用简短的类比带你过一遍。你完全可以把它当作一份独立的技术笔记来读。2. 先把 k8s 的“骨架”搭在脑子里2.1 控制面与工作节点的分工逻辑很多朋友一开始学 k8s喜欢直接去背 etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet 这些名字。背是背下来了但问一句“它们之间是怎么配合的”就答不上来。我的经验是别急着背名字先把 k8s 当成一个“公司”来理解。这个公司里有两类角色一类是管理层另一类是干活的人。管理层不直接跑业务只负责做决策、记台账、下发指令干活的人收到指令后执行并定期向管理层汇报状态。在 k8s 里管理层就是控制面Control Plane也就是你常听到的 Master 节点上的那几个组件。干活的人就是**工作节点Worker Node**上的 kubelet 和容器运行时。控制面里最重要的是四件事etcd公司的档案室所有状态、配置、期望值都记在这里kube-apiserver公司前台所有请求都要从这里进、从这里出kube-scheduler调度官决定某个新来的任务派给哪个员工kube-controller-manager监工不断对比“台账上写的期望状态”和“现场实际状态”发现不一致就动手纠正。这四个组件各司其职缺一个都不行。举个例子你执行kubectl apply创建了一个 Deployment这份“期望状态”首先会被 kube-apiserver 校验并写入 etcd。kube-scheduler 发现有一个新的 Pod 等着被分配节点就按规则把它调度到某个工作节点。这个节点上的 kubelet 收到指令后调用容器运行时把容器拉起来。整个过程里kube-controller-manager 一直在后台盯梢副本数够不够某个节点挂了需不需要重新调度这些修复动作都是它发起的。2.2 组件间通信必须明确这几条路径理解了角色分工第二步就是搞懂“谁和谁说话”。我在实际排障中遇到过太多人一看到网络不通就去翻防火墙却不知道 k8s 组件之间的通信是有固定路径的。第一条路径kubectl → kube-apiserver。这是你作为管理员最常打交道的路径。所有kubectl命令实质都是向 kube-apiserver 发起 HTTP 请求apiserver 会做认证、授权、准入控制然后才会真正执行。第二条路径kubelet → kube-apiserver。每个工作节点上的 kubelet 主动连接 apiserver监听与自己相关的 Pod 变化。注意这个方向是 kubelet 主动连 apiserver而不是反过来。很多新手以为 Master 能直接 SSH 到节点上操作 kubelet其实不是这样。第三条路径kube-scheduler、kube-controller-manager → kube-apiserver。这两个组件也是通过 apiserver 来读写集群状态。它们不直接访问 etcd甚至不应该直接访问 etcd所有状态变更都要过 apiserver 这一层。第四条路径kube-proxy → kube-apiserver。kube-proxy 监听 Service 和 Endpoint 的变化然后在自己所在的节点上维护转发规则。搞清楚这几条路径你的排障思路就会清晰很多。比如某个节点上的 Pod 一直无法创建你先查的不是这个节点是不是挂了而是kubelet 到 apiserver 的链路是否正常、证书是否过期、kubelet 日志里有没有报错。提示kube-apiserver 是整个集群的唯一入口所有组件都通过它交互。生产环境里一定要给 apiserver 做高可用etcd 也要单独考虑容灾这是 k8s 集群稳定的底线。3. 工作负载资源不是只有 Pod 这一种“套餐”3.1 Pod 是“最小调度单位”而不是“最小容器单位”很多人刚开始学 k8s 的时候会被“Pod 是最小调度单位”这句话绕晕。明明 Docker 容器才是最小运行单位怎么到了 k8s 里 Pod 反而变成了最小单位这里我用一个生活化的类比来解释。假设 Pod 是一套合租房里面的每个容器是租客。合租房有一套公共设施——比如网络端口、存储卷、Linux 命名空间的一部分——所有租客共用。k8s 调度的时候不会单独把一个租客派到某个房子去而是把整套合租房作为整体派过去。所以 Pod 里可以只有一个容器也可以有多个容器。当一个 Pod 里有多个容器时它们必然运行在同一台节点上共享网络栈同一个 IP 和端口空间、共享存储卷。这种设计最典型的应用场景是边车模式Sidecar主容器负责业务逻辑边车容器负责日志采集、流量转发、健康检查辅助等辅助功能。但我要提醒一点不要为了“多个容器”而刻意把不相干的服务塞进同一个 Pod。Pod 里的多个容器应该属于同一个生命周期、需要部署到同一台机器、需要共享资源。如果你只是想把两个服务放一起部署应该用两个独立的 Deployment而不是硬塞进一个 Pod。3.2 Deployment、StatefulSet、DaemonSet 怎么选明白了 Pod 是什么接着就要学“谁来管理 Pod”。k8s 里最常用的工作负载资源有三种我整理了一个对比表格方便你直接保存资源类型适合场景副本数是否可变网络标识是否固定升级方式Deployment无状态服务Web、API、微服务可自由伸缩不保证固定Pod 重建后 IP 变化RollingUpdate 滚动更新StatefulSet有状态服务数据库、消息队列、Redis 集群可伸缩但需要注意顺序有稳定网络标识Pod 名称固定按顺序逐个升级DaemonSet每个节点都需要运行一个副本日志采集、监控 Agent、网络插件通常随节点数量变化每节点固定一个RollingUpdate我踩过的一个典型坑是把有状态服务用 Deployment 来跑结果 Pod 重建后 IP 变了集群里的其他服务拿着旧 IP 去连接直接连接失败。后来改用 StatefulSetPod 名称和网络标识稳定问题才彻底解决。选型时记住一条经验法则你的服务需不需要稳定的网络标识和独立的存储需要就选 StatefulSet如果所有 Pod 完全等价、挂了一个随便拉起一个就能顶上选 Deployment如果每个节点都得有一份副本选 DaemonSet。3.3 控制器模式k8s 的“永动机”哲学我经常在面试里问候选人一个问题“k8s 是怎么保证你的 Nginx 副本数永远是 3 的”很多人答不上来或者只会说“有控制器”。其实 k8s 的核心思想就四个字声明式管理。你告诉系统“我要三个副本”这个期望值被写入 etcd。Controller Manager 里的 Deployment 控制器持续监控当前状态如果某个 Pod 挂了实际副本数变成 2控制器就会创建一个新 Pod 来把数量补齐如果实际副本数变成 4比如有人手动创建了同标签的 Pod控制器会主动删除多出来的。这个过程是无限循环的期望状态 对比 实际状态 → 发现差异 → 执行变更 → 重新对比。只要集群还活着这个循环就一直在跑。理解了这一点你就能理解为什么 k8s 里很多东西“删不掉”或者“改了又被改回来”——不是系统出 bug 了而是有控制器在维持期望状态。我自己在实际操作中的体会是遇到“配置改了但很快被还原”的情况先去查是哪个控制器在管这个资源而不是反复去改 YAML。比如你想临时缩容一个 Deployment如果直接去删 Pod控制器会立刻拉一个新 Pod 出来正确做法是修改 Deployment 的副本数让控制器自己去做调整。4. 调度、网络与存储三大硬骨头逐个啃4.1 调度器的工作逻辑与自定义规则kube-scheduler 是负责“把 Pod 放到哪台节点”的组件。放得好不好直接影响整个集群的资源利用率和稳定性。调度流程分两步过滤Filtering和打分Scoring。过滤阶段把不满足条件的节点直接排除比如节点资源不足、端口冲突、不满足 nodeSelector 等。打分阶段对剩余节点按优先级排序选出最合适的节点。我在生产环境里最常用的自定义调度规则有三种nodeSelector最简单的节点选择方式靠节点的标签来匹配。比如给 GPU 节点打上gputrue标签GPU 任务就指定调度到这些节点。节点亲和性nodeAffinity比 nodeSelector 更灵活支持硬性要求requiredDuringScheduling和软性偏好preferredDuringScheduling。Pod 间亲和性与反亲和性控制 Pod 之间的部署关系。比如希望两个 Pod 尽量在同一个节点减少网络延迟或者希望一个服务的多个副本分布在不同节点提高可用性。我特别想强调反亲和性的重要性。有一个客户曾经把所有副本调度到了同一台节点节点一宕整个服务全部不可用。后来加了 Pod 反亲和性规则指定同一个 Deployment 的副本必须分散到不同节点可用性明显提升。4.2 Service 与 kube-proxy稳定入口怎么实现Pod 是“短命”的随时可能被销毁、重建、迁移。那其他服务怎么访问它答案就是 Service。Service 为一组 Pod 提供一个稳定的虚拟 IPClusterIP并负责负载均衡。它的实现原理是kube-proxy 监听 apiserver 中 Service 和 Endpoint 的变化然后在每个节点上写入 iptables 或 IPVS 规则把发往 Service IP 的流量转发到对应的 Pod IP。很多人问为什么不直接访问 Pod IP原因很简单Pod IP 不固定Pod 重建后 IP 就变了。而 Service 的 IP 在整个生命周期内保持不变只要 Service 存在客户端就不用关心后端 Pod 怎么变化。关于 Service 的类型我做个小总结ClusterIP默认类型只能在集群内部访问NodePort在每个节点上开一个端口外部流量通过节点IP:NodePort访问适合测试环境LoadBalancer借助云厂商的负载均衡器把流量引入集群适合公有云环境ExternalName把 Service 映射到集群外部的域名适合访问外部服务。4.3 存储抽象从 PV、PVC 到 StorageClass有状态服务绕不开存储。很多新手在配 PV/PVC 的时候一脸懵其实原理不复杂。PVPersistentVolume集群里的存储资源就像一块“硬盘”PVCPersistentVolumeClaim使用存储的申请单就像“我要 10G 空间”StorageClass动态供给的模板PVC 声明了 StorageClass 后系统会自动创建 PV不用管理员提前手工准备存储。我用一个生活类比帮你串起来你需要一块硬盘来存数据于是填了一张申请单PVC上面写着“我要 10G、读写一次”。管理员看到申请单后从仓库里挑了一块合适的硬盘PV分配给你。如果仓库里的硬盘不够了而系统配置了 StorageClass它就会自动买一块新硬盘动态创建 PV来满足申请。生产环境里我强烈建议使用 StorageClass 做动态供给而不是手动创建 PV。手动创建 PV 的方式在测试环境没问题但到了大规模环境存储需求变化非常快手动维护 PV 会变成一场灾难。4.4 网络插件怎么选Calico vs Flannelk8s 集群必须安装一个 CNI 网络插件否则 Pod 之间无法互通。我用过的、也是当前主流的两大选择是 Flannel 和 Calico。Flannel 实现简单、性能开销低适合中小规模集群。它使用 VXLAN 等 Overlay 技术在不同节点之间建立虚拟网络配置起来很容易排障也比较直观。但 Flannel 不支持 NetworkPolicy网络策略如果你需要做细粒度的网络隔离和访问控制它就不合适了。Calico 则更强大支持 NetworkPolicy网络模式更多元BGP 直连模式性能更好。代价是架构更复杂排障门槛更高。我在生产环境里的选型经验是集群规模小小于 50 节点、没有网络策略需求 → Flannel需要 NetworkPolicy、规模较大、对性能有要求 → Calico。注意网络插件一定要在集群初始化后尽早安装。我见过有人在装完应用之后再回头装 CNI最后 Pod 一直处于 ContainerCreating因为网络一直没就绪排查起来非常痛苦。5. 动手实操从零部署一个高可用 k8s 集群的关键步骤5.1 环境准备与版本选型纸上谈兵再多不如动手做一遍。这里我以最常见的生产部署方式为例演示从零搭建一个三节点高可用集群的核心步骤。注意以下步骤演示的是思路和关键操作实际生产环境建议使用专业的部署工具来简化流程。先说环境准备。我推荐至少准备 3 台 Linux 服务器2C4G 以上操作系统选择主流的发行版例如 CentOS Stream 或 Ubuntu Server。三台机器的规划如下3 台机器都部署 kubelet 和容器运行时比如 containerd既是 Master 也是 Worker适合中小规模集群生产环境如果条件允许建议把 Master 和 Worker 分开避免控制面资源被业务挤占。接下来做基础配置。每一台机器都需要关闭交换分区swap因为 k8s 要求节点关闭 swap 才能正常工作。同时需要加载几个内核模块overlay和br_netfilter前者负责容器文件系统层后者负责网络桥接。这些配置完成后还需要修改sysctl参数开启 IP 转发并让 iptables 能正确处理桥接流量。容器运行时我推荐 containerd。相比 Dockercontainerd 更轻量也是当前 k8s 默认支持的运行时。安装完成后需要配置crictl客户端工具方便在节点上查看容器状态。5.2 使用部署工具完成控制面初始化k8s 集群初始化最核心的命令是kubeadm init。在正式执行之前需要准备好一个配置文件告诉集群控制面地址、Pod 网段、Service 网段等信息。这里有个我一直强调的重点Pod 网段和 Service 网段绝对不能和你的物理网络冲突否则后面各种诡异网络问题都会冒出来。初始化完成后kubeadm 会输出一段提示包含两件重要的事情如何配置kubectl的 kubeconfig 文件让 kubectl 能连接集群如何生成工作节点加入集群的命令。生成一条新的加入命令时需要先创建一个新的 token因为旧 token 会过期。加入命令格式大致如下实际使用时需要用自己的 token 和证书哈希替换kubeadm join 192.168.x.x:6443 --token token --discovery-token-ca-cert-hash sha256:hash每个工作节点执行这条命令后会向控制面发起加入请求kubelet 会自动完成证书签发和组件启动。5.3 验证集群状态与安装 CNI集群初始化完成、节点都加入之后先用下面这条命令确认节点状态kubectl get nodes如果看到所有节点都是Ready状态说明集群本身是健康的。但此时 Pod 网络还是不通的因为还没有安装 CNI 插件。按照前面说的选型原则如果你选择 Calico安装非常简单只需应用一份官方提供的清单文件kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yamlCalico 的 Pod 会以 DaemonSet 形式在每个节点上运行。等待几分钟后用下面命令查看系统命名空间里的 Pod 状态kubectl get pods -n kube-system当所有 Pod 都显示Running后整个集群才算是真正可用了。5.4 部署第一个应用做全链路验证集群装好以后我建议立刻部署一个最简单的应用验证全链路。先用kubectl create deployment创建一个测试 Deployment然后把它暴露成 NodePort 类型的 Service。通过浏览器访问任意节点的IP:NodePort如果能正常访问说明调度、网络、kube-proxy 转发、容器运行时整条链路都通了。这一步千万别跳过。我见过太多人在装完集群后直接上生产业务结果基础链路有问题排半天不知道是业务配置问题还是集群问题。先用一个简单应用验证等于提前排掉一批环境问题。6. 高频故障排查我踩过的那些坑你直接拿去用6.1 Pod 一直 Pending——先查调度约束再看资源余量Pending状态意味着 Pod 还没被调度到任何节点。排查步骤我一般按这个顺序来先用kubectl describe pod pod-name查看事件看有没有调度失败的提示检查节点资源是否充足用kubectl describe node查看每个节点的分配和剩余资源检查 Pod 是否有无法满足的调度约束比如 nodeSelector 指定的标签不存在、资源请求超过节点最大容量、端口被占用等。其中“资源请求超过节点容量”是最常见的。很多开发在配置 requests 的时候拍脑袋填了个大数比如 CPU 写 4 核但节点总共只有 4 核还要留一部分给系统组件结果永远调度不上去。6.2 镜像拉取失败——区分网络问题和配置问题ImagePullBackOff是另一个高频问题。看到这个状态码先别急着怀疑网络按顺序排查用kubectl describe pod查看具体错误信息是pull access denied还是connection refused如果是私有仓库的镜像检查是否有配置 imagePullSecret以及 Secret 里的凭证是否过期在节点上手动执行crictl pull 镜像名测试能否拉取能拉说明容器运行时没问题问题可能出在 YAML 里的镜像地址写错。还有一个容易被忽略的点镜像 tag 不要用latest。在生产环境里latest会带来两个问题一是无法精确回滚到某个版本二是某些情况下会触发意外拉取新版本导致行为不可控。6.3 节点 NotReady——别急着重启先看 kubelet 日志节点状态变成NotReady的时候很多人的第一反应是重启节点。但我的经验是先别急重启治标不治本。正确的排查顺序是登录节点执行systemctl status kubelet查看 kubelet 服务状态用journalctl -u kubelet -f查看实时日志重点看有没有证书过期、apiserver 连接失败、CNI 插件报错等关键信息检查网络插件 Pod 是否正常运行Calico 或 Flannel 挂掉是 NotReady 的常见原因。证书过期是我碰到最多的坑。k8s 默认证书有效期是一年如果集群部署后没有配置自动续期一年后 kubelet 就会因为证书过期无法连接 apiserver节点状态就会变成 NotReady。建议在证书到期前主动更新或者配置自动续期机制。6.4 故障排查速查表我把上面这些常见问题整理成一个速查表建议截图保存或者贴在工位上现象优先排查点常用命令Pod Pending节点资源不足、调度约束不满足kubectl describe podPod CrashLoopBackOff容器启动即退出、命令或参数错误、探针失败kubectl logs、kubectl describe podPod ImagePullBackOff镜像地址错误、仓库凭证失效、网络不通kubectl describe pod、crictl pull节点 NotReadykubelet 状态、证书过期、CNI 异常systemctl status kubelet、journalctl -u kubeletService 无法访问Endpoint 是否正常、kube-proxy 规则、Pod 标签是否匹配kubectl get endpoints、iptables -L存储卷挂载失败PV/PVC 状态、StorageClass 是否存在、存储驱动是否正常kubectl get pv、kubectl get pvc7. 学习路线建议别掉进“越学越焦虑”的陷阱最后聊聊学习方式。我知道很多朋友学 k8s 容易陷入两种困境一是资料太多看不完越看越焦虑二是只盯着细枝末节忽略了主干。我的建议是学习 k8s 要抓住一条主线先搭骨架再填血肉最后做项目。骨架就是控制面组件、工作负载、调度、网络、存储这五大块。先把这五块的职责和关系搞明白再用上面第 5 节的方法亲手部署一套集群。集群跑起来后故意制造一些故障去排查——比如手动删掉一个 Pod、把某个节点kubectl drain掉、模拟节点宕机——这时候踩过的坑比你看一百篇文章都管用。我自己带过不少新人发现一个共同规律凡是能独立完成集群部署、并且敢在生产环境里动手排障的人三个月就能达到中级运维水平凡是只停留在“看视频、抄命令”阶段的人学一年还是原地踏步。动手永远是学习运维最快的方式。如果你看完这篇笔记能亲手部署出一个三节点集群再用它跑一个真实的服务那我写这份笔记的目的就达到了。