云原生 · 深度教程 · 2026 版

Kubernetes 深入实践

从容器底层原理到企业级云原生运维——一本书讲透 Kubernetes 的原理、底层、代码与实战,覆盖 v1.34 / v1.35 最新特性与 CNCF 生态全貌。

Linux 底层控制平面原理网络与存储 kubectl 实战Operator 开发GitOps 可观测性安全与 FinOps多集群
5 大篇章 23 章 10+ 架构原理图 60+ 段可运行代码与 YAML

关于本书

Kubernetes 已经成为云计算时代的"操作系统"。CNCF 2025 年度调查显示,Kubernetes 在生产环境的使用率已达到 82%,并正在成为 AI 工作负载的事实标准底座[1]。然而,大多数教程停留在"会写 YAML、会敲 kubectl"的层面:一旦遇到 CrashLoopBackOff、Service 不通、调度失败、etcd 告警、跨节点网络抖动等真实问题,缺乏底层认知的工程师往往无从下手。

本书的目标是带你穿透 YAML 往下看:从 Linux namespace、cgroups 与容器运行时讲起,逐层剖析 kube-apiserver、etcd、调度器、控制器、kubelet 的工作机制,再深入 CNI/CSI 数据平面,最后落到企业级的 GitOps、可观测性、安全、FinOps 与多集群运维。书中所有命令、YAML 与版本特性均以 2025–2026 年的 Kubernetes v1.32–v1.35 为准,并明确标注 API 版本,避免你学到已经废弃的写法。

适合谁读

应用开发工程师

想理解自己的应用在 K8s 上如何运行、如何正确配置探针与资源、如何排查线上问题。

运维 / SRE 工程师

需要掌握集群搭建升级、监控告警、备份恢复、安全加固与成本治理的完整体系。

云原生方向求职者

备考 CKA/CKS,或面试中被追问"Pod 是怎么创建出来的""Service 流量怎么走到容器"等原理题。

平台工程师 / 架构师

设计企业内部 IDP、GitOps 交付链、多集群与 AI 基础设施,需要把握 CNCF 生态全貌。

如何阅读本书

  • 初学者:按顺序阅读第一篇至第四篇,边读边在 kind 集群上动手验证每段命令。
  • 有经验者:可直接跳到第二、三篇补原理,再按需查阅第四、五篇的实战与企业运维章节。
  • 书中带 GA / Beta / Alpha 标记的特性请留意成熟度;生产环境应优先使用 GA 能力。
版本说明

本书写作时(2026 年 9 月),最新稳定版为 v1.35 "Timbernetes",v1.34 "Of Wind & Will" 的 DRA、结构化授权配置等特性已 GA[2]。Kubernetes 每 4 个月发布一个 minor 版本,具体命令输出可能略有差异,请以官方文档为准。

Part One
第一篇 · 基础篇

从云原生的演进出发,向下穿透到 Linux 内核隔离机制,向上建立 Kubernetes 架构与 API 对象的整体心智模型。

第 1 章

云原生与 Kubernetes 概览

1.1 部署形态的演进:从物理机到云原生

理解 Kubernetes 为什么会出现,先要理解它解决的痛点。应用部署形态在过去二十年经历了三次关键跃迁:

timeline
    title 应用部署形态演进
    物理机时代 : 一个应用一台机器 : 资源利用率低 : 扩容以天计
    虚拟化时代 : 一台物理机多台 VM : 隔离性好但镜像笨重 : 扩容以分钟计
    容器时代 : 共享内核的轻量隔离 : 镜像小、启动快 : 不可变基础设施
    编排时代 : Kubernetes 声明式调度 : 自愈与自动扩缩 : 扩容以秒计
              
图 1-1 部署形态演进时间线

容器解决了"打包"问题——应用连同依赖被封装成不可变镜像,在开发、测试、生产环境保持一致。但当容器数量从十个变成一万个,新的问题出现了:哪个容器该放到哪台机器?机器挂了容器谁来重建?发布新版本如何不中断流量?容器之间怎么互相发现?Kubernetes 正是为解决大规模容器编排问题而生。

1.2 Kubernetes 是什么

Kubernetes(希腊语"舵手",简称 K8s,K 与 s 之间有 8 个字母)源于 Google 2014 年开源的 Borg 系统,现由云原生计算基金会 CNCF 托管。它的官方定义是:一个可移植、可扩展的开源平台,用于管理容器化工作负载和服务,支持声明式配置与自动化。

这个定义里有四个关键词,也是 K8s 区别于一切传统运维工具的灵魂:

声明式(Declarative)

你只描述"期望状态"(我要 3 个副本),而不是下达"怎么做"的命令(创建 3 个容器)。系统持续驱动实际状态收敛于期望状态。

自愈(Self-healing)

容器崩溃自动重启、节点故障自动迁移、不满足健康检查的实例自动摘除流量。

可移植(Portable)

同一套 YAML 可以跑在裸机、私有云、AWS/Azure/GCP 上,通过 CRI/CNI/CSI 等标准接口屏蔽底层差异。

可扩展(Extensible)

CRD 可自定义资源,Operator 可自定义控制器,调度器、准入、网络、存储全部可插拔。

1.3 版本节奏与近期重要特性

Kubernetes 保持每 4 个月一个 minor 版本的发布节奏,每个 minor 提供约 14 个月的补丁支持。以下是与本书内容密切相关的近期版本特性[3][4][2]:

版本代号/时间与本书相关的重要特性
v1.32Penelope
2024-12
结构化授权配置进入 Beta;DRA 结构化参数 Beta;StatefulSet 自动删除 PVC GA
v1.33Octarine
2025-04
Sidecar Containers 正式 GA;In-Place Pod Resize 进入 Beta;Linux 用户命名空间支持
v1.34Of Wind & Will
2025-08
DRA 核心 GA(GPU/TPU/NIC 分配共享);结构化授权配置 GA;External JWT Signers Beta
v1.35Timbernetes
2025-12
In-Place Pod Resize GA(原地调整资源不重启);ipvs 模式进入废弃路径,转向 nftables

表 1-1 Kubernetes 近期版本与关键特性

注意废弃路径

kube-proxy 的 ipvs 模式计划从 v1.40 起默认禁用、v1.43 移除,新集群在 Linux 5.13+ 内核上应优先评估 nftables 模式;大量老教程中的 extensions/v1beta1、policy/v1beta1 等 API 早已删除,本书一律使用当前稳定 API(如 policy/v1、autoscaling/v2、networking.k8s.io/v1)。

1.4 CNCF 生态全景

Kubernetes 不是孤岛,它是整个云原生生态的内核。CNCF 到 2025 年已托管 230+ 项目、拥有 34 个毕业项目,涵盖运行时、网络、存储、可观测性、安全、交付、数据库等全部领域[5]。下表是生产中最常打交道的一批:

领域代表项目解决什么问题
运行时containerd、CRI-O、runc容器镜像管理与容器进程运行
网络Cilium、Calico、Flannel、CoreDNS、EnvoyPod 网络、Service 转发、DNS、四层/七层代理
存储Rook、Longhorn、MinIO分布式块/文件/对象存储
可观测性Prometheus、Grafana、Jaeger、OpenTelemetry、Fluentd指标、日志、链路三大支柱
交付Helm、Argo、Flux、Kustomize包管理、GitOps、持续部署
安全Kyverno、OPA、Trivy、Falco、SPIFFE/SPIRE策略准入、镜像扫描、运行时防护、工作负载身份
伸缩/调度KEDA、Karpenter、Cluster Autoscaler事件驱动伸缩与节点自动供给
多集群Karmada、Cluster API、Istio集群生命周期与多集群应用编排

表 1-2 CNCF 生态核心项目速览

1.5 学习路线建议

根据本书的结构,推荐如下递进路线。切忌跳过底层直接背 YAML——原理决定了你排障时能走多远。

flowchart LR
    A["Linux 基础
namespace/cgroup"] --> B["容器与镜像
OCI/containerd"] B --> C["K8s 架构
与 API 对象"] C --> D["控制平面原理
etcd/控制器/调度"] D --> E["网络与存储
CNI/Service/CSI"] E --> F["实战排障
kubectl/Helm"] F --> G["企业运维
GitOps/可观测/安全"] G --> H["平台工程
Operator/多集群/AI"] style A fill:#EAF1F9,stroke:#2456C4,color:#0F1B2D style H fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 1-2 Kubernetes 工程师进阶路线
第 2 章

容器技术底层:Namespace、Cgroups 与镜像

容器底层示意图:多个发光集装箱被命名空间光罩隔离,置于 Linux 内核电路板之上
本章导引图:容器 = Linux Namespace(隔离光罩)+ Cgroups(资源计量表)之上的标准化"集装箱"

在 Kubernetes 的世界里,最小的调度单位是 Pod,但真正在机器上跑起来的是容器,而容器并不是一种虚拟化技术——容器只是普通的 Linux 进程,被内核的 namespace 和 cgroups 施加了"视野隔离"和"资源约束"。理解这一点,是理解一切 K8s 现象的根基。

2.1 Namespace:进程眼中的"世界"

Linux namespace 让一组进程只能看到与自己相关的系统资源。容器运行时(containerd 调用 runc)在启动容器进程时,通过 clone 系统调用带上一系列 CLONE_NEW* 标志,为它创建新的命名空间:

Namespace隔离的资源在容器中的体现
PID进程号空间容器内进程从 PID 1 开始,看不到宿主机进程
Mount挂载点视图容器有独立的根文件系统(rootfs)与 /proc、/sys 视图
Network网卡、路由表、iptables、端口容器有独立的 lo 和 eth0、独立的端口空间
IPCSystem V 消息队列、共享内存容器间无法直接用共享内存通信
UTS主机名与域名容器可以有自己的 hostname
UserUID/GID 映射容器内 root 在宿主机上可映射为普通用户(v1.33 起 K8s 正式支持)
Cgroupcgroup 根目录视图容器内无法看到宿主机全部资源限制

表 2-1 Linux 七大命名空间

动手实验:亲眼看见 namespace

在任意 Linux 机器上执行以下命令,可以亲手创建一个隔离环境(需要 root):

代码清单 2-1 用 unshare 创建新的 PID + Mount + UTS namespace
$ unshare --pid --mount --uts --fork bash
# hostname my-container
# mount -t proc proc /proc
# ps aux
# 你只会看到少量进程,且 bash 的 PID 是 1 —— 这就是容器看到的世界

反过来,在宿主机上可以用 lsns 列出系统中所有 namespace,用 ls -l /proc/<pid>/ns 查看任意进程(包括容器进程)所属的 namespace。两个容器共享同一个 net namespace,正是 Pod 内容器通过 localhost 互通的原理。

2.2 Cgroups:给资源上"阀门"

Namespace 回答"能看到什么",Cgroups(Control Groups)回答"能用多少"。Kubernetes Pod 中 resources.requests/limits 的 CPU、内存、PID 限制,最终都落到 cgroups 文件系统中。

  • CPU 限制:通过 cpu.cfs_quota_us / cpu.cfs_period_us 控制单位时间内可用的 CPU 时间。limits 设为 500m 即每个 100ms 周期最多用 50ms;超过 throttling(被节流,表现为延迟尖刺)。
  • 内存限制:通过 memory.max(v2)设定上限;触顶即触发 OOM Killer,容器内得分最高的进程被杀,Pod 显示 OOMKilled(exit code 137)。
  • CPU request:转换为 cpu.shares,只是权重而非硬限制——节点空闲时容器可以用满全部 CPU。

cgroups 有两个代际:v1 为每种资源建立独立层级(/sys/fs/cgroup/cpu、/sys/fs/cgroup/memory 各自挂一棵),接口割裂;v2 采用统一层级,所有资源挂在同一棵树下,接口一致,并支持更精确的内存管控。现代发行版(Ubuntu 22.04+、containerd 2.x)默认使用 cgroup v2,K8s 也全面支持。

实战经验

节点上直接查看某个 Pod 的真实限制:找到其 cgroup 路径(v2 下形如 /sys/fs/cgroup/kubepods/<pod-uid>/<container-id>/),cat memory.max cpu.max 即可看到 kubelet 写入的上限值。

2.3 Rootfs 与 OverlayFS:镜像分层的魔法

容器的文件系统来自镜像。镜像并非一个整块文件,而是由多层只读层(layer)叠加而成,每层是一组文件变更的 tar 包,以 SHA-256 摘要做内容寻址。启动容器时,运行时在只读层之上再挂载一个可写层:

flowchart TD
    W["可写层 container layer
所有写操作 / 删除标记 / copy-up"] L3["只读层:应用代码层"] L2["只读层:依赖库层(apt/pip 安装)"] L1["只读层:基础镜像层(debian/distroless)"] W --> L3 --> L2 --> L1 style W fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style L1 fill:#EAF1F9,stroke:#2456C4,color:#0F1B2D style L2 fill:#EAF1F9,stroke:#2456C4,color:#0F1B2D style L3 fill:#EAF1F9,stroke:#2456C4,color:#0F1B2D
图 2-1 OverlayFS 镜像分层结构(lowerdir 只读叠加,upperdir 可写)

这就是 OverlayFS(Docker 默认的 overlay2 驱动)的工作方式:底层目录 lowerdir 可以有多层,顶层 upperdir 可写,merged 是容器看到的合并视图。修改一个来自底层的文件时,内核执行 copy-up:先把文件复制到可写层再修改;删除底层文件则在可写层放置一个 whiteout 标记。

分层带来两个直接收益:层可跨镜像共享与缓存(100 个基于同一基础镜像的容器只存一份基础层),镜像拉取与推送按层增量传输。这也解释了为什么 Dockerfile 中应把"变化少的层(依赖安装)放前面、变化频繁的层(代码 COPY)放后面"以最大化缓存命中。

2.4 OCI 规范与容器运行时栈

为了避免容器生态被单一厂商绑定,Linux 基金会下的 OCI(Open Container Initiative)定义了两个关键规范:

  • OCI Image Spec:规定镜像格式——manifest(层列表与配置指针)、config(环境变量、入口点、工作目录等运行参数)、按 SHA-256 标识的文件系统层。
  • OCI Runtime Spec:规定如何运行一个容器——一份 config.json 描述 rootfs 路径、namespace、cgroups、capabilities、挂载等,运行时据此创建容器。

在 Kubernetes 节点上,这些规范被分成清晰的多层组件实现:

flowchart TD
    K["kubelet"] -->|"CRI gRPC"| C["containerd
(高级运行时:镜像管理、快照、生命周期)"] C -->|"shim v2 / OCI runtime spec"| R["runc(或 crun / kata)
(低级运行时:真正 clone 进程)"] R --> N["Linux 内核
namespace + cgroups + overlayfs"] C -->|"CNI"| NET["CNI 插件
配置 Pod 网络"] style K fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style N fill:#0D1B2A,stroke:#0D1B2A,color:#FFFFFF
图 2-2 Kubernetes 节点容器运行时调用栈

需要特别注意 containerd 与 runc 的分工:runc 只是一个命令行工具,负责"一次性地"创建出容器进程然后退出;containerd 通过 containerd-shim 作为容器进程的父进程持续在场,负责收集退出状态、管理 stdio。所以你在节点上看到的容器进程父进程往往是 containerd-shim-runc-v2,而不是 containerd 本身。

为什么 Kubernetes 1.24 之后没有 dockershim 了?

Docker(dockerd)内部本来也是调用 containerd。kubelet 过去通过 dockershim 说一套 Docker 专属协议,再被翻译成 CRI,链路冗余且维护成本高。移除 dockershim 后 kubelet 直接通过 CRI 与 containerd/CRI-O 对话。镜像仍然是 OCI 格式,docker build 出来的镜像在 K8s 上照常运行,无需恐慌。

2.5 小结:一个容器的诞生

把本章串起来,节点上创建一个容器的完整底层动作是:拉取镜像各层 → 解压成快照 → overlay 挂载出 rootfs → 准备 OCI config.json → runc 设置 cgroups → clone 出新 namespace 中的进程 → pivot_root 切进 rootfs → 执行 entrypoint。Kubernetes 做的所有上层抽象,最终都要落到这几个内核机制上。

第 3 章

Kubernetes 架构总览

一个 Kubernetes 集群由控制平面(Control Plane)和若干工作节点(Worker Node)组成。控制平面做出所有全局决策,节点负责实际运行容器。生产环境中控制平面通常以多副本方式运行(3 或 5 个节点),etcd 组成奇数节点的高可用集群。

3.1 组件全景图

flowchart TB
    subgraph CP["控制平面 Control Plane"]
        API["kube-apiserver
唯一入口 / 认证授权准入"] ETCD[("etcd
集群状态存储")] SCH["kube-scheduler
选择节点"] CM["kube-controller-manager
节点/副本/端点等控制器"] CCM["cloud-controller-manager
云资源对接"] end subgraph N1["工作节点 Node"] KLET["kubelet
节点代理 / Pod 生命周期"] KP["kube-proxy
Service 转发规则"] CR["containerd + runc"] P1["Pod A"] P2["Pod B"] end USER["kubectl / 控制器 / Pod"] -->|"HTTPS REST + watch"| API API <--> ETCD SCH --> API CM --> API CCM --> API API --> KLET KLET --> CR --> P1 CR --> P2 KP --> API style CP fill:#EDF3FE,stroke:#2456C4 style N1 fill:#F1FAF4,stroke:#16A34A
图 3-1 Kubernetes 集群架构与组件交互

3.2 控制平面五大组件

kube-apiserver:集群唯一的入口

apiserver 是整个集群中唯一直接操作 etcd 的组件,对外暴露 RESTful API(JSON over HTTPS)。所有内部组件之间从不直接通信,全部通过 apiserver 中转——这个设计极大降低了耦合。apiserver 无状态、可水平扩展,多个实例前置负载均衡即可。它还承载认证、授权、准入控制三道关卡(第 6 章详解),并通过聚合层(Aggregation Layer)允许自定义 APIServer 注册新的 API 资源。

etcd:集群的唯一事实来源

etcd 是基于 Raft 协议的强一致分布式 KV 存储。集群中所有对象——Pod、Node、Service、ConfigMap、Secret、RBAC——都以 protobuf 序列化后存在 etcd 里(key 形如 /registry/pods/default/nginx-xxx)。etcd 只存期望状态与当前状态,不运行任何业务逻辑;控制器通过它的 watch 机制感知变化。生产建议:3 或 5 节点、SSD 磁盘、独立部署、定期快照备份。

kube-scheduler:给 Pod 找家

调度器监视所有 spec.nodeName 为空(未绑定)的 Pod,为每个 Pod 选择一个最合适的节点。决策分两步:过滤(Filter)排除资源不足、有污点不容忍、亲和性不满足的节点;打分(Score)对剩余节点按资源均衡、亲和、拓扑分布等加权排序,取最高分绑定。第 7 章会展开可插拔的调度框架。

kube-controller-manager:控制器们的集合

控制器是 K8s 的"自动化引擎"。一个二进制里跑着几十个控制器:Node Controller(节点失联检测与驱逐)、ReplicaSet/Deployment/StatefulSet Controller(副本维持)、Job Controller、EndpointSlice Controller(维护 Service 后端列表)、ServiceAccount Controller、PV/PVC Controller 等。它们共享 informer + workqueue 机制,各自执行独立的 reconcile 循环。

cloud-controller-manager:云的触手

把与云厂商强相关的逻辑(创建 LoadBalancer、查询实例生命周期、管理路由与卷)从核心代码中剥离,由各云厂商实现。这使得 K8s 核心可以与云厂商代码独立演进。裸金属/自建集群中该组件可以不部署。

3.3 节点三件套

  • kubelet:每个节点上的"驻场经理"。它接收通过 apiserver 分配来的 PodSpec,调用 CRI 让 containerd 创建沙箱与容器,通过 CNI 接入网络、CSI 挂载存储,并持续执行健康探针、上报节点与 Pod 状态、在资源压力时驱逐 Pod。
  • kube-proxy:并非代理业务流量本身,而是在节点上维护 Service 的转发规则(iptables / ipvs / nftables),让访问 ClusterIP 的流量被 DNAT 到后端 Pod。
  • 容器运行时:containerd(主流)或 CRI-O,负责镜像与容器。

3.4 一次 Pod 创建的完整旅程

把所有组件串起来,跟踪 kubectl apply -f pod.yaml 之后集群内部发生了什么——这是面试高频题,也是后续原理篇的总纲:

sequenceDiagram
    participant U as kubectl
    participant A as apiserver
    participant E as etcd
    participant S as scheduler
    participant C as 控制器们
    participant K as kubelet
    participant R as containerd/CNI
    U->>A: POST /api/v1/namespaces/default/pods
    A->>A: 认证 → 授权(RBAC) → 准入(Webhook)
    A->>E: 写入 Pod 对象(status 未调度)
    S->>A: watch 到新 Pod
    S->>S: Filter 过滤节点 + Score 打分
    S->>A: PUT 绑定结果 spec.nodeName=node-2
    A->>E: 更新 Pod
    K->>A: watch 到绑定给本节点的 Pod
    K->>R: RunPodSandbox(CRI) + CNI 分配 IP
    K->>R: 拉镜像 / 创建容器 / 挂载卷
    K->>A: 上报 status.phase=Running + PodIP
    C->>A: watch 到状态变化,持续 reconcile
              
图 3-2 Pod 创建的端到端时序
关键认知

整个过程中没有任何组件是被"命令"的:kubectl 只是写入了一个对象;调度器、kubelet 都是 watch 到变化后各自行动。任何一步失败,对应控制器都会重试而不是报错退出——这就是声明式系统与命令式脚本的本质区别。

3.5 高可用部署形态

生产集群的控制平面通常堆叠或外置部署:3 个控制平面节点各跑一份 apiserver(前面挂 LB)、scheduler 与 controller-manager(利用 leader election 保证同一时刻只有一个活跃实例,但所有副本都在待命)、一个 3/5 节点的 etcd 集群(Raft 多数派:容忍 (n-1)/2 个节点故障,所以必须奇数)。工作节点通过 LB 的虚拟 IP 访问 apiserver,单个控制平面节点宕机不影响集群运行。

第 4 章

核心 API 对象

Kubernetes 中一切皆"对象"(Object),每个对象都是一段期望状态的声明。先建立通用结构的概念,再逐一认识最重要的几类对象。

4.1 对象的通用结构

无论哪种资源,YAML 都由四个字段统领:

字段含义
apiVersionAPI 组与版本,如 apps/v1、batch/v1、networking.k8s.io/v1
kind对象类型,如 Deployment、Pod、Service
metadata名称、命名空间、labels、annotations、ownerReferences、resourceVersion 等
spec期望状态,由用户填写
status实际状态,由系统回填,只读

表 4-1 Kubernetes 对象的通用字段

Labels 是键值标签,用于松耦合的选择与关联:Deployment 用 selector.matchLabels 找到自己管的 Pod,Service 用 selector 找到后端。Annotations 同样是键值,但不用于选择,用于放元信息(构建版本、负责人、Ingress 控制器配置、last-applied-configuration 等)。ownerReferences 记录"谁创建了我",构成对象间的父子垃圾回收链:删除 Deployment 会级联删除其 ReplicaSet 和 Pod。

4.2 Pod:最小调度单元

Pod 是一个或多个共享网络与存储的容器的集合,是 K8s 中最小的可调度单位。为什么不直接调度容器?因为大量真实场景需要多个强耦合进程协同(应用 + 日志采集 sidecar + 服务网格代理),它们需要共享 localhost、共享卷、共享生命周期。

Pod 内容器共享网络的秘密是基础设施容器 pause(infra 容器):每个 Pod 启动时第一个创建的是 pause 容器,它只做一件事——持有 Pod 的 network namespace 并作为 PID 1 回收僵尸进程;业务容器逐个加入这个 namespace,因此彼此通过 localhost 通信,共享同一个 Pod IP。

Pod 生命周期相位

相位含义
Pending已被 apiserver 接受,但尚未完成调度或镜像仍在拉取
Running已绑定节点,至少一个容器正在运行或处于启动/重启中
Succeeded所有容器成功退出且不会重启(Job 场景)
Failed所有容器已退出且至少一个失败
Unknown通常因节点失联无法获取状态

三类容器:普通、Init 与 Sidecar

  • 普通容器:长期运行的主业务容器。
  • Init Container:在业务容器启动前顺序执行、全部成功才继续的初始化容器,常用于等待依赖、迁移数据库、下载配置。
  • Sidecar Container GA v1.33:本质是 restartPolicy: Always 的特殊 init 容器,它会先于主容器启动并持续运行,在所有主容器退出后才终止。它解决了传统 sidecar 与 Job 协作时的"启动顺序"和"任务完成后 sidecar 不退出"两大顽疾。

三种探针

探针失败的后果典型用途
livenessProbekubelet 杀死并重启容器捕获死锁等"进程在但不可用"
readinessProbe从 Service Endpoints 摘除,不重启预热未完成/依赖不可用时拒流
startupProbe失败计数内杀死重启;成功前另两种探针不生效保护启动很慢的 JVM 类应用

表 4-2 三种健康探针对比

常见误用

把依赖(数据库)写进 livenessProbe:依赖抖动会导致整个集群应用被批量重启,形成雪崩。依赖检查应放在 readinessProbe。探针动作支持 httpGet、tcpSocket、grpc 与 exec。

QoS 与资源

根据 requests/limits 的设置,Pod 被划分为三级 QoS,节点资源紧张时驱逐顺序从低到高:BestEffort(完全不设)→ Burstable(设了但 requests≠limits)→ Guaranteed(每容器 requests 与 limits 的 CPU、内存都相等,最后被驱逐)。关键基础设施组件建议设为 Guaranteed。

4.3 工作负载控制器

控制器管理对象典型场景
ReplicaSet维持指定数量的相同 Pod一般不直接用,是 Deployment 的下层
Deployment管理 ReplicaSet,支持滚动更新/回滚无状态 Web、API、微服务(最常用)
StatefulSet有序、有稳定身份与独立存储的 PodMySQL、Kafka、etcd、ZooKeeper
DaemonSet每个节点(含新节点)一个 Pod日志采集、监控 Agent、CNI、CSI Node 插件
Job / CronJob运行到完成的 Pod / 定时触发 Job批处理、数据迁移、定时备份

表 4-3 五类核心工作负载控制器

StatefulSet 提供三件 Deployment 没有的东西:稳定的网络身份(Pod 名固定为 web-0、web-1,配合 Headless Service 得到 web-0.nginx.default.svc 这样的 DNS 名)、稳定的独立存储(volumeClaimTemplates 为每个 Pod 生成独立 PVC,重建不丢盘)、有序部署与扩缩(默认按 0→N 顺序启动、逆序终止)。配合 podManagementPolicy: Parallel 可放宽启动顺序以提速。

Deployment 滚动更新由两个旋钮精确控制:maxSurge(更新时最多超出期望副本数多少,可为百分比)与 maxUnavailable(最多允许多少个不可用)。例如 maxSurge: 25%, maxUnavailable: 0 表示先多出四分之一新 Pod、且全程不允许容量下降——适合容量敏感的服务。

4.4 服务发现与入口:Service / EndpointSlice / Ingress / Gateway API

Pod IP 会随重建变化,客户端无法直接依赖。Service 为一组 Pod 提供稳定的虚拟 IP(ClusterIP)与 DNS 名,并负载均衡到后端。四种类型:

  • ClusterIP(默认):仅集群内可达;None 时为 Headless Service,直接返回各 Pod IP,是 StatefulSet 对等发现的基础。
  • NodePort:在每个节点上开放一个静态端口(默认 30000–32767)。
  • LoadBalancer:由云厂商创建外部负载均衡器。
  • ExternalName:CNAME 到集群外域名。

Service 的后端列表不再存储在旧的 Endpoints 对象(大对象、全量更新),而是由 EndpointSlice 按最多 100 个端点分片承载,显著提升大规模集群的扩展性,还携带 nodeName、zone 等拓扑信息以支持拓扑感知路由。

Ingress 是七层(HTTP/HTTPS)入口,按 host 与 path 路由到 Service,终结 TLS;具体转发由 Ingress Controller(ingress-nginx、Traefik 等)实现,注意必须指定 ingressClassName。新一代的 Gateway API(v1.3 于 2025 年发布)正在取代 Ingress:它把角色拆为 GatewayClass/Gateway/HTTPRoute,支持流量切分、请求镜像、CORS、重试预算等,表达能力与多实现一致性远优于 Ingress 注解生态。

4.5 配置与存储对象速览

  • ConfigMap:非敏感键值配置,可作为环境变量、命令参数或文件卷注入。
  • Secret:敏感数据(密码、令牌、TLS 证书),默认仅 base64 编码而非加密,启用 encryption at rest 才能落盘加密;挂载方式同 ConfigMap,且 kubelet 支持投影为 tmpfs 内存卷。
  • PV / PVC / StorageClass:持久卷、用户的存储声明、动态供给模板(详见第 12 章)。
  • Namespace:资源与权限的逻辑隔离边界,配合 ResourceQuota 做多租户配额。
查看对象字段的最佳老师

kubectl explain pod.spec.containers.resources 可以层层下钻查看任意字段的类型与说明,比搜索引擎更快、且永远与当前集群版本一致。

Part Two
第二篇 · 原理篇

深入声明式 API、etcd、准入控制链、控制器 Reconcile 循环、调度框架与 kubelet 内部机制,理解集群"为什么会自己动起来"。

第 5 章

声明式 API 与 etcd

etcd Raft 共识示意图:一个金色 Leader 节点向两个 Follower 复制事务日志
本章导引图:etcd 基于 Raft 共识——Leader 接收写入并复制日志,多数派确认后提交,保证集群状态强一致

5.1 声明式 vs 命令式:一行命令背后的哲学差异

命令式系统的交互是"做这个":systemctl start nginx、docker run、kubectl scale --replicas=3——调用方必须知道当前状态并承担编排逻辑。声明式系统的交互是"世界应该长这样":你提交一份完整的期望状态(YAML),系统负责持续对比现状与期望、执行必要动作、处理失败重试,并允许任何角色随时重新提交期望。

kubectl apply 与 kubectl create --edit 的区别正在于此。apply 是声明式的:它把上次提交的内容记录在 annotation kubectl.kubernetes.io/last-applied-configuration 中,下次 apply 时做三方 diff(上次提交 / 本次提交 / 集群当前对象),只删除你从配置中移除的字段;而 replace 会用新对象整体覆盖,容易误删其他控制器(如 HPA)写入的字段。

核心心智模型:Level-Triggered(水平触发)

K8s 控制器不依赖"事件不能丢",而是周期性地拿到当前状态的完整水位再决定动作。哪怕控制器重启漏掉 100 个事件,重启后一次 LIST 就能看到全部现状并继续收敛。这是它能稳定支撑百万级对象的关键,也是你写 Operator 时必须遵循的原则:Reconcile 必须幂等、可重入,不能依赖事件顺序。

5.2 LIST/WATCH:集群的"神经系统"

声明式系统要持续收敛,控制器必须高效地感知变化。轮询(每隔几秒 LIST 全量)在十万 Pod 的集群里会压垮 apiserver。K8s 的答案是 LIST + WATCH 协议(基于 HTTP 长连接 + chunked JSON,或新版的 HTTP/2 流式 protobuf):

  1. 客户端先 LIST 全量对象,响应中携带最新的 resourceVersion(RV,etcd 全局递增版本号);
  2. 随后携带该 RV 发起 WATCH,apiserver(底层 watch etcd)把之后的每次增删改以事件流推送:ADDED / MODIFIED / DELETED;
  3. 连接中断时,客户端从最后看到的 RV 重新 WATCH 续传;RV 太老(已被 etcd compact)则重新 LIST。

Watch Bookmark 是一种特殊事件:它不携带对象,只定期告知"我已经同步到 RV=N 了",让客户端知道自己的延迟水位,避免服务端为维持历史事件而保留过多数据。

5.3 Informer:客户端的标准 watch 框架

所有控制器(包括 scheduler、kubelet)都不直接手写 watch,而是使用 client-go 的 SharedInformer 框架。它的内部结构值得每个 K8s 工程师理解:

flowchart LR
    A["apiserver
LIST + WATCH"] -->|"事件流"| B["Reflector
续传/重连/去重"] B --> C["DeltaFIFO
按对象排队的增量队列"] C --> D["Informer 控制循环
Pop + 写入本地缓存"] D --> E["Indexer/Store
本地只读对象缓存"] D -->|"Add/Update/Delete 回调"| H["ResourceEventHandler"] H --> F["WorkQueue
去重 + 限速重试"] F --> G["Worker: Reconcile(key)
从缓存读对象 → 调 API 收敛"] G -.->|"失败重新入队"| F style A fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style G fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 5-1 client-go SharedInformer 内部机制

这个设计解决了四个问题:本地缓存让控制器读对象不打 apiserver(所有控制器共享 informer 时全节点只有一份 watch);WorkQueue 去重让 100 次修改合并成 1 次 reconcile;限速重试(指数退避 + 令牌桶)避免故障对象热循环打爆 apiserver;事件与处理解耦使 worker 可以水平扩并发。

5.4 etcd:Raft、MVCC 与运维要点

etcd 是整个集群状态的底座,理解它的三个机制对运维至关重要。

Raft:为什么是奇数节点

etcd 用 Raft 共识算法复制数据:每次写请求必须由 Leader 复制到多数派(quorum = n/2+1)节点持久化后才返回成功。3 节点容忍 1 台故障、5 节点容忍 2 台。偶数节点不会提高容错数(4 节点仍只容忍 1 台),反而因为 quorum 更大而更易在网络分区时不可用——所以 etcd 永远部署奇数个。Leader 故障时剩余节点在选举超时内发起投票选出新 Leader,期间集群不可写(通常 1–2 秒)。

MVCC:为什么需要 compact

etcd 的 KV 是多版本的:同一个 key 每次修改都生成一个新 revision,旧版本保留。WATCH 依赖这些历史版本实现续传。但历史无限累积会让 bbolt(底层 B+ 树存储引擎)膨胀、影响性能,因此必须定期 compact(压缩)——丢弃某 revision 之前的历史;压缩后空间不会自动归还,还需 defrag(碎片整理)。kubeadm 部署的 etcd 默认由 apiserver 参数 --etcd-compaction-interval(默认每 5 分钟)自动压缩。

运维项建议 / 命令
磁盘必须 SSD/NVMe;etcd 对 fsync 延迟极敏感,wal_fsync 延迟 P99 应低于 10ms
快照备份etcdctl snapshot save 定期执行,备份是唯一能防 etcd 多数派损毁(如 3 节点坏 2 台)的手段
空间配额默认 2GB,写满后集群只读告警;监控 etcd_mvcc_db_total_size_in_bytes 并做 defrag
网络控制平面节点间 RTT 建议 10ms 以内;跨可用区部署要评估选举超时
版本生产推荐 3.5.11+;K8s 1.32+ 已不再支持 3.4 以下版本

表 5-1 etcd 生产运维要点

5.5 一次 WATCH 事件的完整路径

把 5.2–5.4 串起来:你 kubectl edit 改了一个 Deployment → apiserver 校验后 PUT 给 etcd → etcd Leader 通过 Raft 复制、多数派确认后提交、新 revision 产生 → etcd 通过 watch channel 通知所有订阅者 → apiserver 把事件转发给所有 watch 该资源的客户端(Deployment Controller、HPA、你的 kubectl get -w)→ 各 informer 的 DeltaFIFO 入队 → worker 触发 reconcile。一次写入,驱动整个集群的自动化。

第 6 章

kube-apiserver 与准入控制链

每个请求到达 apiserver 后都要穿过一条严格的处理流水线。理解这条流水线,你才能明白 RBAC、ServiceAccount、Webhook、资源限额分别在哪一层生效,以及"我的请求为什么被 403/拒绝"该去哪里查。

6.1 请求处理三关:认证 → 授权 → 准入

flowchart LR
    R["客户端请求
kubectl/kubelet/controller"] --> A1["Authentication
你是谁"] A1 -->|"401"| X1["拒绝"] A1 --> A2["Authorization
你能做吗"] A2 -->|"403"| X2["拒绝"] A2 --> A3["Mutating Admission
可改写对象"] A3 --> A4["Schema 校验
字段类型/必填"] A4 --> A5["Validating Admission
策略检查"] A5 -->|"拒绝"| X3["拒绝"] A5 --> E[("etcd 持久化")] style A1 fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style A3 fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style A5 fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style E fill:#0D1B2A,stroke:#0D1B2A,color:#FFFFFF
图 6-1 apiserver 请求处理流水线(注意 Mutating 在 Validating 之前)

第一关:Authentication(认证)

认证模块回答"你是谁",且多个认证器串联,任一成功即放行:

  • X.509 客户端证书:kubeadm 集群的默认方式,证书由集群 CA 签发,CN 字段是用户名、O 字段是组(如 O=system:masters)。
  • Bearer Token / ServiceAccount Token:Pod 内默认挂载的投影卷 JWT,apiserver 本地验签;v1.34 起支持 External JWT Signers,可联合外部 OIDC 身份。
  • Bootstrap Token、Webhook、OIDC:节点引导、外部认证服务、企业 SSO 场景。

认证失败返回 401。注意 apiserver 不维护用户数据库——用户是证书或令牌"声明"出来的,能不能做事由下一关决定。

第二关:Authorization(授权)

授权回答"你(用户/组)能否对某资源执行某动词"。多个授权器串联,任一允许即放行:Node(专门约束 kubelet 只能操作自己节点上的资源)、RBAC(主角)、Webhook。结构化授权配置(Structured Authorization Configuration)在 v1.34 GA,允许用配置文件串联多个 authorizer 并在 webhook 上用 CEL 表达式做匹配条件,避免每个请求都调用外部 webhook[2]。

第三关:Admission(准入)

通过授权后,对象在写入 etcd 前还要经过准入插件链,分两类:

  • Mutating:可以修改对象——如自动注入 sidecar、补默认值、补 ServiceAccount 挂载。因为会改写对象,必须先执行。
  • Validating:只做判定不能修改——如 Pod Security、ResourceQuota、自定义策略。

除了内置插件(NamespaceLifecycle、ServiceAccount、ResourceQuota、PodSecurity 等),生产中主要通过两种扩展机制:

  • Admission Webhook:apiserver 通过 HTTPS 调用你部署的服务(MutatingWebhookConfiguration / ValidatingWebhookConfiguration)。服务网格自动注入、证书管理、策略校验都靠它。
  • ValidatingAdmissionPolicy GA v1.30:用 CEL 表达式直接在 apiserver 内联校验,无需部署任何 webhook 服务,省掉一条网络调用与一整套高可用负担,是策略管理的未来方向。
Webhook 的两个生产事故源

① webhook 服务自身挂了且 failurePolicy: Fail——所有相关请求全被拒,集群可能"锁死"(连修复 webhook 的 Pod 都创建不了);② webhook 拦截范围过大(如拦所有资源的 CREATE)导致 apiserver 延迟飙升。务必配置 namespaceSelector 排除 kube-system、配置合理超时,并提供自举逃生通道。

6.2 聚合层:把自定义 API 注册进 apiserver

除了 CRD(自定义资源直接由 apiserver 持久化),还可以通过 APIService 对象把某 API 路径(如 v1beta1.metrics.k8s.io)"委托"给你自己编写的 apiserver 进程:请求进来时主 apiserver 像反向代理一样转发。metrics-server 就是最典型的例子——kubectl top 的数据并不存在 etcd,而是聚合 apiserver 实时向各 kubelet 拉取汇总。选择建议:CRD 满足 95% 场景;只有需要自定义存储、复杂子资源(如 exec、proxy)时才用聚合层。

6.3 动手实验:审计与权限排查

开启 Audit Policy 后,apiserver 会把每个请求按策略记录为审计事件(谁、从哪、对什么、做了什么、结果),这是排查"谁删了我的 Deployment"的唯一可靠手段。排查权限问题则用:

代码清单 6-1 权限自检与审计日志关键字段
$ kubectl auth can-i create pods
yes
$ kubectl auth can-i delete secrets --as=system:serviceaccount:default:my-sa -n default
no
# 审计事件 JSON 关键字段:user.username / verb / objectRef.resource / responseStatus.code
第 7 章

控制器模式与调度框架

7.1 Reconcile:Kubernetes 的核心算法

Kubernetes 里几乎没有"命令式动作",只有一个个控制循环。每个控制器的逻辑都可以浓缩为同一段伪代码:

代码清单 7-1 所有控制器的统一骨架(Reconcile Pattern)
for {
    desired := 从 spec 读取期望状态
    actual  := 观察集群当前实际状态(读本地 informer 缓存)
    if desired != actual {
        执行 create / update / delete 使 actual 趋向 desired
    }
    // 即使本轮出错,也只是重新入队,下一轮继续
}

以 ReplicaSet Controller 为例:它 watch ReplicaSet 与 Pod;发现副本不足就创建 Pod(创建出的 Pod 带着 ownerReference),过多就删除;Pod 被节点驱逐、被手动删除、甚至被别的控制器误删——任何偏差都会在下一轮循环被发现并修正。这种"终态导向 + 无限重试"让系统拥有了自愈能力,也让组件崩溃变得无关紧要:重启后接着 reconcile 即可。

典型的控制器协作链(以 Deployment 为例):Deployment Controller 只管理 ReplicaSet(扩新 RS、缩旧 RS)→ ReplicaSet Controller 管理 Pod 数量 → Scheduler 给未绑定 Pod 选节点 → kubelet 运行 Pod。每个环节只做一件事,通过对象状态松耦合。

7.2 调度框架(Scheduling Framework)

调度器本身也被重构为插件化框架(v1.19 起稳定)。一次调度分两个阶段:Scheduling Cycle(串行,决定节点)与 Binding Cycle(可并发,执行绑定)。核心流水线在若干扩展点上依次调用插件:

flowchart LR
    Q["队列排序
queueSort"] --> PF["preFilter
前置准备"] PF --> F["Filter
过滤不可行节点"] F --> PS["preScore"] PS --> S["Score
并行打分加权"] S --> R["Reserve
预占资源"] R --> P["Permit
可延迟/批准/拒绝"] P --> PB["preBind"] PB --> B["Bind
写 spec.nodeName"] B --> POST["postBind"] style F fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style S fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style B fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 7-1 调度框架扩展点流水线(postFilter 在 Filter 全部失败时触发抢占)

内置插件实现了你熟悉的所有策略:NodeResourcesFit(CPU/内存/临时存储是否够)、NodeAffinity、PodAffinity/AntiAffinity、TaintToleration、NodeUnschedulable、VolumeBinding(处理 WaitForFirstConsumer 存储)、TopologySpreadConstraints、DefaultBind 等。v1.32 引入的 SchedulerQueueingHints(Beta 默认开启)让 Pod 在相关事件发生时才重新入队,显著提升大规模集群的调度吞吐。

7.3 调度策略五件套

机制方向作用
nodeSelector / nodeAffinityPod → 选节点硬性/软性要求节点标签(如 GPU 机型、可用区)
podAffinity / podAntiAffinityPod → 参考 Pod希望与某类 Pod 同域(就近)或跨节点/跨域分散(高可用)
topologySpreadConstraintsPod → 拓扑按 hostname/zone/自定义拓扑域做均衡分布,比反亲和更精确可控
taints + tolerations节点 → 排斥 Pod节点"拒绝"普通 Pod,Pod 必须声明容忍才能上
priority + preemptionPod → 抢资源高优先级 Pod 无法调度时,驱逐低优先级 Pod 腾位

表 7-1 Kubernetes 调度策略矩阵

污点三效应

  • NoSchedule:不容忍的新 Pod 不调度,已有 Pod 不动(专用节点隔离最常用);
  • PreferNoSchedule:尽量避开;
  • NoExecute:不容忍则不调度,且节点上已有的不容忍 Pod 会被驱逐。

节点出问题时 kubelet/node-controller 还会自动打内置污点:node.kubernetes.io/not-ready、memory-pressure、disk-pressure、pid-pressure、network-unavailable,这就是为什么关键 DaemonSet(如 CNI)必须容忍这些污点才能在故障节点上继续工作。

排查 Pending 的第一现场

kubectl describe pod <p> 的 Events 里调度器会直接给出过滤失败原因,如 0/5 nodes are available: 3 Insufficient cpu, 2 node(s) had untolerated taint——这行信息就是 Filter 阶段各插件的拒绝汇总,无需猜测。

第 8 章

kubelet 与容器运行时深入

8.1 kubelet 的主循环与 PLEG

kubelet 是节点上的"驻场经理",它的核心工作流:

  1. 通过 apiserver watch(或静态 Pod 目录 /etc/kubernetes/manifests)获取分配到本节点的 PodSpec;
  2. PLEG(Pod Lifecycle Event Generator)定期(或通过 CRI 事件)对比容器实际状态与期望,生成"容器退出/启动失败"等生命周期事件,驱动 Pod 状态机更新——PLEG 卡住会导致节点被判定 NotReady,经典报错 PLEG is not healthy;
  3. 调用容器运行时执行同步:创建沙箱 → 挂载卷 → 拉镜像 → 启动容器;
  4. 运行探针、汇报资源用量(metrics-server 数据来源)、更新 status;
  5. 执行驱逐管理器与节点状态上报。

8.2 CRI:kubelet 与运行时之间的契约

CRI(Container Runtime Interface)是 kubelet 与运行时之间的 gRPC/protobuf 接口,核心两组服务:

  • ImageService:ListImages / PullImage / ImageStatus 等;
  • RuntimeService:RunPodSandbox / StopPodSandbox / CreateContainer / StartContainer / Exec / 以及事件流(容器退出事件,让 PLEG 不再纯轮询)。

一次 Pod 启动的 CRI 调用顺序很能说明问题:RunPodSandbox(创建 pause 容器、建 netns)→ 运行时调用 CNI 插件给沙箱配网络 → 对每个业务容器 PullImage → CreateContainer(生成 OCI spec)→ StartContainer(runc 启动进程)。v1.26 起 CRI v1 是唯一主线接口。

8.3 containerd 2.x 架构要点

containerd 内部是 client/server 结构:ctr/kubelet 作为 client 经过 containerd 的 CRI 插件 → content store(按摘要存镜像层)→ snapshotter(管理 overlay 挂载,也可换成 btrfs/zfs)→ 分配 bundle(OCI config.json + rootfs)→ 通过 task service 启动 shim → shim 调用 runc。运维常用工具:

代码清单 8-1 节点上直接使用 crictl / ctr 排查
$ crictl ps -a                    # 查看所有容器(含退出),比 docker ps 更贴近 kubelet 视角
$ crictl logs <container-id>    # 容器日志
$ crictl inspect <container-id> | jq .status.exitCode
$ crictl images                  # 节点上已拉取的镜像
$ ctr -n k8s.io containers list  # containerd 原生视角(注意 k8s.io 命名空间)
镜像拉不下来?看 credential provider

containerd 旧版本通过在节点配置 dockergf secret 方式拉私有镜像;新版 kubelet 支持 image pull credential provider(v1.34 起可用 projected ServiceAccount token 动态换取云厂商 registry 凭证),节点上不再需要长期 cloud credential。

8.4 探针执行机制与常见坑

探针由 kubelet 在节点本地周期性执行(不经网络策略、不经 Service)。exec 探针在容器内执行命令,需要镜像里有 shell/二进制——这正是 distroless 镜像中 exec 探针失败、改用 HTTP/gRPC 探针的原因。参数语义要记牢:initialDelaySeconds(首次延迟)、periodSeconds(间隔)、timeoutSeconds、failureThreshold(连续失败几次判死)、successThreshold。startupProbe 成功之前,liveness/readiness 全部暂停,慢启动应用只配 startup + readiness 即可。

8.5 节点资源管理:驱逐与预留

kubelet 不只是"干活的",还是节点资源的看门人。

  • 系统预留:--kube-reserved / --system-reserved 为 kubelet、SSH、systemd 等预留 CPU/内存,防止 Pod 把节点资源吃尽导致连不上机器。
  • 驱逐策略:当节点内存、磁盘、inode、临时存储越过水位(如 memory.available<100Mi),kubelet 主动驱逐 QoS 等级最低的 Pod,并给节点打上 memory-pressure 等污点。
  • 节点失联:kubelet 默认每 10s 上报心跳(NodeLease 对象,每 10s 续约);node-controller 在 grace period(默认 40s)后标记 NotReady,再经宽限期(默认 5 分钟)触发 API 层驱逐,在其他节点重建 Pod。

8.6 DRA:GPU 等异构资源的新分配方式 v1.34

传统的 Extended Resources 只能做"整数计数"(nvidia.com/gpu: 2),无法表达设备拓扑、共享、驱动配置。DRA(Dynamic Resource Allocation)在 v1.34 核心 GA:它把硬件抽象为 ResourceClaim / ResourceSlice / DeviceClass 等对象,由设备驱动(以 kubelet 插件形式运行)声明节点上的设备池,调度器像处理存储一样处理设备请求——支持拓扑感知选择、多容器共享同一设备、管理员 adminAccess(只读/诊断用途)。这是 Kubernetes 承载 AI/ML 工作负载(多卡 GPU、RDMA 网卡、TPU)的关键基础设施。配合 v1.35 GA 的 In-Place Pod Resize(原地修改 requests/limits,按 CPU/内存可调整性决定是否重启),K8s 对资源的掌控正在从"一次性静态分配"走向"运行时动态编排"。

Part Three
第三篇 · 网络与存储篇

沿着数据包的真实路径理解 Pod 网络、CNI、Service 转发、CoreDNS、eBPF 与服务网格,再深入 PV/PVC 与 CSI 的供给链路。

第 9 章

Kubernetes 网络模型与 CNI

容器网络示意图:两排 Pod 立方体跨服务器通过隧道与 eBPF 钩子交换橙色数据包
本章导引图:跨节点 Pod 通信由 CNI 配置的路由/隧道承载,eBPF 数据面在内核钩子上拦截并转发数据包

K8s 网络是初学者最大的迷雾,因为它横跨 Linux 内核网络栈、CNI 插件实现和 iptables/eBPF 多个层面。本章先把最基础的三条原则和 Pod 网卡的诞生过程讲透。

9.1 Kubernetes 网络模型三条铁律

  1. 每个 Pod 获得集群唯一 IP,Pod 内容器共享该 IP(共享 netns);
  2. 任意 Pod 间可以不经 NAT 直接通信,无论是否跨节点;
  3. 节点上的代理(kubelet、DaemonSet)可以访问本节点所有 Pod。

注意:K8s 只定义模型,不实现网络。满足这三条模型的具体方案由 CNI 插件提供——这就是为什么装集群必须装 Flannel/Calico/Cilium 之一,否则节点 Ready 但 Pod 没 IP。

9.2 Pod 网卡诞生记:netns、veth pair 与 CNI

回顾第 2 章:容器有独立 network namespace。一个 Pod 的网络是这样搭建的:

flowchart LR
    subgraph NODE["一台 Linux 节点"]
        VETH1["veth 端
eth0 @ Pod netns"] VETH2["veth 另一端
cni0/cilium_host 等"] BR["二层网桥 / 路由 / eBPF 程序"] NS["Pod network namespace
(pause 持有)
eth0: 10.244.1.7"] VETH1 --- VETH2 VETH2 --> BR end BR -->|"跨节点: 路由 / VXLAN 隧道"| OTHER["其他节点的 Pod"] style NS fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style BR fill:#FEF3E2,stroke:#D97706,color:#0F1B2D
图 9-1 Pod 网络:veth pair 一端在 Pod netns,一端在宿主侧

veth pair 是一对虚拟网卡:从一端写入的数据从另一端冒出,像一根虚拟网线。kubelet 调用 CNI 插件时传入沙箱的 netns 路径,插件负责:创建 veth pair → 一端放进 Pod netns 并命名为 eth0、配置 IP 与路由(默认路由指向对端)→ 另一端接入宿主机网桥或挂 eBPF 程序 → 完成跨节点转发所需的主机路由/隧道配置。

9.3 CNI 规范:插件链与 IPAM

CNI(Container Network Interface)规范极其简单:运行时以 JSON 配置通过 stdin 调用插件二进制,只有两个核心命令 ADD(建网)和 DEL(删网)。它的几个关键设计:

  • 插件链(chaining):主插件(calico/cilium)配完网络后,结果作为输入依次传给 portmap(实现 hostPort)、bandwidth(TC 流量整形)、cilium-firewall 等元插件,各管一段。
  • IPAM 与主插件解耦:IP 地址管理独立成插件——host-local(从节点本地网段文件分配,Flannel 模式)或从中心化数据结构分配(Calico 的 block、Cilium 的 ipam 模式)。
  • CNI 版本:K8s 要求 CNI 插件兼容 v0.4.0+,推荐 v1.0.0+。

在节点上可以直接看到 CNI 的痕迹:ip -d link show 中的 veth 接口(通常以 cni、eth0@ifXX 编号)、/etc/cni/net.d/ 下的配置文件、/opt/cni/bin/ 下的插件二进制。

9.4 跨节点通信的两条路线

Pod IP 跨节点可达,CNI 实现上分为两大流派:

路由型(underlay / 三层直达)

节点通过 BGP(Calico)或静态路由互相告知 Pod 网段,数据包不封装按原 IP 路由转发,性能最好。要求节点间二层/三层网络允许自定义路由(云上通常不允许,故云上多用 VXLAN)。

覆盖型(overlay / 隧道)

把 Pod 原始包封装进 VXLAN(UDP 4789)或 IPIP 隧道,以节点 IP 为外层地址穿越任意底层网络。代价是封装/解封开销和 MTU 减小(通常要调到 1450 左右,MTU 不匹配是疑难丢包的常见原因)。

第 10 章

Service、kube-proxy 与服务发现

Pod IP 易逝,Service 提供稳定入口。但 Service 的 ClusterIP 并不绑定在任何网卡上——它只存在于每个节点的转发规则里。本章追踪一个数据包的 DNAT 之旅。

10.1 数据包的真实旅程

假设 Pod 访问 my-svc.default.svc.cluster.local:80(ClusterIP 10.96.1.100),后端是另一个节点上的 Pod 10.244.2.15:80:

sequenceDiagram
    participant P as 客户端 Pod
    participant L as 本节点 netfilter
    participant RP as kube-proxy 写入的规则
    participant N as 后端 Pod 所在节点
    participant B as 后端 Pod
    P->>L: 发包给 10.96.1.100:80
    Note over L,RP: PREROUTING/OUTPUT 链匹配 Service
随机选择一个端点(DNAT) L->>N: 目标改写为 10.244.2.15:80, 经 CNI 网络跨节点 N->>B: 本地路由入 Pod B-->>P: 回包源地址 10.244.2.15 Note over L: conntrack 根据已有连接自动 SNAT/还原
客户端看到的对端仍是 ClusterIP
图 10-1 ClusterIP 流量的 DNAT 与 conntrack 回程

10.2 kube-proxy 三种数据平面

模式机制特点与趋势
iptables为每个 Service/Endpoint 生成 KUBE-SERVICES 链规则,内核随机模块做端点选择当前默认;规则随 Service 数线性增长,万级 Service 时规则更新变慢
ipvs基于内核 IPVS 哈希表做负载均衡,查找 O(1),支持多种算法进入废弃路径:v1.40 默认禁用、v1.43 移除,转向 nftables
nftables新一代内核包分类框架(内核 5.13+),规则集原子替换、增量更新v1.31 Beta 默认启用 大规模集群性能更优,是未来默认

表 10-1 kube-proxy 三种代理模式

无论哪种模式,本质都是写入内核 netfilter 规则。conntrack(连接跟踪表)记录每条连接的 NAT 映射,保证同一连接的后续包始终落到同一后端,并正确还原回程。经典故障 conntrack table full, dropping packet 就是高并发短连接把跟踪表撑爆——需要调大 nf_conntrack_max。

代码清单 10-1 在节点上验证 Service 规则
# iptables-save | grep KUBE-SVC | head
# ipvsadm -Ln                    # ipvs 模式下查看虚拟服务与真实后端
# conntrack -L | grep 10.96.1.100
# cat /proc/sys/net/netfilter/nf_conntrack_count

10.3 DNS 与令人头疼的 ndots

CoreDNS 以 Deployment(通常 2 副本 + 反亲和)运行,kubelet 把每个 Pod 的 /etc/resolv.conf 指向 Service IP(集群 DNS)。Service 与 Pod 的域名规则:

  • Service:<svc>.<ns>.svc.cluster.local;同命名空间内可缩写为 <svc>;
  • Headless Service 的每个 Pod:<pod-name>.<svc>.<ns>.svc.cluster.local,A 记录直接返回 Pod IP(StatefulSet 集群发现的基石);
  • 普通 Service:A 记录返回 ClusterIP;SRV 记录可发现命名端口。

默认 resolv.conf 带 options ndots:5:当查询域名中的点少于 5 个时,解析器会先依次拼上 search 域后缀逐个尝试。访问外部域名 api.example.com(3 个点)会先产生 api.example.com.default.svc.cluster.local 等 4 次无效查询,第 5 次才查真实域名——这是外部调用延迟与 CoreDNS QPS 翻倍的经典原因。对策:外部域名写全 FQDN(末尾加点)、使用 NodeLocal DNSCache(每节点 DNS 缓存 DaemonSet)、对高频外部域做 stubDomain 配置。

10.4 EndpointSlice 与拓扑感知

EndpointSlice 控制器 watch Ready 的后端 Pod,为每个 Service 维护若干 Slice(每片最多 100 个端点)。kube-proxy watch 这些 Slice 更新转发规则;相比旧 Endpoints 单一大对象,每次 Pod 变化只更新一个小 Slice。Slice 中每个端点携带 zone、nodeName,配合 Service 注解 service.kubernetes.io/topology-mode: Auto 可启用拓扑感知路由:流量优先留在本可用区,降低跨区延迟与云厂商跨区流量费。

第 11 章

主流 CNI 方案、NetworkPolicy 与服务网格

11.1 三大 CNI 横向对比

维度FlannelCalicoCilium
数据路径VXLAN / host-gwBGP 直路 / IPIP / VXLAN / eBPF / VPPeBPF 原生,可覆盖 BGP/Overlay
网络策略不支持(需配合 Canal)完整支持 L3/L4 + L7(企业版)原生 L3/L4/L7 策略,可基于 FQDN/Kafka 等
可观测性基础中等Hubble:eBPF 级流日志、拓扑、丢包原因
特色能力极简、资源占用低大规模 BGP 路由成熟、生态广kube-proxy replacement、多集群、服务网格、透明加密
适用学习、小规模、边缘大规模传统生产、合规网络高性能/安全/可观测敏感的现代生产

表 11-1 Flannel / Calico / Cilium 选型对比

11.2 eBPF 为什么改变了游戏规则

eBPF 允许把经过验证的小程序安全地挂入内核事件点(网卡收发、socket、cgroup、tracepoint),在内核态完成包处理、可观测与策略判定。Cilium 用它替代了 iptables 链的线性遍历:Service 转发在内核中直接查 eBPF map 完成,规则更新是 map 原子写入而非全链刷新;还能在不注入 sidecar 的情况下解析 HTTP/gRPC/Kafka 协议做 L7 策略与指标。开启 kube-proxy replacement 后节点甚至不必运行 kube-proxy,少一跳、少一整套 conntrack/iptables 开销。代价是内核版本要求较高(生产建议 5.15+/6.x)与排障需要新工具链(bpftool、Hubble)。

11.3 NetworkPolicy:默认全通的集群需要显式上锁

K8s 默认网络是"全互联"的,任何 Pod 可访问任何 Pod。NetworkPolicy 是命名空间内的白名单对象(注意:CNI 不支持则完全无效)。它通过 podSelector 选定被保护 Pod,用 from/to 描述允许的来源/目标(三种来源可组合:podSelector、namespaceSelector、ipBlock),并区分 Ingress/Egress。一个关键语义:一旦某个 Pod 被任何 policy 选中,未被允许的流量默认拒绝。生产基线推荐为每个命名空间配"默认拒绝入站 + 显式放行"。

代码清单 11-1 只允许 monitoring 命名空间抓取 9100 指标端口
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-scrape
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: myapp
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring
      ports:
        - protocol: TCP
          port: 9100

11.4 服务网格:从 Sidecar 到 Ambient 的去边车化

Service Mesh 把熔断、重试、mTLS、灰度、可观测等能力从应用代码下沉到基础设施层。传统 Istio 通过 init 容器给每个 Pod 注入 Envoy sidecar 拦截进出流量(iptables redirect),代价是每个 Pod 一份内存/CPU 开销与启动顺序复杂度。2024–2025 年行业明确转向 sidecarless / ambient 架构:

  • Istio Ambient Mode:分层架构——节点级 ztunnel(DaemonSet,负责 L4 转发与 mTLS)承担基础网格;仅需要 L7 能力的命名空间再按需启用 waypoint proxy。多集群 ambient 支持在 1.27 进入 Alpha[6]。
  • Cilium Service Mesh:直接用 eBPF 在内核做重定向,可选择轻量 sidecar 或完全无 sidecar,与网络/策略/Hubble 共用一套数据平面。

选型建议:跨语言统一 mTLS、零代码重试熔断、细粒度灰度是 mesh 的核心价值;如果只需要可观测性,eBPF + OpenTelemetry 无侵入方案往往更轻。

11.5 Ingress 的继任者:Gateway API

Ingress 规范只覆盖最基础的 host/path 路由,高级能力全部沦为各家厂商不兼容的注解。Gateway API(SIG Network 主导,Cilium/Envoy/AKS/GKE 等共同实现)用角色化资源重构入口:GatewayClass(控制器类型)→ Gateway(一个具体 LB 实例与监听器,运维持有)→ HTTPRoute(路由规则,开发者持有)。v1.3(2025)已支持基于百分比的流量镜像、CORS、重试预算等。新项目应直接评估 Gateway API,Ingress 仍将长期维护但不再演进。

第 12 章

存储原理:PV、PVC 与 CSI

存储 CSI 示意图:Pod 通过 PVC 卷轴经 CSI 光门动态挂载出磁盘卷与云存储
本章导引图:PVC 就像一张"提货单",经 CSI 标准接口从后端存储池动态供给出真实磁盘卷

12.1 供给模型:PV/PVC 解耦与动态供给

K8s 用"持久卷(PV)"和"持久卷声明(PVC)"把存储管理员与存储消费者解耦:PV 是集群中的一块实际存储(由管理员静态创建或系统动态创建),PVC 是用户对"容量、访问模式、存储类型"的申请,二者按条件绑定后,Pod 把 PVC 当卷挂载。

生产中几乎都用 StorageClass 动态供给:PVC 指向某个 StorageClass → provisioner 自动调后端 API 建盘 → 自动生成 PV 并绑定。两个关键参数:

  • volumeBindingMode: WaitForFirstConsumer(云盘必选):延迟到 Pod 被调度后才在 Pod 所在可用区建盘,否则盘建在 A 区、Pod 被调度到 B 区会挂载失败;Immediate 则 PVC 一创建就供盘。
  • reclaimPolicy:Delete(PVC 删除则盘随之删除)或 Retain(保留后端盘,需人工清理,重要数据默认建议 Retain 防误删);Recycle 已废弃。

访问模式:RWO(单节点读写,云盘)、ROX(多节点只读)、RWX(多节点读写,需 NAS/NFS/CephFS 类文件存储)。

12.2 CSI:存储厂商的标准插件框架

CSI(Container Storage Interface)把存储能力移出 K8s 主干,与 CRI/CNI 同构。一个存储 Pod 里通常包含 Controller 端(Deployment,管控制面操作)和 Node 端(DaemonSet,每节点执行挂载):

flowchart LR
    PVC["用户创建 PVC"] --> EP["external-provisioner
sidecar 监听到 PVC"] EP -->|"CreateVolume gRPC"| DRV["CSI Driver
Controller 服务"] DRV --> DISK["后端: 云盘 / Ceph / NFS"] POD["Pod 调度完成"] --> ATT["external-attacher
ControllerPublishVolume"] ATT --> NODE["Node 端 CSI 插件
DaemonSet"] NODE -->|"NodeStageVolume + NodePublishVolume"| MNT["块设备挂载到节点
再挂入 Pod 目录"] SNAP["external-snapshotter"] -.->|"快照/恢复"| DRV RES["external-resizer"] -.->|"扩容 ControllerExpandVolume"| DRV style DRV fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style MNT fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 12-1 CSI 供给—挂载链路(sidecar 控制器 + Driver + Node 插件)

这张图回答了两个高频问题:为什么 CSI 驱动 Pod 里总是一堆容器?external-provisioner / attacher / resizer / snapshotter 是 K8s 官方维护的 sidecar,负责监听 K8s 对象并调用你 Driver 的 gRPC 接口;Driver 本身只写后端存储的差异逻辑。为什么 Pod 卡在 ContainerCreating 提示 volume mount 超时?问题域在 Node 端:从云盘 Attach 到节点(ControllerPublish)→ 设备出现 /dev/vdX → mkfs/格式化 → Stage(全局挂载)→ Publish(bind mount 到 Pod 目录),任一步失败都可在 kubelet 日志与 Node CSI Pod 日志中定位。

12.3 快照、临时卷与常见方案

  • VolumeSnapshot:通过 VolumeSnapshotClass 调 CSI CreateSnapshot,可从快照克隆出新 PVC(备份/复制环境的标准手段)。
  • emptyDir:Pod 生命周期内的临时空目录,默认在节点磁盘,medium: Memory 时用 tmpfs(占内存、重启清空),适合高速暂存但要算进内存 request。
  • configMap/secret/downwardAPI 卷:把集群数据投影成文件;CSI ephemeral volume 还支持 Pod 启动时临时建卷、删除即清。
方案类型适用
local-path-provisioner节点本地盘动态供给kind/k3s、开发测试、单节点
Rook-CephCeph(RBD 块 / CephFS 文件 / RGW 对象)自建数据中心、需要全套分布式存储
Longhorn分布式块存储、副本同步、UI 友好中小规模裸金属/边缘生产
云厂商 CSI(EBS PD/EBS/OSS CSI)托管云盘/文件服务云上集群首选,可靠性由云保障

表 12-1 常见 Kubernetes 存储方案

有状态存储的两条军规

① StatefulSet 缩容/删除 PVC 默认不回收(v1.27+ 可配策略自动删除),盘往往比 Pod 长寿——这是数据安全设计,不是 bug;② 数据库跑 K8s 时,备份责任仍在你自己:存储层快照 + Velero + 逻辑备份(如 mysqldump)应组合使用,并定期做恢复演练。

Part Four
第四篇 · 实战篇

从本地多节点集群开始,系统训练 kubectl、完整 YAML、发布策略、Helm、故障排查与灾备恢复,并用 client-go/Kubebuilder 迈出二次开发第一步。

第 13 章

搭建实战环境

纸上得来终觉浅。本书建议你立刻在本机准备一个可以随便折腾的多节点集群。三种主流本地方案:

工具本质适合
kindDocker 容器当"节点",kubeadm 引导,多节点最方便本书首选、CI 测试、版本切换
k3dDocker 里跑轻量发行版 k3s资源紧张、边缘场景、快速起停
minikubeVM/容器驱动,功能面板丰富入门学习、单机 GUI 体验

表 13-1 本地集群工具对比

13.1 用 kind 创建三节点集群

代码清单 13-1 kind-config.yaml(1 控制平面 + 2 worker)
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: ClusterConfiguration
        apiServer:
          extraArgs:
            - --feature-gates=MinDomainsInPodTopologySpread=true
  - role: worker
  - role: worker
# 将宿主机 80/443 映射到控制平面节点,便于本地访问 Ingress
# extraPortMappings:
#   - containerPort: 30080
#     hostPort: 8080
代码清单 13-2 创建、验证、删除
$ kind create cluster --name lab --config kind-config.yaml
$ kubectl get nodes -o wide
$ kubectl get pods -A
$ kind export logs /tmp/kind-logs --name lab   # 导出全组件日志,排障神器
$ kind delete cluster --name lab

13.2 让本地镜像进入集群

kind 集群内的节点看不到你宿主机 docker build 的镜像,两种方式:推到镜像仓库(含本地 registry),或直接加载镜像 tar:

代码清单 13-3 加载本地镜像(离线环境同样适用)
$ docker build -t myapp:dev .
$ kind load docker-image myapp:dev --name lab
# 多镜像可一次加载;Pod 中 imagePullPolicy 需设为 IfNotPresent 或 Never

13.3 安装必备插件

一个"能练全书"的集群还缺三样东西:CNI(kind 默认已装 kindnet,生产可换 Calico/Cilium)、Ingress Controller、metrics-server:

代码清单 13-4 安装 metrics-server 与 ingress-nginx
$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 本地自签证书环境需给 metrics-server 加 --kubelet-insecure-tls
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
$ kubectl wait --namespace ingress-nginx \
    --for=condition=ready pod \
    --selector=app.kubernetes.io/component=controller --timeout=120s
第 14 章

kubectl 必备技能

kubectl 是你与集群对话的手。本章按工作场景组织高频命令,建议全部亲手敲一遍并配置好别名与自动补全。

14.1 效率配置

代码清单 14-1 别名、补全与上下文
$ echo "alias k=kubectl" >> ~/.bashrc
$ echo "complete -o default -F __start_kubectl k" >> ~/.bashrc
$ kubectl config get-contexts
$ kubectl config use-context lab
$ kubectl config set-context --current --namespace=prod

14.2 查询:从 get 到 jsonpath

代码清单 14-2 查询与输出技巧
$ kubectl get pods -n kube-system -o wide
$ kubectl get pods -l app=myapp --sort-by='.status.containerStatuses[0].restartCount'
$ kubectl get deploy myapp -o yaml > deploy.yaml     # 导出当前线上对象
$ kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'
$ kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu
$ kubectl explain deployment.spec.strategy.rollingUpdate   # 字段字典

14.3 变更:apply、scale 与编辑

代码清单 14-3 声明式变更与干跑
$ kubectl apply -f manifests/ --recursive          # 目录批量声明
$ kubectl apply -f deploy.yaml --dry-run=client -o yaml # 客户端演练,不落库
$ kubectl scale deploy/myapp --replicas=5
$ kubectl autoscale deploy/myapp --min=2 --max=10 --cpu-percent=70
$ kubectl annotate pod mypod owner=team-a --overwrite
$ kubectl label pod mypod tier=frontend --overwrite

14.4 发布与回滚

代码清单 14-4 rollout 全家桶
$ kubectl set image deploy/myapp myapp=myapp:1.26.0
$ kubectl rollout status deploy/myapp               # 阻塞观察进度,适合流水线
$ kubectl rollout history deploy/myapp
$ kubectl rollout history deploy/myapp --revision=3  # 查看某版本详情
$ kubectl rollout undo deploy/myapp                  # 回滚上一版
$ kubectl rollout undo deploy/myapp --to-revision=2
$ kubectl rollout pause deploy/myapp                 # 暂停:连续改多个东西,最后 resume 一次性发布

14.5 调试与节点操作

代码清单 14-5 日志、执行、临时容器与节点维护
$ kubectl logs deploy/myapp --tail=200 -f
$ kubectl logs mypod -c sidecar --previous          # --previous 看上一个崩溃容器的日志
$ kubectl exec -it mypod -- sh
$ kubectl port-forward svc/myapp 8080:80              # 本地转发,调试集群内服务首选
$ kubectl debug -it mypod --image=busybox --target=myapp # 注入临时容器共享进程命名空间
$ kubectl debug node/node-1 -it --image=busybox       # 在节点上起调试 Pod(chroot /host)
$ kubectl top pod -A --sort-by=cpu
$ kubectl cordon node-1                               # 标记不可调度
$ kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
$ kubectl uncordon node-1
$ kubectl taint nodes node-1 dedicated=gpu:NoSchedule
$ kubectl get events --sort-by=.lastTimestamp -A | tail -30
两个容易被忽略的利器

kubectl debug 基于 Ephemeral Containers(GA)向运行中的 Pod 动态加入调试容器,distroless 镜像没 shell 时全靠它;kubectl get events --field-selector reason=FailedScheduling 可以只看调度失败事件。

第 15 章

工作负载 YAML 实战

本章给出可直接用于生产的完整 YAML 模板,字段全部采用当前稳定 API 版本。建议作为你的个人清单收藏。

15.1 生产级 Deployment

代码清单 15-1 deployment.yaml——探针、资源、安全上下文齐全
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels: { app: myapp }
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels: { app: myapp }
  template:
    metadata:
      labels: { app: myapp }
    spec:
      securityContext:            # Pod 级安全默认值
        runAsNonRoot: true
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: myapp
          image: registry.example.com/myapp:1.26.0
          imagePullPolicy: IfNotPresent
          ports:
            - { containerPort: 8080, name: http }
          env:
            - name: LOG_LEVEL
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: log.level
          resources:
            requests: { cpu: "100m", memory: "128Mi" }
            limits:   { cpu: "500m", memory: "256Mi" }
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
          startupProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 5
            failureThreshold: 30
          livenessProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet: { path: /ready, port: http }
            periodSeconds: 5
            failureThreshold: 2
      terminationGracePeriodSeconds: 30

15.2 ConfigMap、Secret 与挂载

代码清单 15-2 config-secret.yaml
apiVersion: v1
kind: ConfigMap
metadata: { name: app-config }
data:
  app.properties: |
    server.port=8080
    log.level=info
---
apiVersion: v1
kind: Secret
metadata: { name: app-secret }
type: Opaque
stringData:                 # stringData 直接写明文,apiserver 自动编码
  db.password: "S3cr3t!2026"
---
apiVersion: v1
kind: Pod
metadata: { name: config-demo }
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "cat /etc/config/app.properties; sleep 3600"]
      envFrom:
        - secretRef: { name: app-secret }
      volumeMounts:
        - { name: cfg, mountPath: /etc/config }
  volumes:
    - name: cfg
      configMap: { name: app-config }

15.3 StatefulSet + Headless Service + PVC 模板

代码清单 15-3 statefulset.yaml(有序身份 + 独立存储)
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
  clusterIP: None
  selector: { app: nginx }
  ports:
    - { port: 80, name: web }
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: web }
spec:
  serviceName: web
  replicas: 3
  podManagementPolicy: OrderedReady
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels: { app: nginx }
  template:
    metadata:
      labels: { app: nginx }
    spec:
      containers:
        - name: nginx
          image: registry.k8s.io/nginx-slim:0.27
          ports:
            - { containerPort: 80, name: web }
          volumeMounts:
            - { name: www, mountPath: /usr/share/nginx/html }
  volumeClaimTemplates:
    - metadata: { name: www }
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: standard
        resources:
          requests: { storage: 1Gi }

部署后你会得到 web-0/1/2 三个 Pod、各自独立的 www-web-0/1/2 PVC,以及 web-0.web.default.svc.cluster.local 形式的稳定 DNS 名。

15.4 DaemonSet 与 CronJob

代码清单 15-4 日志采集 DaemonSet 与定时清理 CronJob
apiVersion: apps/v1
kind: DaemonSet
metadata: { name: log-agent }
spec:
  selector:
    matchLabels: { app: log-agent }
  template:
    metadata:
      labels: { app: log-agent }
    spec:
      tolerations:                # 关键:容忍污点,确保每个节点(含 master)都跑
        - operator: Exists
      containers:
        - name: agent
          image: fluent-bit:3.0
          resources:
            requests: { cpu: "50m", memory: "64Mi" }
            limits:   { cpu: "100m", memory: "128Mi" }
          volumeMounts:
            - { name: varlog, mountPath: /var/log }
      volumes:
        - name: varlog
          hostPath: { path: /var/log }
---
apiVersion: batch/v1
kind: CronJob
metadata: { name: nightly-cleanup }
spec:
  schedule: "0 3 * * *"
  timeZone: "Asia/Shanghai"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: cleanup
              image: bitnami/kubectl:1.34
              command: ["kubectl", "delete", "jobs", "--field-selector=status.phase=Failed"]

15.5 HPA 与 PDB:弹性与可用性护栏

代码清单 15-5 hpa-pdb.yaml(autoscaling/v2 + policy/v1)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: myapp-hpa }
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 10
  behavior:
    scaleUp:   { stabilizationWindowSeconds: 60 }
    scaleDown: { stabilizationWindowSeconds: 300 }
  metrics:
    - type: Resource
      resource:
        name: cpu
        target: { type: Utilization, averageUtilization: 70 }
    - type: Resource
      resource:
        name: memory
        target: { type: Utilization, averageUtilization: 80 }
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: { name: myapp-pdb }
spec:
  minAvailable: 2
  selector:
    matchLabels: { app: myapp }

PDB(PodDisruptionBudget)只约束"自愿中断":节点 drain、集群升级、HPA 缩容时,系统必须保证至少 2 个(或至多不可用 1 个)myapp Pod 在线。它不防节点宕机等非自愿故障,但能防止运维操作一把把服务全部排空。

第 16 章

应用发布策略与 Helm 实战

16.1 四种发布策略对比

策略做法成本/风险
滚动更新Deployment 原生,新 RS 渐增、旧 RS 渐减零额外成本;但新旧版本同时服务,缺细粒度流量控制
蓝绿发布部署完整两套(blue/green),切换 Service selector 一键切流瞬时双倍资源;回滚最快(切回 selector)
金丝雀先让一小部分流量(按权重/Header)进新版本,观察指标后逐步放量需要 Ingress/Service Mesh/Argo Rollouts 支持
A/B / 特性开关按用户特征(Cookie、地域)路由通常在网关或应用层实现

表 16-1 发布策略对比

16.2 蓝绿发布:Service selector 切换

蓝绿不依赖任何高级组件,核心是两个 Deployment 共享同一 Service,通过修改 selector 决定流量去向。关键技巧:把版本放 Pod 标签里(version: blue),Service 的 selector 只切 version:

代码清单 16-1 蓝绿切换命令
$ kubectl apply -f myapp-blue.yaml -f myapp-green.yaml
$ kubectl get pods -l app=myapp --show-labels
# 切到 green:修改 service 选择器(会产生一次 Service 对象变更)
$ kubectl patch svc myapp -p '{"spec":{"selector":{"app":"myapp","version":"green"}}}'
# 观察无误后下线 blue;异常则一条命令切回
$ kubectl scale deploy/myapp-blue --replicas=0

16.3 金丝雀发布:Ingress 权重

使用 ingress-nginx 时,部署一个额外的 canary Ingress,通过注解按权重或请求头引流。稳定版与金丝雀版是两个 Deployment、两个 Service,host/path 保持一致:

代码清单 16-2 canary-ingress.yaml(10% 流量到新版本)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
    # 也可按 Header 精确灰度:
    # nginx.ingress.kubernetes.io/canary-by-header: X-Canary
spec:
  ingressClassName: nginx
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-green
                port: { number: 80 }

更专业的渐进式交付应使用 Argo Rollouts(AnalysisTemplate 可根据 Prometheus 错误率/延迟自动继续或回滚)或服务网格(Istio VirtualService 按权重切分),它们与 GitOps 天然集成,是企业发布的最终形态。

16.4 Helm 3/4:Chart 与模板化交付

当 YAML 多到几十个文件、每套环境参数不同,裸 YAML 就不够用了。Helm 是 K8s 的包管理器:Chart 是参数化模板包,Release 是一次安装实例。Helm 3 移除了 Tiller、Release 成为命名空间级资源、Secret 存储版本;Helm 4 已于 2025 年底发布,Helm 3 进入生命周期末期(安全补丁持续到 2027 年 2 月),核心模板语法两者通用[7]。

代码清单 16-3 Helm 日常命令流
$ helm create mychart
$ helm lint ./mychart
$ helm template ./mychart -f values-prod.yaml   # 本地渲染,不碰集群,CI 里先 template 再 apply 很常见
$ helm install myapp ./mychart -n prod --create-namespace
$ helm upgrade myapp ./mychart -f values-prod.yaml --set image.tag=1.26.0
$ helm history myapp -n prod
$ helm rollback myapp 1 -n prod
$ helm list -A
$ helm uninstall myapp -n prod

一个 Chart 的标准结构:Chart.yaml(元信息与依赖)、values.yaml(默认参数)、templates/(Deployment/Service/Ingress 模板 + _helpers.tpl)、charts/(子 chart 依赖)。模板基于 Go template + Sprig 函数库:

代码清单 16-4 templates/deployment.yaml 节选
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    app: {{ .Chart.Name }}
    version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
{{- if .Values.ingress.enabled }}
    {{/* 条件渲染:不需要 Ingress 时整块省略 */}}
{{- end }}
Helm 与 Kustomize 如何选

需要参数化分发、版本管理、应用市场选 Helm(生态中几乎所有中间件都提供 Chart);只是想给同一套 YAML 打环境补丁(dev/staging/prod 的镜像、副本数差异)选 Kustomize(kubectl 原生 -k 支持,无模板语言)。二者并不冲突:Helm 支持 post-rendering 调用 Kustomize 对渲染结果再加工。

第 17 章

故障排查、etcd 备份与灾备恢复

排障能力是运维与开发的分水岭。本章给出 Kubernetes 最常见故障的标准化排查路径,全部基于你已学的原理,而非背命令。

17.1 通用排障心法:自顶向下五层

  1. 对象层:get -o wide 看状态、describe 看 Events;
  2. 工作负载层:日志(含 --previous)、退出码、探针;
  3. 调度层:Pending → 资源/污点/亲和/PVC;
  4. 网络层:DNS → Endpoints → kube-proxy → CNI → NetworkPolicy;
  5. 节点层:kubelet/containerd/磁盘/内核(kubectl describe node、dmesg)。

17.2 Pod 常见状态速查表

现象根因域关键命令与修复
Pending调度不出去describe 看 Events:资源不足 / 污点不容忍 / PVC 未绑定 / 亲和无匹配;扩容节点或放宽约束
ImagePullBackOff镜像核对镜像名与 tag、imagePullSecret、仓库网络;crictl pull 在节点上复现
CrashLoopBackOff应用反复退出logs --previous 看退出原因:配置缺失、权限、数据库不通、启动命令错误;BackOff 是递增等待不是新故障
OOMKilled (137)内存超 limitdescribe 确认 reason;调大 memory limit 或修内存泄漏;区分节点 OOM 与 cgroup OOM
137 无 OOMKilled被节点/SIGKILL节点压力驱逐、liveness 失败、基础设施重启;查节点 events 与 kubelet 日志
CreateContainerConfigError配置引用ConfigMap/Secret 不存在或 key 拼错;describe 有明确提示
ContainerCreating 长时网络/存储CNI 插件失败、PVC 挂载超时、镜像卷拉取;describe events 给出具体插件错误
Terminating 卡住终结流程preStop 过长、finalizers 未移除、PID 1 不响应 SIGTERM(检查是否用了 shell 形式启动)

表 17-1 Pod 故障速查

17.3 Service 不通的标准排查链

"Service 访问不通"是最高频工单。严格按以下顺序,每一步都对应一个原理环节:

flowchart TD
    A["1. nslookup <svc>
DNS 解析正常吗"] -->|失败| B["查 CoreDNS Pod / Pod dnsConfig / ndots"] A -->|正常| C["2. kubectl get endpoints <svc>
有后端 IP 吗"] C -->|空| D["selector 与 Pod labels 不一致
或 Pod 未 Ready(readiness 失败)"] C -->|有| E["3. 在同节点 Pod 内直接 curl PodIP:port
绕过 Service"] E -->|失败| F["应用监听/端口 targetPort 错误"] E -->|通| G["4. 查 kube-proxy 与规则
kube-proxy Pod 状态 / iptables-save"] G --> H["5. 跨节点不通? CNI 组件 / MTU / 路由"] H --> I["6. NetworkPolicy / 安全组拦截"] style A fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style D fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style F fill:#FEF3E2,stroke:#D97706,color:#0F1B2D
图 17-1 Service 不通排查决策树
代码清单 17-1 一键检查命令
$ kubectl get svc,ep myapp
$ kubectl run dns-test --image=busybox:1.36 -it --rm -- nslookup kubernetes.default
$ kubectl run nettest --image=nicolaka/netshoot --rm -it -- bash
# 进入后:curl -v <pod-ip>:8080 / tcpdump / conntrack -L / iptables-save

17.4 节点 NotReady 排查

节点 NotReady 意味着 kubelet 心跳/Lease 续约中断或 kubelet 自检失败。标准动作:kubectl describe node 看 Conditions(MemoryPressure/DiskPressure/PIDPressure/KubeletReady)→ 登录节点 systemctl status kubelet containerd → journalctl -u kubelet -f → 查磁盘(df -h,镜像层与日志常把盘写满)→ PLEG 健康度 → 证书是否过期(openssl x509 -noout -dates 检查 kubelet.conf 证书)。

17.5 etcd 备份与恢复(必练)

etcd 快照是集群级灾难恢复的最后防线。kubeadm 集群中 etcd 是静态 Pod,可直接用集群内证书操作:

代码清单 17-2 etcd 快照备份
# ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F).db \
    --endpoints=https://127.0.0.1:2379 \
    --cacert=/etc/kubernetes/pki/etcd/ca.crt \
    --cert=/etc/kubernetes/pki/etcd/server.crt \
    --key=/etc/kubernetes/pki/etcd/server.key
# etcdctl snapshot status /backup/etcd-xxxx.db -w table   # 校验快照完整性

恢复流程(务必先在演练环境验证):停止所有 apiserver(避免恢复期间写入)→ 每个 etcd 成员都用同一份快照执行 snapshot restore --data-dir=/var/lib/etcd-restored --initial-cluster ...(restore 会生成新的 member 标识,因此必须用新的 data-dir 并更新 etcd Pod 的 --initial-cluster-token/data-dir 挂载)→ 重启 etcd → 重启 apiserver → 校验工作负载。

没有"备份过",只有"恢复演练过"

未做过恢复演练的备份等于没有备份。建议每季度在隔离环境用真实快照恢复一次集群并验证关键 Deployment 与 PVC 数据,同时记录 RTO/RPO。应用层资源备份用 Velero(支持集群/命名空间级别资源导出与 PV 快照、跨集群恢复),etcdctl 负责底层兜底,二者互补。

第 18 章

二次开发入门:client-go 与 Kubebuilder

当你需要"让 Kubernetes 管理一种新东西"(数据库集群、证书、自定义工作负载),就要从使用者变成开发者。本章打通 CRD + Controller(即 Operator 模式)的任督二脉。

18.1 Operator = CRD + 控制器循环

CRD(CustomResourceDefinition)让你向 K8s 注册一种新对象,例如 Website.platform.example.com;自定义控制器watch 这种对象并在 reconcile 中驱动现实(创建 Deployment/Service、配置 nginx)。用户体验与原生对象完全一致:kubectl get websites、GitOps、RBAC 全部自动适用。Operator 模式特别适合封装"有状态中间件的运维知识"——备份、扩缩、故障转移、版本升级,代表项目有 Prometheus Operator、Strimzi(Kafka)、Cert-Manager。

18.2 client-go 控制器骨架

代码清单 18-1 informer + workqueue 控制器核心结构(节选)
factory := informers.NewSharedInformerFactory(clientset, 30*time.Minute)
deployInformer := factory.Apps().V1().Deployments()

informer := deployInformer.Informer()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc:    func(obj interface{}) { enqueue(obj) },
    UpdateFunc: func(_, obj interface{}) { enqueue(obj) },
    DeleteFunc: func(obj interface{}) { enqueue(obj) },
})
lister := deployInformer.Lister()      // 从本地缓存读,不打 apiserver

stopCh := signals.SetupSignalHandler()
factory.Start(stopCh)
factory.WaitForCacheSync(stopCh)       // 缓存同步完成前不要启动 worker

// worker:永远从 workqueue 取 key,reconcile 失败就限速重入队
func (c *Controller) processNext() bool {
    key, shutdown := c.queue.Get()
    defer c.queue.Done(key)
    if err := c.reconcile(key.(string)); err != nil {
        c.queue.AddRateLimited(key)    // 指数退避重试
    } else {
        c.queue.Forget(key)
    }
    return true
}

三条铁律:① reconcile 入参只有 namespace/name,重新从缓存取对象(队列里的对象可能已过期);② 所有操作幂等,因为同一个 key 会被处理多次;③ 失败用 RateLimited 重试并设最大重试次数,防止坏对象永久热循环。

18.3 Kubebuilder:现代 Operator 工程脚手架

手写脚手架繁琐,Kubebuilder(Operator SDK 的上游)把 CRD 定义、控制器、RBAC、Webhook、镜像构建全部工程化:

代码清单 18-2 Kubebuilder 全流程
$ mkdir my-operator && cd my-operator
$ kubebuilder init --domain example.com --repo example.com/myop
$ kubebuilder create api --group platform --version v1alpha1 --kind Website
# 编辑 api/v1alpha1/website_types.go 定义 Spec/Status 字段
$ make manifests    # 从 Go struct + 注解生成 CRD/RBAC YAML
$ make generate     # 生成 DeepCopy 代码
$ make install      # CRD apply 到当前集群
$ make run          # 本地运行 controller(连接远端集群,便于调试)
$ make docker-build docker-push IMG=registry.example.com/myop:v0.1
$ make deploy IMG=registry.example.com/myop:v0.1

Reconcile 函数长这样(controller-runtime 框架):

代码清单 18-3 website_controller.go 核心逻辑
func (r *WebsiteReconciler) Reconcile(ctx context.Context,
    req ctrl.Request) (ctrl.Result, error) {

    var site platformv1.Website
    if err := r.Get(ctx, req.NamespacedName, &site); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err) // 删除事件:对象已不存在
    }

    // 1. 确保 Deployment 存在且镜像/副本数与 Spec 一致(不存在就创建,不一致就更新)
    // 2. 确保 Service/Ingress 存在
    // 3. 把实际结果写回 site.Status
    // 4. 需要定时复查时返回 ctrl.Result{RequeueAfter: 5*time.Minute}
    return ctrl.Result{}, nil
}
进阶方向

生产级 Operator 还需要:status conditions(Kubernetes 标准条件类型)、finalizers(删除前清理外部资源,如云负载均衡器)、ownerReferences(级联垃圾回收)、webhook 做默认值与校验、leader election 多副本部署、基于 metrics 的 kubebuilder 告警。学完这些,你就具备了平台工程师的核心能力。

Part Five
第五篇 · 企业级云原生运维

把单个集群的能力扩展到企业规模:GitOps 交付、可观测性、弹性伸缩、成本治理、安全合规、多集群与平台工程。

第 19 章

集群生命周期与 GitOps 交付

GitOps 闭环示意图:开发者提交、CI 构建、Git 事实源与集群控制器观测同步形成环形流程
本章导引图:GitOps 闭环——Git 是唯一事实源,集群内控制器持续观测期望与现状,发现漂移(橙色虚线)自动纠正

19.1 发行版选型:从 kubeadm 到托管 K8s

方案特点适用
kubeadm官方引导工具,各组件原生、可定制性强,运维责任全在自己自建数据中心、需要深度掌控、CKA 考试
k3s / RKE2单二进制、资源占用低(k3s);RKE2 面向政府/安全合规,默认加固边缘、IoT、分支机构、中小团队
EKS / GKE / AKS托管控制平面,节点自管或全托管(Fargate/Autopilot),与云 IAM/网络深度集成云上生产主流,降低控制平面运维
OpenShiftRed Hat 企业发行版,内置 Registry、Service Mesh、Pipeline、统一安全基线与 SSO大型企业、需要商业支持
Cluster API声明式管理集群本身的生命周期(Machine/MachineDeployment CRD),基础设施无关大规模自建多集群、集群即代码

表 19-1 Kubernetes 发行版对比

19.2 集群升级:唯一安全的路径是持续小步升级

K8s 版本偏差策略严格:kube-apiserver 与 kubelet 之间最多差 2 个 minor,且只能逐 minor 升级(1.32 不能直接跳到 1.34)。标准流程(kubeadm 与托管集群一致):

  1. 升级前:阅读目标版本 release notes 的 deprecated/removed API(用 kubectl deprecations 或 pluto 工具扫描清单);备份 etcd;确认所有 PDB 合理、应用可容忍滚动;先在测试集群验证。
  2. 升级控制平面:逐台进行(kubeadm upgrade plan → apply → 升级 kubelet/kubectl 包 → uncordon),一次只动一个控制平面节点。
  3. 升级工作节点:逐台 cordon → drain(PDB 保护容量)→ 升级 → uncordon;托管集群可用蓝绿节点池实现零风险切换(新建新版本节点池,排空旧池再删除)。

托管集群(GKE release channel、AKS auto-upgrade)把这套流程产品化,但废弃 API 检查与应用验证仍是你的责任。

19.3 GitOps:Git 作为唯一事实来源

传统 CI/CD 在流水线末端持有 kubeconfig 直接 kubectl apply——凭证散落、集群真实状态不可审计、漂移无人知晓。GitOps 翻转了方向:

flowchart LR
    DEV["开发者提交
代码 + Helm/Kustomize"] --> CI["CI: 构建镜像
测试 / 扫描 / 推送仓库"] CI -->|"更新镜像 tag 提交"| GIT["Git 仓库
唯一事实来源"] GIT -->|"pull / watch"| ARGO["Argo CD / Flux
集群内控制器"] ARGO -->|"对比 Git 期望 vs 集群现状"| K8S["Kubernetes API"] ARGO -->|"检测到漂移自动/手动同步"| K8S ARGO -->|"健康/同步状态"| UI["UI / 通知 / 告警"] style GIT fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style ARGO fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D
图 19-1 GitOps 闭环:注意是集群内控制器主动拉取(Pull),不是 CI 推送
维度Argo CDFlux
形态All-in-one:控制器 + 出色的 Web UI + Application CRD模块化控制器家族(source/kustomize/helm/notification 等)
多集群ApplicationSet 按集群/标签矩阵批量生成 Application多租户、多集群 provisioning 原生设计
渐进交付Argo Rollouts(金丝雀/蓝绿/分析自动化)搭配 Flagger
回滚GitOps 的标准回滚是 git revert + 同步;禁止在集群里手改(手改即漂移,会被自动纠正)

表 19-2 Argo CD 与 Flux 对比(两者均为 CNCF 毕业项目)

代码清单 19-1 Argo CD Application 示例
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-prod
  namespace: argocd
spec:
  destination: { server: https://kubernetes.default.svc, namespace: prod }
  source:
    repoURL: https://git.example.com/platform/manifests.git
    path: apps/myapp/overlays/prod
    targetRevision: main
  syncPolicy:
    automated: { prune: true, selfHeal: true }  # selfHeal: 手工漂移自动纠正
    syncOptions: [CreateNamespace=true, ApplyOutOfSyncOnly=true]
第 20 章

可观测性体系:指标、日志、链路

可观测性三支柱示意图:指标图表、日志面板、火焰状追踪链路围绕中央雷达
本章导引图:可观测性三支柱——指标(Metrics)、日志(Logs)、分布式追踪(Traces),由统一采集管道汇聚分析

"监控(Monitoring)告诉你哪里坏了,可观测性(Observability)帮你回答为什么坏。"云原生可观测性建立在三支柱之上,2025 年后又明确走向 OpenTelemetry 统一标准与 eBPF 无侵入采集。

20.1 指标:Prometheus + Alertmanager + Grafana

Prometheus 采用拉模式(pull):服务通过 /metrics 端点暴露文本格式指标,Prometheus 按 scrape interval 主动抓取并存储为时序数据。K8s 场景靠 Prometheus Operator 管理:ServiceMonitor/PodMonitor CRD 声明"抓谁、怎么抓",Operator 自动生成抓取配置;kube-state-metrics 提供对象状态指标(Deployment 副本、Pod 相位),node-exporter(DaemonSet)提供节点 CPU/内存/磁盘指标。

PromQL 必备模式

代码清单 20-1 高频 PromQL
# 容器 CPU 实际使用率(rate 必须配 counter)
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod)

# 内存工作集(OOM 判断依据,不是 usage 总量)
container_memory_working_set_bytes{pod="myapp-xxx"}

# 近 5 分钟 HTTP P99 延迟(直方图正确姿势)
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route))

# Pod 重启次数激增
increase(kube_pod_container_status_restarts_total[1h]) > 0

# 节点 NotReady 数量(关键告警)
sum(kube_node_status_condition{condition="Ready",status!="true"})

告警设计建议遵循 SRE 黄金信号(流量、错误率、延迟、饱和度)与告警分级:告警应面向症状(P99 延迟、错误率)而非原因(CPU 高),每条告警要有可执行的 runbook,避免告警疲劳。长期存储与全局视图用 Thanos 或 Mimir(对象存储 + 全局查询层),打通多集群指标。

20.2 日志:结构化、节点采集与集中检索

  • 应用侧:只输出 JSON 结构化日志到 stdout/stderr,不要自己写文件、不要管轮转(kubelet 负责);带上 traceId 串联请求。
  • 采集侧:DaemonSet 部署 Fluent Bit / Vector / Promtail,从节点 /var/log/containers 采集、加元数据(namespace、pod、labels)、过滤后转发。
  • 后端选型:Loki(只索引标签、日志正文对象存储,成本低,与 Grafana 一体)适合大多数 K8s 场景;Elasticsearch + Kibana(EFK)适合需要全文检索与复杂分析;两者均可用 OpenTelemetry Collector 作为统一管道。

20.3 链路追踪与 OpenTelemetry

微服务一次请求穿越十几个服务,没有分布式追踪就无法定位延迟瓶颈。OpenTelemetry(OTel)已成为厂商中立的遥测统一标准,把 Trace/Metrics/Logs 的 API、SDK 与数据协议(OTLP)统一:

flowchart TD
    APP["应用: OTel SDK 注入
Trace/Metric/Log"] -->|"OTLP"| COL["OTel Collector
Agent(DaemonSet) + Gateway(Deployment)"] COL -->|"尾部采样/属性加工/脱敏"| P["Prometheus / Mimir"] COL --> J["Jaeger / Tempo"] COL --> L["Loki / ES"] EBPF["eBPF 无侵入采集
(OTel OBI / Beyla)"] -.->|"RED 指标+Trace"| COL style COL fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style EBPF fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 20-1 OpenTelemetry 统一采集管道(Collector 一次接入,多后端分发)

Collector 部署为 Agent(每节点)+ Gateway(集群级)两层:在 Gateway 做 tail-based sampling(根据整条 trace 是否出错决定采样率,而非头部盲采)、属性增强、PII 脱敏,再分发到任意后端——后端可替换而应用零改动。2025 年 Grafana Labs 将 Beyla 捐赠给 OTel 形成的 eBPF Instrumentation(OBI),可零代码从内核自动生成 RED 指标与 Trace,是老系统快速接入可观测性的捷径[8]。

落地顺序建议

① 先装 kube-state-metrics + node-exporter + Grafana 官方集群看板,覆盖基础设施;② 定义 5–10 条 SLO(如"99% 的请求 30 天内 P99 < 300ms")并配多窗口多燃尽率告警;③ 再推应用 RED 指标与 traceId;④ 最后用 OTel 统一管道替换碎片化 agent。

第 21 章

弹性伸缩体系与 FinOps 成本治理

弹性伸缩示意图:负载曲线越过阈值,Pod 立方体扩容、节点服务器自动生长
本章导引图:负载曲线越过阈值,Pod 副本与节点两层弹性依次自动扩容,高峰过后平滑回缩

21.1 三层自动伸缩

Kubernetes 的弹性分三个层次,需要协同设计:

flowchart TB
    EVT["事件源: Kafka / RabbitMQ / Cron / Redis / 云指标"] --> KEDA["KEDA
事件驱动伸缩器"] MET["资源/自定义指标"] --> HPA["HPA
水平扩 Pod 副本"] KEDA --> HPA HPA --> POD["Pod 副本数变化"] VPA["VPA
垂直调 requests/limits"] -.->|"建议值/准入调整"| POD POD --> NODE["节点资源需求变化"] NODE --> KARP["Karpenter / Cluster Autoscaler
节点自动供给与缩容"] style HPA fill:#EDF3FE,stroke:#2456C4,color:#0F1B2D style KARP fill:#E7F6EC,stroke:#16A34A,color:#0F1B2D
图 21-1 Pod(HPA/VPA/KEDA)与节点(Karpenter/CA)两级弹性联动
  • HPA:根据 CPU/内存或自定义指标(QPS、队列长度)调 Deployment 副本数。v2 API 支持多指标与 behavior 调参(扩容快、缩容慢,防抖)。前提是 metrics-server(资源指标)或 Prometheus Adapter(自定义指标)。
  • VPA:观察历史用量,给出(或自动应用)更合理的 requests/limits。它解决的是资源画像问题;与 HPA 同用 CPU 指标会冲突,建议 VPA 仅做推荐(off 模式)或与基于自定义指标的 HPA 组合。
  • KEDA(CNCF 毕业):把 60+ 种外部事件源(Kafka lag、Cron、Prometheus 查询、云队列)映射为 HPA 指标,还能把 Deployment 缩到零——事件驱动与 Serverless 化任务的标准方案。
  • 节点层:Cluster Autoscaler 监听因资源不足 Pending 的 Pod 去调云 ASG;Karpenter(2025 年毕业)更进一步——直接按待调度 Pod 的资源/架构/污点要求实时供给最合适的实例,缩容更激进、节点利用率更高,是云上弹性的新一代选择[9]。

21.2 requests/limits:成本与稳定性的第一杠杆

调度器按 requests 装箱,节点容量本质上是 requests 之和。requests 设高 → 节点利用率低、成本高;设低 → 超卖严重、高峰期节点压力驱逐。治理手段:

  • LimitRange:给命名空间设置默认 request/limit 与上下限,防止业务忘配或漫天要价;
  • ResourceQuota:限制命名空间总 requests/limits、对象数量,做多租户硬隔离;
  • VPA 推荐 + 周期性 rightsizing:用真实 P95 用量(CPU 看 P95、内存看峰值 WSS)校准 requests,通常能回收 30%–50% 的资源浪费。

21.3 FinOps:让云成本可见、可控

杠杆做法典型收益
成本可视化OpenCost(CNCF 项目,Kubecost 内核开源)按命名空间/标签/团队分摊节点、盘、LB 成本建立责任归属
rightsizingVPA 建议 + 超卖比治理 + 清理无主资源10%–40%
Spot/抢占实例Karpenter 按 Pod 中断容忍度混合供给 Spot(最高可省 90% 单价)[9]无状态批处理可达 60%+
弹性调度HPA/KEDA + 节点自动缩容,夜间/周末缩零非生产环境30%–70%(非生产)
架构优化合并碎片化集群、用 ARM/Graviton、GP3/标准盘替换高端盘10%–20%

表 21-1 Kubernetes FinOps 五大杠杆

Spot 不是免费午餐

Spot 实例可能被 2 分钟通知后回收。无状态、可中断、副本跨实例类型分布的工作负载(配合多 AZ、PDB、Karpenter node disruption handling)才适合;有状态主库、单点服务不要放。

第 22 章

安全、合规与多租户

云原生安全示意图:盾牌笼罩集群,Pod 间以金色光锁加密连接,攻击箭头被弹开
本章导引图:零信任纵深防御——策略准入、身份与 RBAC、mTLS 加密、镜像验签共同构成集群盾牌

安全是贯穿整个栈的体系工程:从容器镜像、到 API 授权、到工作负载运行时、再到供应链。本章按攻击面组织,并给出每条防线的可落地措施。

22.1 RBAC:最小权限的落地

K8s 所有 API 调用都要经过"认证 → 鉴权 → 准入"三道门。RBAC 的四个核心对象:Role(命名空间内权限)/ClusterRole(集群范围或可复用权限集)、RoleBinding/ClusterRoleBinding。铁律:

  • 能 Role 绝不 ClusterRole,能命名空间隔离绝不跨空间;给业务服务账号只绑定其需要的 verbs × resources,定期用 kubectl auth can-i --list --as=... 审计实际权限;
  • 工作负载用专属 ServiceAccount,禁用 default SA 自动挂载(automountServiceAccountToken: false),99% 的应用根本不需要访问 K8s API;
  • 云上用 IRSA / Workload Identity(OIDC 联邦)让 Pod 直接获得云 IAM 角色,彻底消灭长期 AK/SK。
代码清单 22-1 最小权限 Role 示例
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { namespace: prod, name: config-reader }
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["app-config"]      # 精确到单个对象
    verbs: ["get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { namespace: prod, name: myapp-reads-config }
subjects:
  - kind: ServiceAccount
    name: myapp
    namespace: prod
roleRef: { kind: Role, name: config-reader, apiGroup: rbac.authorization.k8s.io }

22.2 策略即代码:准入控制

RBAC 回答"能不能调这个 API",准入策略回答"提交的对象合不合规"。OPA Gatekeeper 与 Kyverno 是两大主流(均 CNCF 毕业):Kyverno 用 YAML 写策略、自带镜像校验与例外机制,上手快;Gatekeeper 用 Rego,表达力强、适合复杂组织规则。典型强制策略:

  • 禁止 privileged、hostNetwork/hostPath、latest 标签进入生产;
  • 必须声明 resources.requests/limits、必须带团队标签;
  • 镜像必须来自企业可信仓库;Ingress host 必须在白名单。

22.3 Pod Security 与运行时加固

Kubernetes 1.25 起 Pod Security Standards(PSS)取代了 PodSecurityPolicy,通过命名空间标签一键施加三级基线:

代码清单 22-2 命名空间强制 restricted 基线
apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted    # 违规直接拒绝
    pod-security.kubernetes.io/audit: restricted      # 同时记审计事件
    pod-security.kubernetes.io/warn: restricted

restricted 要求:runAsNonRoot、seccompProfile=RuntimeDefault、drop ALL capabilities、不允许特权与提权。更底层可叠加:gVisor / Kata Containers(用户态内核/轻量 VM 做强隔离,多租户不可信代码场景)、节点上的 Falco(CNCF 毕业,基于内核事件/eBPF 的运行时威胁检测,例如 Pod 里突然起 shell、读 /etc/shadow 立刻告警)。

22.4 Secret 与供应链安全

  • Secret 加密:开启 apiserver encryptionConfiguration(etcd 落盘加密),生产用 KMS 插件对接云 KMS/Vault,而不是默认的仅 base64;敏感配置用 External Secrets Operator 从 Vault/云密钥库动态同步,不进 Git。
  • 镜像供应链:构建阶段做 SBOM(Syft)与漏洞扫描(Trivy/Grype);用 Cosign 对镜像签名,部署阶段用 Kyverno/Sigstore policy-controller 验签准入,杜绝未签名/被篡改镜像;基础镜像选 distroless 减少攻击面。
  • 网络零信任:默认拒绝的 NetworkPolicy 只放通显式流量;跨命名空间流量同样收敛。
  • 审计:开启 audit policy(至少记录 metadata 级、写操作与鉴权失败 RequestResponse 级),日志接入 SIEM。
第 23 章

服务网格、多集群与平台工程

23.1 服务网格:把流量治理从应用中剥离

当服务数量过百,重试、超时、熔断、mTLS、灰度路由在每种语言的代码里重复实现就成为灾难。服务网格通过Sidecar(或 Sidecarless)数据面把这些能力下沉:

能力没有网格有网格(Istio Ambient / Sidecar)
加密每个服务自己管证书与 TLS全自动 mTLS,证书由控制面轮转
流量治理代码内实现重试/超时/熔断VirtualService/DestinationRule 声明式配置
灰度发布依赖网关能力任意服务间按权重/Header 金丝雀
可观测性每服务埋点自动生成服务拓扑、黄金指标、分布式 trace
授权应用内鉴权L7 AuthorizationPolicy:服务 A 只能调服务 B 的 /api/x

表 23-1 服务网格带来的统一能力

2025 年网格格局明显分化:Istio 推进 Ambient 模式(ztunnel + waypoint 分层,无需每 Pod 注入 Sidecar,降低资源与运维成本,已 GA 并成为推荐模式之一)[10];Cilium Service Mesh 直接用 eBPF 在内核做转发,彻底无 Sidecar;Linkerd 继续主打"最轻 Sidecar + 简单"。选型原则:没有明确痛点不要上网格——多语言、多团队、强合规零信任、需要精细灰度的组织收益最大;小团队用网关 + 约定往往足够。

23.2 多集群与多云

多集群的真实驱动力通常是:高可用/容灾(地域故障)、合规与数据主权、环境隔离(prod/staging)、规模上限与爆炸半径控制。三种拓扑:

  1. 松散联邦:各集群独立,上层用 Argo CD ApplicationSet/Flux 统一投递配置,用全局 LB(Anycast/GSLB)做跨集群入口——最简单、最主流;
  2. 控制面联邦:Karmada / KubeFed 式,把应用一次提交、按策略分发到成员集群,支持副本迁移与故障转移;
  3. 服务网格多集群:信任域联邦,东西向 mTLS 跨集群直连,适合跨集群服务级容灾。

配套:跨集群身份用联邦 OIDC;DNS 用 ExternalDNS + 全局负载均衡;存储与备份(Velero)按 RPO/RTO 设计;Cluster API + GitOps 实现"集群像牛而不是宠物"——几十个集群的创建、升级、销毁全部声明式自动化。

23.3 平台工程:把 Kubernetes 藏到自助平台背后

Kubernetes 强大但复杂,让每个业务团队都精通 YAML、探针、HPA、NetworkPolicy 既不现实也危险。2023 年以来 Platform Engineering 成为明确方向:平台团队构建内部开发平台(IDP),为开发者提供黄金路径(Golden Path)——一条从代码到生产的、带安全默认值的自助通道。

flowchart TD
    DEV["开发者"] --> PORTAL["开发者门户 (Backstage)
服务模板 / 文档 / 服务目录"] PORTAL -->|"选择模板: '一个带监控的 Web 服务'"| SCAFF["脚手架生成
Git 仓库 + Helm/CD 流水线 + CI"] SCAFF --> GITOPS["GitOps 下发 (Argo CD)"] GITOPS --> K8S["一个/多个 K8s 集群"] subgraph PLAT["平台层: 开发者不直接接触"] K8S OBS["可观测性 (OTel)"] SEC["安全基线 (Kyverno/PSS)"] NET["网格/网关 (Istio/Cilium)"] DB["自助中间件 (Operator: DB/MQ/缓存)"] end style PORTAL fill:#FEF3E2,stroke:#D97706,color:#0F1B2D style PLAT fill:#F5F8FC,stroke:#5A6B84,color:#0F1B2D
图 23-1 平台工程产品模型:抽象复杂度,开放自助能力

关键理念:平台是产品,开发者是内部用户;用 Backstage(CNCF 毕业)做服务目录与软件模板;用 Crossplane(CNCF 毕业)把云资源(RDS、桶、队列)也变成 K8s 对象,让应用与基础设施在同一套 GitOps/API 模型里自助申请;抽象工具(Helm Chart、Kustomize、内部 CRD)承载安全默认值与组织最佳实践,但保留逃生舱。目标不是让开发者学不到 K8s,而是让"做对的事"成为最省事的路径。

23.4 云原生技术地图与趋势

站在 2026 年回看,几个确定性趋势值得投入:① eBPF 正在重写网络(Cilium 成为事实标准 CNI)、观测与安全的数据面;② AI/ML on K8s(GPU 调度、KServe、LLM 推理服务、vLLM 编排)是增长最快的工作负载方向;③ WebAssembly(SpinKube/wasm-shim)探索更冷更快的工作负载;④ 统一控制面(Crossplane/Cluster API)让 K8s API 成为一切基础设施的通用接口。Kubernetes 本身正在" boring 化"——它不再是明星项目,而是像 Linux 一样稳定的底座;真正的价值已经转移到它之上的平台、数据与智能工作负载。

结语

学习路线与练习清单

读完这本书只是起点。下面给出一条被许多工程师验证过的 16 周实操路线,每周都有可验证的产出:

阶段周期目标与验收产出
入门第 1–3 周kind 三节点集群;部署一个多副本 Web 应用 + Service + Ingress;能手写 Deployment/Service/Ingress YAML
原理第 4–6 周读 kube-scheduler 调度事件、抓 iptables/IPVS 规则、观察一次 etcd watch;画出从 kubectl apply 到 Pod Running 的完整时序
进阶工作负载第 7–9 周StatefulSet 部署一套有状态服务(如 PostgreSQL 主从或 Kafka);配置 PV 动态供给、探针、PDB、HPA
交付第 10–11 周把应用打成 Helm Chart;Argo CD 接管;实现金丝雀发布并验证自动回滚
运维第 12–14 周搭建 Prometheus+Grafana+Loki+OTel,定义 3 条 SLO 与告警;制造故障演练(删 Pod、切网络、打满磁盘);完成一次 etcd 备份恢复演练
平台第 15–16 周用 Kyverno 施加安全基线;用 Kubebuilder 写一个小 Operator(如 TLS 证书自动续期或网站控制器);写一篇复盘

表 24-1 16 周从入门到平台工程师的实操路线

最后的建议

Kubernetes 学不深的人通常只有一个原因:只点鼠标、不碰真实故障。请务必准备一个"随便搞坏"的集群,主动制造问题、读日志、看内核与规则、再修复它。原理、底层、实战三者会在每一次排障中互相印证。当你能用一个 kubectl describe 还原出 apiserver、scheduler、kubelet、CRI、CNI 的协作过程时,你就真正掌握了 Kubernetes。

Appendix

参考资料与延伸阅读

本书编写时参考了 Kubernetes 官方文档与 CNCF 生态项目的最新资料(2025–2026 年版本),以下为核心来源,建议按顺序延伸阅读:

  1. Kubernetes 官方文档(概念、组件、API 参考与版本说明),kubernetes.io/docs;以及 Kubernetes 1.32–1.35 Release Notes,github.com/kubernetes/kubernetes CHANGELOG。
  2. Kubernetes 架构与控制平面组件参考:kube-apiserver、etcd、scheduler、kubelet 官方概念页,Components of Kubernetes。
  3. 容器运行时与 CRI:containerd 文档与 Kubernetes CRI 说明,containerd/containerd;CRI 规范 kubernetes/cri-api。
  4. Kubernetes 网络模型与 CNI:Cluster Networking;Cilium 文档与 eBPF 数据面说明 docs.cilium.io;Calico 文档 docs.tigera.io。
  5. 存储体系:持久卷、StorageClass、CSI 规范,Kubernetes Storage Concepts;CSI 接口仓库 container-storage-interface/spec。
  6. 调度器框架与资源管理:Scheduling, Preemption and Eviction;资源管理与 QoS Manage Resources for Containers。
  7. Helm 3/4 文档与版本生命周期公告,helm.sh/docs;Helm 4 发布说明 helm.sh/blog。
  8. OpenTelemetry 官方文档与 eBPF Instrumentation(OBI/Beyla 捐赠)进展,opentelemetry.io/docs;Grafana Beyla grafana.com/oss/beyla-ebpf。
  9. Karpenter 文档及 2025 年 CNCF 毕业公告,karpenter.sh/docs;OpenCost 成本分摊规范 opencost.io。
  10. Istio Ambient Mesh GA 文档与服务网格架构说明,istio.io Ambient Mesh;Cilium Service Mesh docs.cilium.io servicemesh。

其他强烈推荐的经典资料:Brendan Burns 等《Kubernetes in Action / K8s in Action 2nd》、Liz Rice《Container Security》、Google SRE Books(sre.google/books)、CNCF Landscape(landscape.cncf.io),以及认证体系 CKA/CKAD/CKS 的官方考纲。