K8s 学习笔记(十):调用链路深挖

写在前面

承接篇9 组件。本篇把那些组件串起来,演示两条最核心的调用链路:

  • 管理面:一次 kubectl apply 怎么把一个 Deployment 跑起来(组件协作部署)
  • 数据面:一次外部 HTTP 请求怎么从公网打到 Pod(流量怎么进来)

读完这两条,“apiserver / etcd / controller / scheduler / kubelet / kube-proxy / CNI / CoreDNS / Ingress 这些到底怎么配合"就彻底通了——排查问题也知道该看哪个环节。


一、两条链路总览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
管理面(控制流)—— 部署一个 Pod
  你 → kubectl → apiserver → etcd → controller-manager → scheduler
     → kubelet → runtime → kube-proxy → Pod Running

  这是"声明 → 调谐 → 执行"链路,组件靠 watch apiserver 协作

数据面(数据流)—— 一次外部请求
  用户 → 公网 DNS → LoadBalancer → Ingress Controller → Service
     → kube-proxy 规则(iptables/ipvs DNAT) → Pod IP(CNI 转发)→ container

  这是"流量 → 路由 → 转发 → 到达"链路,靠网络组件 + 内核规则

两条链路分开看,但共享组件(apiserver 是管理面枢纽,kube-proxy / CNI 是数据面枢纽)。


二、管理面:kubectl apply 全流程

场景:kubectl apply -f deploy.yaml(一个 replicas: 3 的 Deployment)。

步骤拆解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
① kubectl apply
   kubectl 读 YAML,发 HTTPS POST/PUT 到 apiserver
   附上 kubeconfig 里的证书/Token(认证)

② apiserver 收请求
   认证(你是谁)→ 授权(RBAC:你能创建 Deployment 吗)→ 准入(mutating/validating webhook)
   校验 YAML schema,乐观锁写入 etcd
   返回成功给 kubectl

③ etcd 存储
   Deployment 对象持久化(期望状态:replicas=3,image=nginx)
   此时还没有任何 Pod —— 只是"账本上记了要 3 个"

④ Deployment Controller(在 controller-manager 里)watch 到
   它 watch apiserver 的 Deployment 资源
   看到新 Deployment → 创建一个 ReplicaSet(版本 v1)

⑤ ReplicaSet Controller watch 到
   看到新 RS 期望 3 副本,但当前 0 → 创建 3 个 Pod 对象(Pod 还没绑定节点)

⑥ kube-scheduler watch 到未调度的 Pod
   对每个 Pod:过滤可用节点 → 打分 → 选定 → 写 Pod.spec.nodeName
   Pod 现在"知道"自己该跑在哪个节点

⑦ 目标节点的 kubelet watch 到分给自己的 Pod
   kubelet 一直 watch apiserver:"有没有分给我的新 Pod?"
   收到 → 调 CRI(containerd)拉镜像、起容器
   挂载 volume、跑 liveness/readiness probe

⑧ kubelet 上报状态
   Pod Running、容器就绪 → 写回 apiserver → etcd 更新实际状态
   ReplicaSet Controller 看到实际=期望,停止创建

⑨ kube-proxy watch 到 Service 变化(如果有)
   新 Pod 的 Endpoints 变了 → 在每个节点更新 iptables/ipvs 规则
   现在 Service 的 ClusterIP 能路由到这些 Pod

⑩ 用户 kubectl get pods 看到 Running
   —— 链路闭合

关键认知:watch + reconcile

1
2
3
4
5
6
组件间不互相直连:
  kubectl 不通知 controller,controller 不通知 scheduler
  全靠"watch apiserver"——apiserver/etcd 是唯一的消息总线

  每个组件被动等待自己关心的资源变化,各自响应
  这就是 K8s 的"事件驱动 + 声明式"架构

排查 Pod 没起来:① kubectl get 卡在 Pending → scheduler 问题(资源 / 亲和);② ImagePullBackOff → kubelet 拉镜像失败;③ CrashLoopBackOff → kubelet 起了但容器崩(应用问题)。每个阶段对应不同组件,kubectl describe pod 的 Events 串起链路。


三、数据面:外部请求进 K8s 全流程

场景:用户访问 http://app.example.com,集群里有个 Ingress 把它路由到 web Service(后面 3 个 Pod)。

步骤拆解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
① 用户浏览器:http://app.example.com
② 公网 DNS 解析 app.example.com → 一个公网 IP
   这个 IP 是云 LoadBalancer / Ingress Controller 的对外入口

③ 流量到云 LoadBalancer(type=LoadBalancer 的 Service,由 CCM 创建)
   LB 把流量转到某节点的 NodePort(Ingress Controller 通过它暴露)

④ Ingress Controller(一个 Pod,通常是 NGINX)收到请求
   读 Host: app.example.com,查 Ingress 规则
   匹配到 → 找到目标 Service(web)

⑤ Ingress Controller 查 Service 的 Endpoints
   Endpoints = web Service 选中的 Pod IP 列表(由 Endpoint Controller 维护)
   Ingress Controller 选一个 Pod IP(负载均衡)

⑥ Ingress Controller 直连 Pod IP
   不经过 Service 的 ClusterIP / kube-proxy!
   (Ingress Controller 自己读 Endpoints 直接路由到 Pod)
   跨节点:CNI 负责路由(overlay/VXLAN 或 BGP,把 Pod IP 路由到目标节点)

⑦ 流量到目标节点的 Pod 网络命名空间 → container 端口
   container 处理请求,返回响应(原路或直接出网)

关键认知:ClusterIP 是虚拟的,Ingress 直连 Pod

1
2
3
4
5
反直觉点:Ingress Controller 不"经过 Service ClusterIP"
  - ClusterIP 是虚拟的(没有网卡),靠 kube-proxy 的 iptables 规则 DNAT
  - Ingress Controller 每个请求都走 DNAT 路径会很慢 + 不灵活
  - 所以它直接 watch Endpoints,拿 Pod IP 列表,自己负载均衡,直连 Pod
  - 这也是为什么删 Pod 时 Ingress Controller 要 watch Endpoints 更新,避免发到已死的 Pod

四、对比:集群内 Pod 访问 Service(走 kube-proxy)

外部请求是"经 Ingress Controller 直连 Pod”。集群内 Pod 访问 Service 名则是"走 kube-proxy 规则"。两条不一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
集群内 Pod → http://web-app(Service 名)
① CoreDNS 把 web-app 解析成 Service 的 ClusterIP(如 10.96.0.10)
② Pod 发包到 10.96.0.10:80
③ 包到达本节点内核 → 命中 kube-proxy 写的 iptables/ipvs 规则
④ 规则把目的地址 DNAT:10.96.0.10 → 某个真实 Pod IP(如 10.244.1.5)
⑤ CNI 把包路由到 Pod(本节点直连;跨节点靠 overlay/BGP)
⑥ Pod 收到

  关键:ClusterIP 是"虚拟入口",真正干活的还是 Pod IP
        kube-proxy 的规则就是"虚拟入口 → 真实 Pod"的映射
1
2
3
两种访问路径对比:
  外部 → Ingress Controller → 直连 Pod IP(绕过 ClusterIP)
  内部 → Service 名 → CoreDNS → ClusterIP → kube-proxy DNAT → Pod IP

五、组件在两条链路里的角色

1
2
3
4
5
6
7
8
9
管理面(apply)的主力:
  apiserver(枢纽)/ etcd(账本)/ controller-manager(reconcile)
  / scheduler(调度)/ kubelet(执行)

数据面(请求)的主力:
  CoreDNS(名字解析)/ Ingress Controller(七层路由)
  / Endpoints(Pod 列表)/ kube-proxy(DNAT 规则)/ CNI(跨节点路由)

  共享:apiserver 是管理面枢纽;kube-proxy/CNI 是数据面枢纽

六、按故障现象定位环节

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
现象                         卡在哪              查
─────────────────────────────────────────────────────────────
kubectl apply 后 Pod 不出现   controller/调度     kubectl get events
Pod Pending                  scheduler          kubectl describe pod(FailedScheduling)
ImagePullBackOff             kubelet/runtime    镜像名/仓库权限
CrashLoopBackOff             应用               kubectl logs
Service 名解析失败           CoreDNS            kubectl get pods -n kube-system(coredns)
Service ClusterIP 不通       kube-proxy/CNI     iptables-save / 跨节点 ping Pod IP
Ingress 503/502              Ingress Controller kubectl describe ingress + controller 日志
外部访问超时                 LB/Ingress 入口    LB 健康检查、NodePort、安全组

七、小结

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
管理面(kubectl apply):
  apply → apiserver → etcd → controller 创建 RS/Pod
       → scheduler 选节点 → kubelet 起容器 → kube-proxy 更新规则
  组件靠 watch apiserver 协作,不直连

数据面(外部请求):
  公网 → DNS → LB → Ingress Controller(查 Ingress+Endpoints)
       → 直连 Pod IP(CNI 跨节点路由)
  ClusterIP 是虚拟的,靠 kube-proxy 的 iptables/ipvs DNAT 实现

核心认知:
  ✓ apiserver/etcd 是管理面总线,组件 watch 它协作
  ✓ Service ClusterIP 虚拟,流量靠 kube-proxy 规则 DNAT 到 Pod
  ✓ Ingress Controller 直连 Pod IP,不走 ClusterIP
  ✓ CNI 管 Pod IP 分配和跨节点路由
  ✓ 故障按"管理面 vs 数据面"先分流,再定位具体组件

至此 K8s 学习笔记 10 篇走完:资源怎么用(1-8)+ 组件是什么(9)+ 组件怎么协作(10)。配上本站「云托管 K8s 实战」5 篇(云上落地),K8s 从原理到生产就齐了。