AI时代的Kubernetes教程36招-第2招-Kubernetes有哪些专有名词、核心架构和使用场景?

K8s 完整术语与架构图:从 API Server 到 kubelet 的职责边界

组件职责说不清,故障定位就只能靠猜。

[读者] 正在给业务团队交付 K8s 集群的平台工程师
[痛点] 术语背得出,故障一上来就分不清该查谁
[现在读] 集群开始出现 Pending 和 Service 不通,定位成本在飙升
[读完] 拿到一张职责边界表和一条可复用的故障定位路径
[旧方案] kubectl 能部署能跑,就认为集群没问题
    |
    v
[新需求] 多团队多命名空间,Pod、Service、存储、网络全上
    |
    v
[冲突] 所有组件都通过 API Server 读写 etcd,故障现象却指向完全不同的组件
    |
    v
[后果] 排查凭直觉,改错一层,问题从 Pending 变成雪崩

我是老李,做内部平台工程。手里这个集群是给七个业务团队共用的,控制面三台、工作节点六台,从命名空间配额到 Ingress 网关都在我这边落地。

集群本身没出过什么大事故,真正让我头疼的是「出问题时该找谁」。团队里每个人都能背出 kube-apiserver、kube-scheduler、kubelet 这些名字,但没有一个人能在故障发生的第一分钟说清边界在哪。

我的任务是把这个边界钉死:一份术语表、一张架构图、一条从现象回溯到组件的路径,任何人拿到告警都能按图索骥。

同一个 Pending 的 Pod,为什么三个人能给出三种互相矛盾的根因?

遇到「服务不通」,是先翻 kube-proxy 的网络规则,还是先看 Service 背后的端点列表?

如果分不清 Desired State 和 Actual State 那条边界,一次误判会把什么问题从局部拖成全局?

01、故事

集群最早是三个团队在用,五个命名空间,三十来个 Deployment,日子过得很舒服。当前方案也很简单:业务方写 YAML,我扫一眼,kubectl apply 上去,能跑就算交付。

变化从今年一季度开始。团队从三个扩到七个,命名空间涨到四十多个,工作负载从无状态 Deployment 变成了 Deployment 加 StatefulSet 加 Job 加 CronJob,存储挂上了 CSI,入口从 NodePort 换成了 Ingress,网络策略开始按命名空间隔离。

冲突在这一刻暴露出来:整个集群里,写状态的组件只有一个(etcd),读写入口只有一个(kube-apiserver),其余十几个组件都是围着它转的客户端;可故障现象却五花八门——Pending、CrashLoopBackOff、ClusterIP 超时、PVC 卡住、节点 NotReady——每一条看起来都指向不同的组件。

现场最典型的一幕发生在周二下午。一个业务的 Pod 卡在 Pending,值班同学说是节点资源不够,另一个同学说是调度器挂了,我扫了一眼存储指标,说可能跟磁盘有关。三个人,三种判断,没有一个能立刻被证伪。

后来我们花了四十分钟才弄清楚:kube-scheduler 根本没报错,它工作得很正常——它只是找不到满足 requests 的节点,于是诚实地什么都不做,把 Pod 留在未绑定状态。这个「什么都不做」,恰恰是最难被看见的行为。

一图分主次
一表定职责
一问见边界
一查有依据

02、问题

旧方案失效得很彻底:靠「能跑就行」和口口相传的组件印象,支撑不了多团队、多工作负载下的故障定位。

业务影响是直接的——告警一来,值班同学的第一反应变成了「重启试试」。重启能不能解决问题,完全取决于运气,而运气不构成运维能力。

技术表现可以归成四类:

  • 现象与组件对不上号。看到 Pending 就怀疑调度器,看到超时就怀疑网络插件,实际上产生现象的位置和产生原因的位置经常不在一层。
  • 状态没有归属。分不清哪个字段是「期望」、哪个字段是「观测」,于是不知道该看谁写的值。
  • 组件之间的调用关系被想象成网状。以为 Scheduler 会通知 kubelet、以为 Controller 会直接操作容器,实际上它们之间只有一条通路。
  • 缺少可执行证据。所有判断停留在「我觉得」,没有一条命令能立刻推翻或坐实。

可验证的完成标准我定成三条。给定任意一条集群告警,必须能在五分钟内回答:这个现象由哪个组件产生;该组件读写的状态存在哪里;至少一条可以直接执行的验证命令是什么。

旧法靠经验
新局要边界
现象乱如麻
归因须有据

03、原理

这一篇只需要四条原理,多一条都是噪音。

第一条,单入口。kube-apiserver 是整个控制面唯一的读写入口,它对外暴露 REST API,负责认证、鉴权、准入、校验,然后把对象持久化。任何组件想读或想写集群状态,都必须经过它。

第二条,单事实源。etcd 是集群状态的唯一持久化存储,其他组件内存里的东西都只是缓存。etcd 一旦不可写,整个控制面就变成了只读系统——已经跑着的容器不受影响,但没有任何新的期望状态能被记录下来。

第三条,控制循环。控制器不断做同一件事:读取期望状态,读取观测到的实际状态,计算差异,执行动作让实际状态向期望状态收敛。ReplicaSet 控制器、Deployment 控制器、Node 控制器、EndpointSlice 控制器,形态各异,骨架相同。

第四条,接口分层。kubelet 要拉起容器,需要通过 CRI 和容器运行时对话;节点要接入网络,需要 CNI 插件实现 Pod 网络;持久卷要挂载,需要 CSI 驱动完成挂载与挂接。这三个都是接口规范,不是具体实现,所以同一个集群可以换不同的运行时、网络插件和存储驱动。

这里有一个反直觉判断:kube-scheduler 和 kube-controller-manager 从来不下发指令给 kubelet。它们只往 API Server 写对象字段,kubelet 自己 watch 出变化,然后干活。

还有第二个反直觉判断:etcd 里从来没有「Pod 正在运行」这个事实。etcd 里只有「Pod 应该运行」以及「kubelet 上报说它现在处于某个状态」。真正的运行事实在节点的容器运行时里,控制面看到的永远是二手信息。

理解这两条,后面所有的排查都有了方向:现象在节点上,原因可能在 etcd 的某个字段里;现象在字段里,原因可能在控制器的收敛逻辑里。

单口通万象
单源定真伪
循环自收敛
谁写谁负责

04、架构

[输入] kubectl apply / 控制器写入 / kubelet 上报
    |
    v
[模块] kube-apiserver(认证 → 鉴权 → 准入 → 校验)
    |
    v
[数据/状态] etcd(期望状态 + 被观测到的实际状态)
    |
    v
[处理] Scheduler 绑定节点 + Controller 调谐副本 + kubelet 执行
    |
    v
[输出] 节点上的容器 / Service 端点 / 状态回写

这张图的边界很清楚:apisverver 之上是用户和客户端,之下是唯一的持久化层,两侧是并行工作的控制器和节点代理。所有跨组件的交互都被压扁成「读写 API 对象」这一种形式。

下面把术语按职责层分组,逐条钉死。

控制面与集群骨架:

术语 负责什么 不负责什么
Cluster 一组工作节点加上一套控制面,共享同一份 etcd 状态 不负责跨集群调度
Control Plane 做出全局决策的组件集合,通常跑在独立节点上 不运行业务容器
Node 真正承载 Pod 的机器,可以是物理机也可以是虚拟机 不决定 Pod 该不该来
Namespace 给对象名提供作用域,做逻辑隔离与配额边界 不是网络隔离,也不是安全边界

控制面组件:

术语 负责什么
kube-apiserver 控制面入口,暴露 REST API,做认证鉴权准入校验,读写 etcd
etcd 一致且高可用的键值存储,集群全部持久状态的唯一来源
kube-scheduler 为尚未绑定的 Pod 选择节点,把结果写进 spec.nodeName
kube-controller-manager 运行内置控制器,持续把实际状态推向期望状态
cloud-controller-manager 承载云厂商相关的控制逻辑,如节点、路由、负载均衡器

节点组件:

术语 负责什么
kubelet 节点上的代理,watch 绑定到本节点的 Pod,驱动容器达到期望状态并上报状态
container runtime 真正创建和运行容器的软件,通过 CRI 被 kubelet 调用
kube-proxy 维护节点上的转发规则,让 Service 的虚拟地址能被访问到

接口与插件:

术语 负责什么
CRI kubelet 与容器运行时之间的接口规范
CNI 容器网络插件的接口规范,负责给 Pod 分配地址并打通网络
CSI 容器存储接口规范,负责卷的创建、挂载与挂接

工作负载与流量:

术语 负责什么
Pod 最小可部署单元,一组共享网络与存储的容器
ReplicaSet 维持指定数量的 Pod 副本存在
Deployment 管理 ReplicaSet,提供声明式的滚动更新与回滚
Service 为一组 Pod 提供稳定的虚拟地址和 DNS 名称
Ingress 把集群外的 HTTP 与 HTTPS 流量按规则路由到 Service
Gateway Gateway API 中的资源,把路由能力按角色拆开,是 Ingress 的演进方向,具体状态需按当前官方文档核验

扩展与治理:

术语 负责什么
Admission 对象写入 etcd 之前的最后一道关卡,可变更也可拒绝
CRD 向 API Server 注册自定义资源类型,让集群认识新的对象

收益是显而易见的:十几个名字被压缩成四层,任何一条告警都能先定位到层,再定位到组件。

代价是这张表只描述稳定职责,不描述版本差异。kube-proxy 在部分 CNI 实现下可以被完全替代,Gateway API 的成熟度在不同版本里差别很大,cloud-controller-manager 在自建集群里可能根本不存在。这些都需要按当前官方文档核验。

适用条件是:集群组件没有被大量替换。一旦换掉 kube-proxy、换掉运行时、接入自研调度器,职责表需要同步重画。

入口唯一处
状态藏一处
调度只管放
执行在节点

05、实战一次

先把最小可用版本跑起来,目的是让术语和真实进程一一对上号。

环境要求:一个可用的 Docker 环境、kubectl、kind。版本请按当前官方文档核验,本文不假设具体版本号。

第一步,写集群配置。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: arch-lab
nodes:
  - role: control-plane
  - role: worker
  - role: worker

第二步,创建集群。

kind create cluster --config kind-3node.yaml

第三步,把控制面组件的真实进程列出来。

kubectl get pods -n kube-system -o wide

示例输出:

NAME                                              READY   STATUS    AGE
coredns-5d78c9869d-2kqxp                          1/1     Running   2m
etcd-arch-lab-control-plane                       1/1     Running   2m
kube-apiserver-arch-lab-control-plane             1/1     Running   2m
kube-controller-manager-arch-lab-control-plane    1/1     Running   2m
kube-proxy-8xqzn                                  1/1     Running   2m
kube-scheduler-arch-lab-control-plane             1/1     Running   2m
kindnet-4kt9p                                     1/1     Running   2m

这一步的价值在于对号入座:术语表里的 kube-apiserver、etcd、kube-scheduler、kube-controller-manager 在这里是控制面节点上的静态 Pod;kube-proxy 和 CNI 插件(这里是 kindnet)是每个节点都有的守护进程;cloud-controller-manager 没有出现,因为本地集群不在任何云上。

第四步,部署一个最小工作负载。

kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl expose deployment web --port=80 --target-port=80
kubectl rollout status deployment/web

第五步,验证 Scheduler 到底做了什么。

kubectl get pods -l app=web -o wide
kubectl get pod -l app=web -o jsonpath='{.items[0].spec.nodeName}'

第一个命令的输出里,NODE 列就是 kube-scheduler 写入的结果;第二个命令直接把这个字段取出来,证明所谓「调度」,本质上就是给 spec.nodeName 赋了一个值。

第六步,验证 Service 背后是谁在维护端点。

kubectl get endpointslices -l kubernetes.io/service-name=web

示例输出:

NAME   ADDRESSTYPE   PORTS   ENDPOINTS                          AGE
web    IPv4          80      10.244.1.5,10.244.2.7,10.244.2.8   40s

这些地址由 kube-controller-manager 里的端点控制器写入,kube-proxy 只是把它们变成节点上的转发规则。

第七步,验证 kubelet 在写状态。

kubectl get pod -l app=web -o jsonpath='{.items[0].status.phase}'
kubectl get pod -l app=web -o jsonpath='{.items[0].status.podIP}'

status 里的内容全部来自 kubelet 的上报,用户和控制器都改不了它。

V1 到这里就通了。它解决了「术语看得见」的问题,但留下了一个明显的缺口:这套流程全靠人手写规范,没有任何机制保证下一次提交的 YAML 是合格的。

先起三节点
再对号入座
术语落进程
图上有真名

06、排查

诊断链一:Pod 长期 Pending

现象:新创建的 Pod 一直停在 Pending,不报错也不消失。

怀疑:kube-scheduler 出问题了。

检查:

kubectl describe pod web-7d9f8c5b4-abcde

证据(示例输出):

Events:
  Type     Reason             Age   From               Message
  ----     ------             ----  ----               -------
  Warning  FailedScheduling   12s   default-scheduler  0/2 nodes are available: 2 Insufficient cpu.

进一步确认绑定结果:

kubectl get pod web-7d9f8c5b4-abcde -o jsonpath='{.spec.nodeName}'

返回为空。

根因:kube-scheduler 运行正常,它只是执行了过滤逻辑,发现两个工作节点都没有满足 requests 的剩余资源,于是不做绑定。Pod 的 spec.nodeName 保持为空,etcd 里记录的就是「还没被安排」。

修复:把容器的 resources.requests 降到节点可满足的范围,或者给集群加节点。

错误尝试:直接删掉 Pod 让它重建。为什么错——调度是一个纯函数式的决策过程,输入是节点可分配资源和 Pod 的 requests。两个输入都没变,重建只是把同样的输入重新算了一遍,结果必然相同。删 Pod 解决不了这一类问题,只会浪费时间。

诊断链二:ClusterIP 完全不通

现象:集群内访问 Service 的地址超时,Pod 本身看起来是 Running。

怀疑:kube-proxy 的转发规则没有下发。

检查:

kubectl get pods -l app=web
kubectl get endpointslices -l kubernetes.io/service-name=web

证据(示例输出):

NAME                   READY   STATUS    RESTARTS   AGE
web-7d9f8c5b4-abcde    0/1     Running   0          3m

NAME   ADDRESSTYPE   PORTS   ENDPOINTS   AGE
web    IPv4          80      <unset>     3m

Pod 存在,但 READY 是 0/1;EndpointSlice 的端点列表是空的。

根因:端点控制器只把「就绪」的 Pod 地址写进 EndpointSlice。readinessProbe 一直失败,Pod 就不被认定为就绪,端点列表为空,kube-proxy 面对一个空集合,自然没有任何目标可以转发。

修复:修正 readinessProbe 的路径与端口,让探针通过;端点列表随即会被填充。

错误尝试:重启 kube-proxy。为什么错——kube-proxy 只是 EndpointSlice 的消费者,它做的是把已知端点翻译成转发规则。源数据是空的,重启一个消费者不会凭空产生数据。这个尝试即使侥幸让问题短暂消失,也只是掩盖。

两条链放在一起看,规律很清楚:现象出现在最下游,原因却常常在最上游的一个字段。

现象非根因
证据才开口
错解要记下
下次不绕路

07、优化

从第 06 章的两条链里能抽出同一个根因:非法输入进入了 etcd。

链一里,缺少合理 requests 的 Pod 被写进了 etcd;链二里,缺少可用就绪探针的 Deployment 被写进了 etcd。两者都是在对象已经持久化之后,才在执行层暴露问题。

于是 V2 的修改方向很明确:把校验前移到准入阶段。

修改一,用准入策略拒绝不合格的工作负载。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-requests-and-probe
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: >-
        object.spec.template.spec.containers.all(c,
          has(c.resources) && has(c.resources.requests) &&
          has(c.resources.requests.cpu) && has(c.resources.requests.memory))
      message: "每个容器必须声明 cpu 与 memory 的 requests"
    - expression: >-
        object.spec.template.spec.containers.all(c, has(c.readinessProbe))
      message: "每个容器必须声明 readinessProbe"
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: require-requests-and-probe-binding
spec:
  policyName: require-requests-and-probe
  validationActions: ["Deny"]

修改二,用 CRD 把服务契约变成集群里的一等对象。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: servicecontracts.platform.example.com
spec:
  group: platform.example.com
  scope: Namespaced
  names:
    kind: ServiceContract
    plural: servicecontracts
    singular: servicecontract
    shortNames: ["sc"]
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: ["owner", "alertChannel"]
              properties:
                owner:
                  type: string
                alertChannel:
                  type: string
                components:
                  type: array
                  items:
                    type: string

这么做的原因有两层。第一层是技术上的:准入发生在对象写入 etcd 之前,不合格的输入根本不会产生后续状态,Pending 和空端点这两类现象会在源头消失。第二层是协作上的:过去「这个服务归谁管」散落在文档和聊天记录里,现在它变成了一个 CRD 对象,可以被 kubectl 查询、被 RBAC 控制、被控制器消费。

新行为是什么样?提交一个缺少 requests 的 Deployment,预期结果如下。未在当前环境实测,以下为预期结果:

Error from server: error when creating "web.yaml":
The deployments "web" is invalid: [每个容器必须声明 cpu 与 memory 的 requests]

验证方法也很直接:先提交一个合规的 Deployment 确认能创建成功,再提交一个删掉 readinessProbe 的版本,确认被拒绝。两次结果不一致,说明策略确实生效了。

需要注意,ValidatingAdmissionPolicy 的可用版本与字段细节需按当前官方文档核验;如果集群版本不支持,等价的兜底方案是 ValidatingWebhookConfiguration 加一个自建 webhook,代价是要自己维护证书和高可用。

根因进准入
错误挡门外
新规要可验
旧债慢慢还

08、演进

[同一个 kubectl apply 请求]
    |
    +--[V1] 直接写入 etcd
    |      代价:非法对象进入集群,问题推迟到 kubelet 阶段暴露
    |
    +--[V2] 在 apiserver 准入阶段拦截
    |      代价:策略表达式的正确性成为新的故障面
    |
[Trade-off]
得到什么 / 非法输入零成本拒绝,故障点前移
失去什么 / 策略写错会拦下合法对象,需要版本化管理
适用边界 / 只能覆盖已知的失败模式,未知模式仍需人工排查

逐项比较。

正确性上,V2 更优。V1 依赖每个提交者自觉,V2 把规则变成了集群的强制约束。

稳定性上,V2 引入了一个新的失败点:准入策略本身。表达式写错、failurePolicy 设为 Fail 而策略服务不可用,都会导致合法对象被拒绝。这是必须接受的代价,缓解方式是先在 audit 模式下观察一段时间再切到 Deny,用 warn 模式收集误伤。

复杂度上,V1 简单到几乎为零,V2 多了两个对象类型和一个 CRD。这些对象本身也需要版本管理、需要评审、需要测试。

成本上,V2 的增量主要是认知成本和维护成本,不涉及额外的基础设施。

适用范围上,V1 适合单人维护的小集群,V2 适合多团队共用、需要契约的集群。V2 的收益随提交者数量线性增长,随集群规模增长则趋于平缓。

遗留问题有三个。第一,准入策略只能覆盖已经被我们见过的失败模式,没见过的问题照样会漏过去。第二,CRD 对象需要有人真正使用,否则它只是另一种形式的文档。第三,云相关的路径没有覆盖,cloud-controller-manager 在本地集群根本不出现,负载均衡器、路由表这些行为在这套实验里验证不到。

旧路凭人治
新路靠机制
得失去取舍
边界要写清

09、洞见

9.1 现象和组件之间隔着一条状态边界

第 06 章的两条链里,问题现象分别是 Pending 和超时,但根因都在一个字段上:一个是被调度器判定为不可绑定的 spec.nodeName,一个是被端点控制器判定为未就绪的 Pod 状态。

反直觉判断:Kubernetes 里绝大多数被当成「网络问题」的现象,第一现场其实在 etcd 的某个字段里,而不是在网络链路上。

这条判断的实用价值是:排查时不要先去看协议栈,先去看对象状态。对象状态是一手信息,网络只是它的下游表现。

9.2 控制面组件之间不互相调用

kube-scheduler 不通知 kubelet,kube-controller-manager 也不指挥 kube-scheduler。它们唯一的共同语言是 API 对象。

反直觉判断:kube-scheduler 并不会「告诉」kubelet 把 Pod 放到哪台机器,它只是往 API Server 写了一个 spec.nodeName 字段,剩下的靠 kubelet 自己 watch 出来。

理解了这一点,就能理解为什么控制面可以水平扩展——组件之间没有直接依赖,交互全部通过一个可扩展的 API 层完成,新增一个组件不需要改动其他任何组件。也能理解为什么 API Server 一挂,整个控制面就哑了,但已有容器照样跑:因为它们之间本来就不靠心跳维持关系。

9.3 实际状态永远追不上期望状态

第 03 章讲过控制循环。收敛不是一次性动作,是持续动作。

反直觉判断:集群「稳定」不是因为实际状态等于期望状态,而是因为控制器一直在纠偏。你看到的平静,是动态平衡的结果,不是静止。

推论是:如果想判断某个控制器是否正常工作,看的不是状态快照,而是它有没有在产生变更。一个停止工作的控制器和一个无事可做的控制器,在状态面上长得一模一样。

9.4 准入是成本最低的失败点

第 07 章的改动之所以值得,是因为它把失败从执行层挪到了准入层。

反直觉判断:降低故障成本最有效的办法不是让故障修得更快,而是让它根本不在系统里落地。一个被拒绝的 Deployment 不占用 etcd、不产生事件、不惊动任何人。

这条判断的边界也很清楚:准入只能拦住「可被形式化描述」的错误。资源够不够、探针写得对不对,这些都可以拦;业务逻辑对不对、流量洪峰扛不扛得住,拦不了。

直觉常有误
边界最见真
一图通全局
排查自成章

10、系统落地

原来有什么:一台能跑起来的集群,一份存在脑子里的术语印象,一套「出问题就重启」的处理习惯。

本篇新增了什么:一张覆盖控制面、节点、接口、负载、扩展五层的职责表;一次可复现的三节点实验,把术语和真实进程对应起来;两条带证据的排查链;两个准入策略对象和一个服务契约 CRD。

现在能做什么:看到任意一条集群告警,先定位到层,再定位到组件,再用一条命令拿到证据。提交一个不合格的工作负载,会在写入 etcd 之前被拒绝。服务归属可以通过 kubectl 查到,而不是去翻聊天记录。

还缺什么:第一,可观测性缺失。当前只能靠人工执行命令,没有把「现象 → 组件 → 验证命令」的映射变成自动化的检查项。第二,CRD 只是定义了类型,还没有控制器去消费它,服务契约目前只能被人读,不能被系统用。第三,云相关组件完全没覆盖,cloud-controller-manager 的职责在本地集群里无从验证。第四,准入策略的表达式没有测试,误伤风险靠人工评审兜底。

下一步如何演进:先把两条排查链固化成检查清单,挂到告警规则上,让值班同学拿到告警的同时就拿到验证命令;然后给 ServiceContract 写一个小控制器,自动校验被它管的工作负载是否满足契约要求,把两套机制接起来。至于云相关的部分,需要在一个真实的托管集群上重新做一遍第 05 章的对照实验。

旧有徒手查
新增准入关
能查能拦能追
仍缺自动修复

11、小结

Q1 → 三种根因之所以互相矛盾,是因为判断停在了现象层;Pending 的直接证据是 FailedScheduling 事件和空的 spec.nodeName,它指向 kube-scheduler 的过滤结果,而不是节点或 etcd
Q2 → 先看 EndpointSlice。它是源数据,kube-proxy 的规则是它的下游派生结果;源数据为空时,改下游没有任何意义
Q3 → 分不清这条边界,会把「执行层的一次失败」误判成「控制面的一次故障」,进而去重启本不该重启的组件,把小范围的输入错误扩散成整个集群的抖动
状态 → 一张五层职责表、一次可复现的三节点实验、两条带证据的排查链,以及两个把非法输入挡在 etcd 之外的准入策略对象

带回一句话:现象在下游,原因在上游的字段,证据在 API Server。

问题有答案
状态可交付
一图一表一链
边界即是能力

12、作业

12.1 理解题:kube-apiserver 挂了,为什么已经在运行的 Pod 不会立刻停止?

参考答案:因为已经运行的容器由节点上的 container runtime 管理,kubelet 是节点上的独立进程,不依赖到 API Server 的长连接来维持容器存活。但影响是分阶段的:节点状态不再上报,超过节点监控宽限期后 Node 会被标记为不可用,进而触发驱逐逻辑,届时 Pod 才会被清理。所以「立刻不停」不等于「永远不停」。

12.2 实战题:构造一个必定处于 Pending 的 Deployment,并给出证明它处于 Pending 而不是其他状态的命令。

参考答案:把 requests 设成超过任何一个节点可分配资源的值,例如 cpu 填一个远超单节点容量的数字,副本数设为 2。触发后执行 kubectl describe pod 查看 Events 中的 FailedScheduling 记录,再用 kubectl get pod 加 jsonpath 取 spec.nodeName 确认该字段为空。两者同时成立,才能证明是调度阶段未绑定,而不是启动阶段失败。

12.3 排障题:Service 的 ClusterIP 不通,但直接用 Pod IP 能通,应该先查什么?

参考答案:两条路径完全不同。ClusterIP 的流量要经过节点上的转发规则,由 kube-proxy 或替代它的 CNI 组件实现;Pod IP 的流量走的是 CNI 建立的网络平面。Pod IP 通说明网络平面和 Pod 本身正常,问题被锁定在转发规则这一层。此时先确认 EndpointSlice 里是否有就绪端点,再检查转发规则是否存在,顺序不能颠倒,因为规则是端点的派生结果。

12.4 架构判断题:为什么不建议绕过 kube-apiserver 直接读改 etcd 来修状态?

参考答案:有三层理由。第一,绕过认证、鉴权和准入,等于把集群的安全边界和一致性约束一次性全部放弃。第二,直接写 etcd 会破坏乐观并发控制,resourceVersion 不再被正确推进,其他组件的 watch 会收到错误或缺失的事件,缓存与存储开始不一致。第三,也是最重要的一点,status 这类字段是 kubelet 上报的观测结果,人为改写它并不会改变真实世界,只会让控制循环基于错误的前提做决策,把一个可解释的问题变成一个不可解释的问题。

理解要落地
实战要跑通
排障要证据
架构要取舍

13、思考

回到最开始那个场景:一个 Pending 的 Pod,三个人给出三种判断。

问题从来不在工具,也不在谁的水平,而在于我们没有把「谁负责什么」变成一张可以共同指认的图。当每个人脑子里都有一套自己的组件模型,「我觉得」就成了唯一可用的推理方式,而「我觉得」在故障面前一文不值。

这次梳理给我留下的最实在的一条经验是:先把边界画出来,再谈排查。边界不清,再多的排查技巧都是在猜;边界清楚,一条命令就能把范围砍掉一大半。

第二条经验是关于顺序的。Kubernetes 的每个现象都有它的上游,Pending 的上游是调度决策,调度决策的上游是资源声明,资源声明的上游是准入规则。往前追一层,问题的可修复性就提升一个量级;往后追一层,往往只是在和症状纠缠。

第三条经验是关于代价的。把校验前移到准入,失去的是「随手提交」的便利,得到的是「错误不落地」的确定性。这个交换不是免费的,策略本身也会出错,所以要在严和松之间留出观察期。

最后一点:这一整套东西,本质上只是把 API Server 这个唯一入口的位置真正认清楚了。它是入口,也是边界,也是所有证据的出处。想明白这一层,剩下的组件不过是围着它转的配角,各司其职,各负其责。

一念辨边界
一证定根因
一改知取舍
一役见真章

posted @ 2026-10-12 10:31  李福春  阅读(6)  评论(0)    收藏  举报