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 去连接,直接连接失败。后来改用 StatefulSet,Pod 名称和网络标识稳定,问题才彻底解决。
选型时记住一条经验法则:你的服务需不需要稳定的网络标识和独立的存储?需要就选 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 节点打上
gpu=true标签,GPU 任务就指定调度到这些节点。 - 节点亲和性(nodeAffinity):比 nodeSelector 更灵活,支持硬性要求(requiredDuringScheduling)和软性偏好(preferredDuringScheduling)。
- Pod 间亲和性与反亲和性:控制 Pod 之间的部署关系。比如希望两个 Pod 尽量在同一个节点(减少网络延迟),或者希望一个服务的多个副本分布在不同节点(提高可用性)。
我特别想强调反亲和性的重要性。有一个客户曾经把所有副本调度到了同一台节点,节点一宕,整个服务全部不可用。后来加了 Pod 反亲和性规则,指定同一个 Deployment 的副本必须分散到不同节点,可用性明显提升。
4.2 Service 与 kube-proxy:稳定入口怎么实现
Pod 是“短命”的,随时可能被销毁、重建、迁移。那其他服务怎么访问它?答案就是 Service。
Service 为一组 Pod 提供一个稳定的虚拟 IP(ClusterIP),并负责负载均衡。它的实现原理是: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 的时候一脸懵,其实原理不复杂。
- PV(PersistentVolume):集群里的存储资源,就像一块“硬盘”;
- PVC(PersistentVolumeClaim):使用存储的申请单,就像“我要 10G 空间”;
- StorageClass:动态供给的模板,PVC 声明了 StorageClass 后,系统会自动创建 PV,不用管理员提前手工准备存储。
我用一个生活类比帮你串起来:你需要一块硬盘来存数据,于是填了一张申请单(PVC),上面写着“我要 10G、读写一次”。管理员看到申请单后,从仓库里挑了一块合适的硬盘(PV)分配给你。如果仓库里的硬盘不够了,而系统配置了 StorageClass,它就会自动买一块新硬盘(动态创建 PV)来满足申请。
生产环境里,我强烈建议使用 StorageClass 做动态供给,而不是手动创建 PV。手动创建 PV 的方式在测试环境没问题,但到了大规模环境,存储需求变化非常快,手动维护 PV 会变成一场灾难。
4.4 网络插件怎么选:Calico vs Flannel
k8s 集群必须安装一个 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。相比 Docker,containerd 更轻量,也是当前 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 pod |
| Pod CrashLoopBackOff | 容器启动即退出、命令或参数错误、探针失败 | kubectl logs、kubectl describe pod |
| Pod ImagePullBackOff | 镜像地址错误、仓库凭证失效、网络不通 | kubectl describe pod、crictl pull |
| 节点 NotReady | kubelet 状态、证书过期、CNI 异常 | systemctl status kubelet、journalctl -u kubelet |
| Service 无法访问 | Endpoint 是否正常、kube-proxy 规则、Pod 标签是否匹配 | kubectl get endpoints、iptables -L |
| 存储卷挂载失败 | PV/PVC 状态、StorageClass 是否存在、存储驱动是否正常 | kubectl get pv、kubectl get pvc |
7. 学习路线建议:别掉进“越学越焦虑”的陷阱
最后聊聊学习方式。我知道很多朋友学 k8s 容易陷入两种困境:一是资料太多看不完,越看越焦虑;二是只盯着细枝末节,忽略了主干。
我的建议是,学习 k8s 要抓住一条主线:先搭骨架,再填血肉,最后做项目。骨架就是控制面组件、工作负载、调度、网络、存储这五大块。先把这五块的职责和关系搞明白,再用上面第 5 节的方法亲手部署一套集群。集群跑起来后,故意制造一些故障去排查——比如手动删掉一个 Pod、把某个节点kubectl drain掉、模拟节点宕机——这时候踩过的坑,比你看一百篇文章都管用。
我自己带过不少新人,发现一个共同规律:凡是能独立完成集群部署、并且敢在生产环境里动手排障的人,三个月就能达到中级运维水平;凡是只停留在“看视频、抄命令”阶段的人,学一年还是原地踏步。
动手永远是学习运维最快的方式。如果你看完这篇笔记,能亲手部署出一个三节点集群,再用它跑一个真实的服务,那我写这份笔记的目的就达到了。