前言
如果你做过 Java 后端开发,大概率经历过这样的阶段:把应用打成 jar 包,丢到一台服务器上,用 systemd 或脚本拉起进程,再用 Nginx 做反向代理。一台机器跑一个应用时,这套打法完全够用。但当应用数量从三五个涨到几百个,当一次发版需要滚动进行、不能中断流量,当某台机器半夜宕机需要有人(或者某种机制)把进程在别的机器上重新拉起时,手工运维的边际成本会指数级上升——值班的同学被迫从「开发」退化成了「人肉调度器」。
Kubernetes 要解决的正是这个问题。按照官方 Overview 页的定义,Kubernetes 是一个「可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,支持声明式配置与自动化」。它把 Google 内部十五年大规模生产负载的运维经验,连同社区的最佳实践,凝练成了一组可以组合的积木:你只需要用 YAML 描述「我想要什么」,剩下的「怎么做到」交给集群自己去收敛。
这篇文章采用「官方文档带读」的写法:我以 kubernetes.io 官方文档的 Overview 与 Kubernetes Components 两页为主线,逐段解读其中的关键表述,再顺着文档给出的线索,把控制平面组件、节点组件、核心 API 对象、kubectl 工作流串成一条完整的故事线。所有技术细节均以当前版本官方文档为准。读完后,你应该能回答一个最常被问到的问题:「我敲下 kubectl apply 之后,集群里到底发生了什么」。
整体架构与核心概念
官方 Components 页开篇给出一个最朴素的划分:一个 Kubernetes 集群由控制平面(control plane)和若干工作节点(worker node)组成。控制平面负责管理集群的整体状态,节点组件则在每台机器上运行,维持 Pod 的运行并提供 Kubernetes 运行时环境。
+------------------------------------------+
| Control Plane |
| |
| +------------------+ +--------------+ |
| | kube-apiserver |<->| etcd | |
| +------------------+ +--------------+ |
| ^ |
| v |
| +------------------+ +--------------+ |
| | kube-scheduler | | kube- | |
| +------------------+ | controller- | |
| | manager | |
| +--------------+ |
| +------------------------------------+ |
| | cloud-controller-manager (可选) | |
| +------------------------------------+ |
+------------------+-----------------------+
| (API / 指令下发)
+-----------------------------+-----------------------------+
| | |
+----------v-----------+ +----------v-----------+ +----------v-----------+
| Node 1 | | Node 2 | | Node 3 |
| +----------------+ | | +----------------+ | | +----------------+ |
| | kubelet | | | | kubelet | | | | kubelet | |
| +----------------+ | | +----------------+ | | +----------------+ |
| +----------------+ | | +----------------+ | | +----------------+ |
| | kube-proxy | | | | kube-proxy | | | | kube-proxy | |
| +----------------+ | | +----------------+ | | +----------------+ |
| +----------------+ | | +----------------+ | | +----------------+ |
| | Container | | | | Container | | | | Container | |
| | Runtime | | | | Runtime | | | | Runtime | |
| +----------------+ | | +----------------+ | | +----------------+ |
| [Pod] [Pod] [Pod] | | [Pod] [Pod] [Pod] | | [Pod] [Pod] [Pod] |
+----------------------+ +----------------------+ +----------------------+
理解了这张图,也就理解了 Kubernetes 的第一个核心概念:几乎所有操作都收敛为对 API Server 的一次 API 调用。kubelet 定期向 API Server 汇报节点和 Pod 的状态;kubectl 的全部命令本质上都是 REST 客户端;控制器则是一个「盯着 API Server 的数据变化、然后采取行动把现实拉向期望」的循环。etcd 是这个体系里唯一可信的持久化存储,官方文档将其描述为「所有 API Server 数据一致且高可用的键值存储」——API Server 是无状态可水平扩展的,而 etcd 必须谨慎对待,它是集群的「单一事实来源」。
第二个核心概念是 Pod 是调度的最小单元。官方文档反复强调,Kubernetes 不直接调度容器,而是调度 Pod——一组共享网络和存储卷、运行在同一节点上的容器集合。这条约束决定了后面所有对象的设计:Deployment 管理的不是容器,ReplicaSet 维持副本数的单位也是 Pod。
第三个概念是 声明式(declarative)。官方 Overview 页有一句话值得逐字读:「你可以用 Kubernetes 描述已部署容器的期望状态,它会以受控的速率把实际状态变更为期望状态」。这句话是整门技术的主轴,我们下一节专门展开。
你现在应该掌握了什么:集群由控制平面与节点构成;API Server 是所有组件交互的中枢,etcd 是唯一持久化存储;Pod 是调度和管理的最小单位;「声明式」是贯穿全部对象的范式。
控制平面与节点组件的职责分工
先带读官方 Components 页对控制平面组件的描述,逐条拆开看。
kube-apiserver 是「暴露 Kubernetes HTTP API 的核心组件服务」。注意它的定位:它是集群的「门面」,也是「关口」。所有组件、所有 kubectl 命令、所有控制器,读写集群状态都必须经过它。它内部做了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Control),并把合法的写请求持久化到 etcd。因为它是无状态的,生产环境通常部署多个实例,前置一个负载均衡。对开发者而言,记住一条排查原则:当集群表现异常时,先看 API Server 的日志和健康状况,再看下面各组件。
etcd 官方描述是「一致且高可用的键值存储,保存所有 API Server 数据」。集群里哪些东西存在 etcd 里?Deployment、Service、Pod、ConfigMap、Secret、事件……一切 API 对象。反过来,容器里跑的进程、节点上的 iptables 规则这些「运行时状态」并不在 etcd 里,它们由各节点上的组件现场维护。这解释了为什么 etcd 的快照备份是集群级灾备的底线:只要 etcd 数据还在,整个集群的「意图」就还在。
kube-scheduler 的职责写得非常精确:「寻找尚未绑定到节点的 Pod,并把每个 Pod 指派到一个合适的节点」。注意它只负责「选节点」,不负责「创建 Pod」,也不负责「在节点上把容器拉起来」。调度决策考虑的因素官方在调度章节列得很长:资源请求(requests/limits)、亲和与反亲和、污点和容忍、拓扑分布约束等,此处不展开,以当前版本官方文档为准。对 Java 读者的类比:scheduler 像一个只做「分库路由」的中间件,真正的读写发生在节点上。
kube-controller-manager 是「运行控制器以落实 Kubernetes API 行为」的进程。它是一个进程内跑了许多控制循环(control loop)的集合,包括 ReplicaSet 控制器、Deployment 控制器、Node 控制器、Job 控制器等。每个控制器干的事高度同构:watch 自己关心的对象,对比期望状态与当前状态,向 API Server 发请求消除差异。
cloud-controller-manager 是可选组件,负责与底层云提供商集成。它把「创建负载均衡器」「管理节点路由」「操作存储卷」这类云资源操作从核心控制器里剥离出来,让 Kubernetes 核心代码不绑定任何一家云。
节点组件则运行在每台机器上:
kubelet 官方定义:「确保 Pod 在运行,包括它们的容器」。它是节点上的「工头」:从 API Server(或本地静态文件)拿到属于本节点的 Pod 定义,调用容器运行时把容器拉起来,然后持续通过探针检查容器健康,并把状态汇报回 API Server。
kube-proxy 是可选组件,「在节点上维护网络规则以实现 Service」。Service 拥有一个虚拟 IP(ClusterIP),kube-proxy 通过 iptables 或 IPVS 规则,把这个虚拟 IP 上的流量转发到后端 Pod 的真实地址。
容器运行时 是「负责运行容器的软件」。Kubernetes 通过 CRI(Container Runtime Interface)与具体运行时解耦,containerd、CRI-O 都是常见实现——这里尤其要提醒老读者:dockershim 已被移除,不要再假设集群还直接支持 Docker Engine 作为运行时,具体以当前版本官方文档为准。
把六个组件的职责压缩成一张表,方便对照记忆:
| 组件 | 所在位置 | 官方职责概括 | 类比理解 |
|---|---|---|---|
| kube-apiserver | 控制平面 | 暴露 Kubernetes HTTP API | 集群的门面与关口 |
| etcd | 控制平面 | 一致且高可用的键值存储 | 单一事实来源 |
| kube-scheduler | 控制平面 | 把未绑定节点的 Pod 指派到合适节点 | 只做分派,不做执行 |
| kube-controller-manager | 控制平面 | 运行控制器落实 API 行为 | 不知疲倦的纠偏循环 |
| cloud-controller-manager | 控制平面(可选) | 与云提供商集成 | 云资源的代理 |
| kubelet | 每个节点 | 确保 Pod 及其容器在运行 | 节点上的工头 |
| kube-proxy | 每个节点(可选) | 维护网络规则实现 Service | 节点内的流量调度员 |
| 容器运行时 | 每个节点 | 运行容器 | 真正干活的执行引擎 |
你现在应该掌握了什么:每个组件「负责什么、不负责什么」的边界,尤其是 scheduler 只管选节点、controller manager 只管发 API 请求、kubelet 才在节点上真正拉起容器这条责任链。
声明式 API 与期望状态收敛(reconciliation)模型
官方 Overview 页有一段话是理解 Kubernetes 的钥匙,原文意思是:编排(orchestration)的传统定义是「执行一个定义好的工作流:先做 A,再做 B,再做 C」,而 Kubernetes 恰恰消除了对编排的需要——它由一组独立的、可组合的控制过程组成,这些过程持续地把当前状态驱动向给定的期望状态;从 A 到 C 怎么走并不重要,也不需要中心化控制。
这段话听起来抽象,落到工程上就是 reconciliation(调和)模型,它有三个要素:
- 对象里只有「期望状态」。你提交的 Deployment YAML 里写的是
replicas: 3、镜像版本、资源请求,你不需要(也不能)写「先创建 Pod-1,等它就绪,再创建 Pod-2」这样的流程。 - 控制器负责「观察差异并行动」。Deployment 控制器发现「期望 3 个副本,实际 ReplicaSet 只有 2 个」,就补一个;发现 Pod 所在节点失联,就通过 ReplicaSet 再建一个。
- 收敛是持续进行的。即使没有任何人提交新配置,控制器也在循环里跑着。有人手工
kubectl delete pod,副本数少了,几分钟后(通常几秒内)又会补回来——这不是魔法,只是 reconciler 在做本职工作。
对习惯命令式思维的 Java 工程师,可以用一个类比:声明式 API 像给线程池提交一个 ForkJoinTask,你只描述结果,框架决定拆分与调度;而命令式运维像自己写 for 循环逐个起线程,任何一步失败都要自己补偿。Kubernetes 把「补偿逻辑」从业务代码里剥离出来,下沉到了平台层。
官方文档里与收敛直接相关的能力列表也值得对照读:自动发布与回滚(Automated rollouts and rollbacks)——「以受控的速率把实际状态变为期望状态」;自愈(Self-healing)——重启失败的容器、替换容器、杀死不响应健康检查的容器、在就绪前不对外通告;水平扩缩——一条命令、一个界面或基于 CPU 使用率自动完成。这些能力的底层,全是同一个 reconciliation 循环的不同投影。
你现在应该掌握了什么:声明式意味着「描述目标,而非步骤」;控制器持续比对期望与实际并消除差异;自愈、滚动更新、自动扩缩都是同一模型的自然推论。
一次 apply 之后:从 API 请求到容器运行的完整链路
理解了组件职责和 reconciliation 模型,我们可以把「kubectl apply 之后到底发生了什么」按时间线完整推演一遍,这是带读官方文档时最值得慢下来的一段。
第一步,kubectl 解析本地 YAML,向 API Server 发起一次 HTTPS 的 PATCH/CREATE 请求。API Server 依次执行认证(你是谁)、鉴权(你能对这个命名空间的 Deployment 做什么)、准入控制(请求里的字段是否合法、是否违反策略),然后把这个对象的期望状态写入 etcd。到这里,「你的意图」已经成为了集群的持久化事实。
第二步,Deployment 控制器通过 watch 机制感知到新对象或变更,对比「期望副本数与模板」和「当前 ReplicaSet 的状态」。如果是新对象,它创建一个以 pod-template-hash 为后缀的 ReplicaSet;如果是镜像升级,它新建一个新哈希的 ReplicaSet,并按滚动策略逐步伸缩新旧两者。这一步仍然只是写 API 对象,没有任何容器被真正创建——记住控制器的共同特征:只发 API 请求,不直接操作节点。
第三步,ReplicaSet 控制器清点标签选择器匹配到的 Pod 数量,发现不足,于是按模板创建 Pod 对象。注意此时 Pod 只是 etcd 里的一条记录,它的 spec.nodeName 还是空的——调度尚未发生。
第四步,scheduler 的 watch 流里出现「未绑定节点的 Pod」,它运行过滤与打分:先排除资源不足、不满足污点容忍的节点,再在候选节点上按资源余量、亲和性等打分,把得分最高的节点写回 Pod 的 spec.nodeName。这一步叫绑定(binding),是一次对 Pod 对象的更新,同样只是写数据。
第五步,对应节点上的 kubelet 发现「分配给本节点且尚未运行的 Pod」,调用 CRI 接口让容器运行时拉取镜像、创建容器、挂载卷、配置网络,然后周期性地执行探针并把容器状态通过 API Server 写回 etcd。此时 Pod 进入 Ready,Deployment 的 Available 条件满足,kubectl rollout status 返回成功。
第六步,如果你之前创建了 Service,Service 控制器会把就绪端点写入 EndpointSlice;kube-proxy 或等价的转发组件 watch 到端点变化后,更新本节点的转发规则。从此集群内的客户端访问 Service 的虚拟 IP,流量被分摊到各个就绪副本。
整条链路里没有任何一个组件「认识」其他组件的内部实现,它们唯一的协作媒介就是 API Server 里的对象状态。这正是官方那句话的工程含义:一组独立的、可组合的控制过程,持续地把当前状态驱动向期望状态。排查问题时也按这条时间线反向切分:对象没建出来查 API Server 与准入;Pod 一直 Pending 查调度;镜像拉不动查 kubelet 与运行时;Service 不通查端点与转发规则。
你现在应该掌握了什么:apply 之后是一条六步的责任链——准入与持久化、Deployment 分层、ReplicaSet 建 Pod、scheduler 绑定节点、kubelet 起容器、Service 端点与转发;每一步都只是对象状态的变化,排查按时间线反推即可。
核心对象的关系:Pod、ReplicaSet、Deployment、Service
官方对象体系里,这四者是日常最高频的组合。它们不是平级关系,而是一条自上而下的「委托链」:
- Pod 是最小调度单元,官方定义为一组共享存储与网络资源、运行在同一节点的容器集合。Pod 是「易失品」,设计上就不追求长存——节点故障、驱逐、资源不足都可能让它消失。
- ReplicaSet 的官方职责是「维持一组稳定的 Pod 副本在任何时刻都在运行」。它自己不直接管容器,而是通过 Pod 模板(template)创建 Pod,并通过标签选择器持续清点「属于它的 Pod 有几个」。多了删、少了建。
- Deployment 官方定位是「为 Pod 与 ReplicaSet 提供声明式更新」。你可以把 Deployment 理解为「给 ReplicaSet 加了一个版本管理器」:每次修改模板(比如升级镜像),Deployment 就创建一个新的 ReplicaSet,滚动地把旧 ReplicaSet 的 Pod 替换掉,同时保留旧 ReplicaSet 的历史记录,以便回滚。
- Service 官方定义是「将运行在同一组 Pod 上的应用程序暴露为网络服务的抽象」。因为 Pod 的 IP 随生随灭,客户端需要一个稳定的访问入口;Service 用虚拟 IP 加标签选择器,把流量转发到当前匹配的 Pod 集合上,配合 kube-proxy 在节点上落实转发规则。
用一张表对比三者的分工:
| 维度 | Pod | ReplicaSet | Deployment |
|---|---|---|---|
| 解决的问题 | 容器如何共享运行环境 | 副本数恒定、Pod 消失后补齐 | 无停机发布、版本历史与回滚 |
| 是否直接创建 Pod | 是(被创建的对象) | 是(按模板创建) | 否(通过 ReplicaSet) |
| 是否管镜像更新策略 | 否 | 仅重建(无滚动语义) | 是(RollingUpdate/Recreate) |
| 删除后是否重建自身 | 否 | 是(被 Deployment 管理时) | 按级联策略决定 |
一个典型 Deployment 的 YAML 骨架如下,注意模板里 labels 与 selector 的对应关系——这是全篇后面要反复强调的细节:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
labels:
app: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: registry.example.com/order-service:1.4.2
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
把它暴露成集群内可访问的 Service:
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
你可以用一条命令快速验证「对象链」是否真的建立起来了:
kubectl get deploy order-service
kubectl get rs -l app=order-service
kubectl get pod -l app=order-service -o wide
kubectl describe deploy order-service | grep -A 5 "Events"
你会发现 ReplicaSet 的名字是 order-service-<pod-template-hash> 的形式——那个哈希后缀正是 Deployment 区分新旧版本的机制:模板一变,哈希就变,新 ReplicaSet 诞生,旧 ReplicaSet 缩容。
你现在应该掌握了什么:Deployment 管 ReplicaSet、ReplicaSet 管 Pod、Service 靠选择器找到 Pod;镜像更新走「新 ReplicaSet 滚动替换」而非原地修改;selector 与模板 labels 必须严格一致。
标签选择器与 ownerReference:对象间的隐形纽带
上一节反复出现的「标签选择器」,是官方 Labels and Selectors 页的核心概念。官方的定义是:标签(Labels)是附加到对象上的键值对,用来标识对用户有语义但不直接为系统核心所知的属性;选择器(Selectors)则让客户端/用户能按标签筛选出一组对象。
看两组最常见的等值匹配写法(以当前版本官方文档为准):
# 命令行:按标签过滤
kubectl get pods -l app=order-service
kubectl get pods -l 'app in (order-service, payment-service)'
# YAML 中的选择器
selector:
matchLabels:
app: order-service
matchExpressions:
- key: tier
operator: In
values: ["backend"]
Service 靠 selector 找到后端 Pod,ReplicaSet 靠 selector 清点自己的副本,Deployment 靠 selector 认出自己的 ReplicaSet——对象之间几乎不直接引用对方的名字,而是靠「共同遵守一套标签约定」建立联系。这带来一个工程上的重要推论:标签是你集群里的「公共契约」,改标签等于改契约,会引发真实流量与副本管理的连锁反应。比如给 Deployment 模板里的 app 标签改个名字,老 ReplicaSet 的选择器就匹配不到新 Pod 了,滚动更新会立刻卡住——这类事故在生产环境并不少见。
如果说标签是「横向」的组带,那么 ownerReference 就是「纵向」的依赖链。官方 Owners and Dependents 页说明:从属对象(dependent)通过元数据中的 ownerReferences 字段指向属主(owner);当属主被删除时,Kubernetes 默认按级联策略把从属对象一并清理。验证方法很简单:
kubectl get pod order-service-7d9f4b5c8-x2v1q -o jsonpath='{.metadata.ownerReferences}'
# 输出会显示该 Pod 的属主是某个 ReplicaSet
kubectl get rs order-service-7d9f4b5c8 -o jsonpath='{.metadata.ownerReferences}'
# 再往上,属主是 Deployment order-service
这条依赖链解释了 kubectl delete deployment order-service 为什么能一口气删掉全部 Pod:API Server 的级联删除(cascading deletion)机制顺着 ownerReference 一路清下去。也解释了为什么手工删一个 Pod 不会删掉 ReplicaSet——方向相反,owner 不关心 dependent 的单个消亡,只负责把数量补齐。
清理孤儿对象时还可以利用官方提供的 --cascade 相关行为与前台/后台删除选项(以当前版本官方文档为准):前台删除(foreground)会等从属对象先删干净再删属主,后台删除(background)则立即返回、由垃圾回收控制器异步清理。对大规模删除而言,这个选择直接影响控制平面的压力曲线。
你现在应该掌握了什么:标签选择器是对象间横向关联的纽带,ownerReference 是纵向的归属链;删除行为(级联、前台/后台)由这两条纽带共同决定;改标签是高危操作。
kubectl 声明式工作流:apply、diff 与 rollout
官方对象管理章节明确区分三种工作流:命令式命令(Imperative commands)、命令式对象配置(Imperative object configuration)、声明式对象配置(Declarative object configuration)。生产环境的推荐答案只有一个:声明式。三者的对比值得放在一张表里随时对照:
| 维度 | 命令式命令 | 命令式对象配置 | 声明式对象配置 |
|---|---|---|---|
| 典型命令 | kubectl create/run/expose | kubectl create/replace -f | kubectl apply -f |
| 是否依赖本地配置文件 | 否 | 是(要求完整对象) | 是(允许部分字段) |
| 能否团队协作 + 版本控制 | 差 | 一般(字段冲突覆盖) | 好(与 GitOps 天然契合) |
| 误操作风险 | 高 | 高(replace 全量覆盖) | 低(三方合并、字段级归属) |
声明式 apply 的底气来自 Server-Side Apply(服务端应用):字段的「归属者」记录在对象的原数据中,多人/多工具分别管理同一对象的不同字段时互不踩脚。一条标准的变更流水线长这样:
# 1. 先看将要发生什么,不产生任何写入
kubectl diff -f ./manifests/
# 2. 确认无误后应用
kubectl apply -f ./manifests/
# 3. 观察滚动发布的推进过程
kubectl rollout status deployment/order-service
# 4. 需要回滚时,先看历史,再回退
kubectl rollout history deployment/order-service
kubectl rollout undo deployment/order-service --to-revision=2
# 5. 排查时直接看事件流
kubectl describe deployment order-service
两个容易忽略但极其实用的细节:其一,kubectl diff 是免费的「预演」,把它接到 CI 里,能让评审者直接看到每次提交的集群影响面;其二,rollout undo 并不是把旧配置「写回去」,而是直接把旧 ReplicaSet 扩容、新 ReplicaSet 缩容——本质仍是 reconciliation,回滚与发布共用同一条滚动通道,这也解释了为什么 maxSurge 与 maxUnavailable 同时约束两个方向。
你现在应该掌握了什么:声明式配置是唯一适合团队协作与版本管理的工作流;apply 前有 diff、变更后有 rollout status/history/undo 形成闭环;回滚本质是让旧 ReplicaSet 重新收敛。
从零到可访问:一个完整的部署全流程
把前面所有章节串起来,走一遍「从零到集群内可访问」的最短闭环,也是对官方新手教程主线(创建 Deployment、探索 Pod 与节点、用 Service 暴露、扩缩、滚动更新)的压缩复刻:
# 1. 起一个 Deployment(教学可用 --dry-run=client 先生成骨架)
kubectl create deployment order-service \
--image=registry.example.com/order-service:1.0.0 \
--replicas=3 --dry-run=client -o yaml > deploy.yaml
# 2. 声明式应用,并确认收敛完成
kubectl apply -f deploy.yaml
kubectl wait --for=condition=available deployment/order-service --timeout=120s
# 3. 创建 Service,获得稳定的集群内访问入口
kubectl expose deployment order-service --port=80 --target-port=8080 --dry-run=client -o yaml > svc.yaml
kubectl apply -f svc.yaml
# 4. 集群内验证:任一 Pod 里都能通过 Service 名解析到 ClusterIP
kubectl run curl-test --rm -it --image=curlimages/curl --restart=Never -- \
curl -s http://order-service/health
# 5. 扩容与升级(生产推荐改 YAML 后 apply,而非命令式 set image)
kubectl scale deployment order-service --replicas=5
kubectl set image deployment/order-service order-service=registry.example.com/order-service:1.1.0
kubectl rollout status deployment/order-service
这条链路里每一步都对应前文的一个知识点:apply 触发 API Server 持久化到 etcd;Deployment 控制器创建/更新 ReplicaSet;scheduler 把新 Pod 绑定到有空闲资源的节点;kubelet 调容器运行时拉起容器并通过探针汇报就绪;Service 的控制器维护 EndpointSlice(Service 到后端 Pod 的映射),kube-proxy 把它翻译成节点上的转发规则。至此,集群内的其他服务访问 http://order-service 即可到达任一副本,负载由 kube-proxy 的转发规则分摊。若需集群外访问,再叠加 Ingress 或 LoadBalancer 类型的 Service,具体机制以当前版本官方文档为准。
你现在应该掌握了什么:一条命令链背后是六个组件与四个控制器协同的 reconciliation 过程;任何一步卡住,都可以沿着「API Server → 控制器 → 调度 → kubelet → 运行时」的责任链反向定位。
踩坑经验
以下四组对照,全部来自对官方文档职责边界的常见误读。
第一组:直接创建裸 Pod
❌ 错误做法:kubectl run order-service --image=... 直接拉起一个裸 Pod,然后指望它高可用。Pod 是易失品,节点故障即消失,且无人重建。
✅ 正确做法:始终用 Deployment(或官方列出的其他工作负载对象)间接管理 Pod,让 ReplicaSet 控制器负责副本补齐。
第二组:用 replace 做日常变更
❌ 错误做法:每次改动用 kubectl replace -f,它会全量覆盖对象,把别人或其他工具管理的字段一起冲掉,团队协作时互相覆盖。
✅ 正确做法:日常变更一律 kubectl apply -f,变更前先 kubectl diff -f 预演;只有确实需要整体重建对象的场景才考虑 replace。
第三组:选择器与模板标签对不上
❌ 错误做法:Deployment 的 spec.selector.matchLabels 写的是 app: order,Pod 模板里写的却是 app: order-service,或者发版时顺手改了模板标签。结果 ReplicaSet 匹配不到任何 Pod,副本数永远凑不齐。
✅ 正确做法:创建后 spec.selector 不可变(以当前版本官方文档为准),模板 labels 必须与 selector 严格一致;改标签前先想清楚谁在选择它(Service?HPA?NetworkPolicy?)。
第四组:把 Service 当进程看板用
❌ 错误做法:Service 创建后立刻压测,发现部分请求连接拒绝——因为此时匹配的后端 Pod 尚未 Ready,Service 不会把流量导向未就绪端点。
✅ 正确做法:发布流程里显式等待就绪:kubectl wait --for=condition=available,并为容器配置 readinessProbe,让 Service 的端点列表与真实服务能力同步。
第五组:liveness 与 readiness 混用
❌ 错误做法:把「依赖的外部数据库还没就绪」写进 livenessProbe,数据库抖动时所有 Pod 被连环重启,雪崩反而加重;或者只配 liveness 不配 readiness,启动慢的应用一上线就接到流量,请求全部超时。
✅ 正确做法:liveness 只回答「进程是否已无可救药」,用轻量检查(如固定端点);readiness 回答「现在能否接流」,可以包含依赖检查;启动特别慢的服务再加 startupProbe 兜底(以当前版本官方文档为准)。
第六组:不给资源请求就指望自动扩缩
❌ 错误做法:容器不写 resources.requests,HPA 基于资源利用率扩缩时发现分母是 0,行为不可预期;调度器也因不知道占用而把所有 Pod 堆到同一节点。
✅ 正确做法:为每个容器设置 requests 与 limits,并以压测数据为准;HPA 的指标口径与 requests 联动,调度与驱逐的决策才有可靠依据。
最佳实践清单
- 一切变更走声明式:配置进 Git,CI 里跑
kubectl diff,合并后kubectl apply,让集群状态与仓库状态一一对应(GitOps)。 - 日常操作的三段式:diff 预演 → apply 应用 → rollout status 确认收敛,三件事缺一不可。
- 标签即契约:为对象定义统一的标签规范(团队、应用、环境、版本),并在 CR/评审里把「改了谁的选择器」当作高危变更对待。
- 删除操作先想清楚方向:
kubectl delete deployment会级联清理 Pod;删除单个 Pod 只会触发重建。需要保留现场排查时,用--cascade=orphan(以当前版本官方文档为准)或先导出配置。 - 给容器配上 requests/limits 与探针:requests 影响调度与自动扩缩的决策,limits 兜底资源爆炸;liveness 管重启、readiness 管接流,语义不要混用。
- 滚动参数与容量联动:
maxSurge/maxUnavailable决定了发布期需要多少额外容量,小集群别把两个都设成激进值。 - 排查沿责任链走:现象(Pod 没起来)→ 看调度(Pending 原因)→ 看 kubelet 事件(ImagePullBackOff/CrashLoopBackOff)→ 看运行时与镜像,逐层缩小范围。
- 集群级变更先小流量验证:在测试命名空间复演同一套 manifest,确认 diff、滚动行为与回滚路径都符合预期,再推到生产;升级前务必备份 etcd 快照,这是灾备的最后底线。
总结
- 集群由控制平面与节点组成:API Server 是门面、etcd 是事实来源、scheduler 只做调度决策、controller manager 运行纠偏循环,kubelet 与容器运行时才真正在节点上拉起容器。
- 声明式 API + reconciliation 是 Kubernetes 的地基:描述期望状态,由控制过程持续收敛实际状态,自愈、滚动更新、回滚都是同一模型的推论。
- 核心对象是一条委托链:Deployment 管理 ReplicaSet,ReplicaSet 按标签清点 Pod,Service 按选择器把流量引到就绪的 Pod。
- 标签选择器与 ownerReference 是对象间的两条纽带:前者负责横向关联与流量路由,后者决定级联删除等纵向行为。
- 工作流上,kubectl 的 diff/apply/rollout 构成「预演—应用—验证」闭环,声明式配置是唯一适合团队协作与版本管理的方式。
- 建议以官方文档为唯一事实源继续深入:对象管理、标签与选择器、所有者与依赖、集群架构四个章节是本文的自然延伸。
参考资料
- Kubernetes Overview: https://kubernetes.io/docs/concepts/overview/
- Kubernetes Components: https://kubernetes.io/docs/concepts/overview/components/
- Objects In Kubernetes: https://kubernetes.io/docs/concepts/overview/working-with-objects/
- Kubernetes Object Management: https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/
- Labels and Selectors: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
- Owners and Dependents: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/
- Cluster Architecture: https://kubernetes.io/docs/concepts/architecture/