Kubernetes 深入实践
从容器底层原理到企业级云原生运维——一本书讲透 Kubernetes 的原理、底层、代码与实战,覆盖 v1.34 / v1.35 最新特性与 CNCF 生态全貌。
关于本书
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 版本,具体命令输出可能略有差异,请以官方文档为准。
云原生与 Kubernetes 概览
1.1 部署形态的演进:从物理机到云原生
理解 Kubernetes 为什么会出现,先要理解它解决的痛点。应用部署形态在过去二十年经历了三次关键跃迁:
timeline
title 应用部署形态演进
物理机时代 : 一个应用一台机器 : 资源利用率低 : 扩容以天计
虚拟化时代 : 一台物理机多台 VM : 隔离性好但镜像笨重 : 扩容以分钟计
容器时代 : 共享内核的轻量隔离 : 镜像小、启动快 : 不可变基础设施
编排时代 : Kubernetes 声明式调度 : 自愈与自动扩缩 : 扩容以秒计
容器解决了"打包"问题——应用连同依赖被封装成不可变镜像,在开发、测试、生产环境保持一致。但当容器数量从十个变成一万个,新的问题出现了:哪个容器该放到哪台机器?机器挂了容器谁来重建?发布新版本如何不中断流量?容器之间怎么互相发现?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.32 | Penelope 2024-12 | 结构化授权配置进入 Beta;DRA 结构化参数 Beta;StatefulSet 自动删除 PVC GA |
| v1.33 | Octarine 2025-04 | Sidecar Containers 正式 GA;In-Place Pod Resize 进入 Beta;Linux 用户命名空间支持 |
| v1.34 | Of Wind & Will 2025-08 | DRA 核心 GA(GPU/TPU/NIC 分配共享);结构化授权配置 GA;External JWT Signers Beta |
| v1.35 | Timbernetes 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、Envoy | Pod 网络、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
容器技术底层: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、独立的端口空间 |
| IPC | System V 消息队列、共享内存 | 容器间无法直接用共享内存通信 |
| UTS | 主机名与域名 | 容器可以有自己的 hostname |
| User | UID/GID 映射 | 容器内 root 在宿主机上可映射为普通用户(v1.33 起 K8s 正式支持) |
| Cgroup | cgroup 根目录视图 | 容器内无法看到宿主机全部资源限制 |
表 2-1 Linux 七大命名空间
动手实验:亲眼看见 namespace
在任意 Linux 机器上执行以下命令,可以亲手创建一个隔离环境(需要 root):
$ 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
这就是 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
需要特别注意 containerd 与 runc 的分工:runc 只是一个命令行工具,负责"一次性地"创建出容器进程然后退出;containerd 通过 containerd-shim 作为容器进程的父进程持续在场,负责收集退出状态、管理 stdio。所以你在节点上看到的容器进程父进程往往是 containerd-shim-runc-v2,而不是 containerd 本身。
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 做的所有上层抽象,最终都要落到这几个内核机制上。
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.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
整个过程中没有任何组件是被"命令"的:kubectl 只是写入了一个对象;调度器、kubelet 都是 watch 到变化后各自行动。任何一步失败,对应控制器都会重试而不是报错退出——这就是声明式系统与命令式脚本的本质区别。
3.5 高可用部署形态
生产集群的控制平面通常堆叠或外置部署:3 个控制平面节点各跑一份 apiserver(前面挂 LB)、scheduler 与 controller-manager(利用 leader election 保证同一时刻只有一个活跃实例,但所有副本都在待命)、一个 3/5 节点的 etcd 集群(Raft 多数派:容忍 (n-1)/2 个节点故障,所以必须奇数)。工作节点通过 LB 的虚拟 IP 访问 apiserver,单个控制平面节点宕机不影响集群运行。
核心 API 对象
Kubernetes 中一切皆"对象"(Object),每个对象都是一段期望状态的声明。先建立通用结构的概念,再逐一认识最重要的几类对象。
4.1 对象的通用结构
无论哪种资源,YAML 都由四个字段统领:
| 字段 | 含义 |
|---|---|
apiVersion | API 组与版本,如 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 不退出"两大顽疾。
三种探针
| 探针 | 失败的后果 | 典型用途 |
|---|---|---|
| livenessProbe | kubelet 杀死并重启容器 | 捕获死锁等"进程在但不可用" |
| 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 | 有序、有稳定身份与独立存储的 Pod | MySQL、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 可以层层下钻查看任意字段的类型与说明,比搜索引擎更快、且永远与当前集群版本一致。
声明式 API 与 etcd
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)写入的字段。
K8s 控制器不依赖"事件不能丢",而是周期性地拿到当前状态的完整水位再决定动作。哪怕控制器重启漏掉 100 个事件,重启后一次 LIST 就能看到全部现状并继续收敛。这是它能稳定支撑百万级对象的关键,也是你写 Operator 时必须遵循的原则:Reconcile 必须幂等、可重入,不能依赖事件顺序。
5.2 LIST/WATCH:集群的"神经系统"
声明式系统要持续收敛,控制器必须高效地感知变化。轮询(每隔几秒 LIST 全量)在十万 Pod 的集群里会压垮 apiserver。K8s 的答案是 LIST + WATCH 协议(基于 HTTP 长连接 + chunked JSON,或新版的 HTTP/2 流式 protobuf):
- 客户端先 LIST 全量对象,响应中携带最新的
resourceVersion(RV,etcd 全局递增版本号); - 随后携带该 RV 发起 WATCH,apiserver(底层 watch etcd)把之后的每次增删改以事件流推送:
ADDED / MODIFIED / DELETED; - 连接中断时,客户端从最后看到的 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
这个设计解决了四个问题:本地缓存让控制器读对象不打 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。一次写入,驱动整个集群的自动化。
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
第一关: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 服务自身挂了且 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"的唯一可靠手段。排查权限问题则用:
$ 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.1 Reconcile:Kubernetes 的核心算法
Kubernetes 里几乎没有"命令式动作",只有一个个控制循环。每个控制器的逻辑都可以浓缩为同一段伪代码:
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
内置插件实现了你熟悉的所有策略:NodeResourcesFit(CPU/内存/临时存储是否够)、NodeAffinity、PodAffinity/AntiAffinity、TaintToleration、NodeUnschedulable、VolumeBinding(处理 WaitForFirstConsumer 存储)、TopologySpreadConstraints、DefaultBind 等。v1.32 引入的 SchedulerQueueingHints(Beta 默认开启)让 Pod 在相关事件发生时才重新入队,显著提升大规模集群的调度吞吐。
7.3 调度策略五件套
| 机制 | 方向 | 作用 |
|---|---|---|
| nodeSelector / nodeAffinity | Pod → 选节点 | 硬性/软性要求节点标签(如 GPU 机型、可用区) |
| podAffinity / podAntiAffinity | Pod → 参考 Pod | 希望与某类 Pod 同域(就近)或跨节点/跨域分散(高可用) |
| topologySpreadConstraints | Pod → 拓扑 | 按 hostname/zone/自定义拓扑域做均衡分布,比反亲和更精确可控 |
| taints + tolerations | 节点 → 排斥 Pod | 节点"拒绝"普通 Pod,Pod 必须声明容忍才能上 |
| priority + preemption | Pod → 抢资源 | 高优先级 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)必须容忍这些污点才能在故障节点上继续工作。
kubectl describe pod <p> 的 Events 里调度器会直接给出过滤失败原因,如 0/5 nodes are available: 3 Insufficient cpu, 2 node(s) had untolerated taint——这行信息就是 Filter 阶段各插件的拒绝汇总,无需猜测。
kubelet 与容器运行时深入
8.1 kubelet 的主循环与 PLEG
kubelet 是节点上的"驻场经理",它的核心工作流:
- 通过 apiserver watch(或静态 Pod 目录
/etc/kubernetes/manifests)获取分配到本节点的 PodSpec; - PLEG(Pod Lifecycle Event Generator)定期(或通过 CRI 事件)对比容器实际状态与期望,生成"容器退出/启动失败"等生命周期事件,驱动 Pod 状态机更新——PLEG 卡住会导致节点被判定 NotReady,经典报错
PLEG is not healthy; - 调用容器运行时执行同步:创建沙箱 → 挂载卷 → 拉镜像 → 启动容器;
- 运行探针、汇报资源用量(metrics-server 数据来源)、更新 status;
- 执行驱逐管理器与节点状态上报。
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。运维常用工具:
$ 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 命名空间)
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 对资源的掌控正在从"一次性静态分配"走向"运行时动态编排"。
Kubernetes 网络模型与 CNI
K8s 网络是初学者最大的迷雾,因为它横跨 Linux 内核网络栈、CNI 插件实现和 iptables/eBPF 多个层面。本章先把最基础的三条原则和 Pod 网卡的诞生过程讲透。
9.1 Kubernetes 网络模型三条铁律
- 每个 Pod 获得集群唯一 IP,Pod 内容器共享该 IP(共享 netns);
- 任意 Pod 间可以不经 NAT 直接通信,无论是否跨节点;
- 节点上的代理(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
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 不匹配是疑难丢包的常见原因)。
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.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。
# 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 可启用拓扑感知路由:流量优先留在本可用区,降低跨区延迟与云厂商跨区流量费。
主流 CNI 方案、NetworkPolicy 与服务网格
11.1 三大 CNI 横向对比
| 维度 | Flannel | Calico | Cilium |
|---|---|---|---|
| 数据路径 | VXLAN / host-gw | BGP 直路 / IPIP / VXLAN / eBPF / VPP | eBPF 原生,可覆盖 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 选中,未被允许的流量默认拒绝。生产基线推荐为每个命名空间配"默认拒绝入站 + 显式放行"。
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 仍将长期维护但不再演进。
存储原理:PV、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
这张图回答了两个高频问题:为什么 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-Ceph | Ceph(RBD 块 / CephFS 文件 / RGW 对象) | 自建数据中心、需要全套分布式存储 |
| Longhorn | 分布式块存储、副本同步、UI 友好 | 中小规模裸金属/边缘生产 |
| 云厂商 CSI(EBS PD/EBS/OSS CSI) | 托管云盘/文件服务 | 云上集群首选,可靠性由云保障 |
表 12-1 常见 Kubernetes 存储方案
① StatefulSet 缩容/删除 PVC 默认不回收(v1.27+ 可配策略自动删除),盘往往比 Pod 长寿——这是数据安全设计,不是 bug;② 数据库跑 K8s 时,备份责任仍在你自己:存储层快照 + Velero + 逻辑备份(如 mysqldump)应组合使用,并定期做恢复演练。
搭建实战环境
纸上得来终觉浅。本书建议你立刻在本机准备一个可以随便折腾的多节点集群。三种主流本地方案:
| 工具 | 本质 | 适合 |
|---|---|---|
| kind | Docker 容器当"节点",kubeadm 引导,多节点最方便 | 本书首选、CI 测试、版本切换 |
| k3d | Docker 里跑轻量发行版 k3s | 资源紧张、边缘场景、快速起停 |
| minikube | VM/容器驱动,功能面板丰富 | 入门学习、单机 GUI 体验 |
表 13-1 本地集群工具对比
13.1 用 kind 创建三节点集群
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
$ 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:
$ 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:
$ 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
kubectl 必备技能
kubectl 是你与集群对话的手。本章按工作场景组织高频命令,建议全部亲手敲一遍并配置好别名与自动补全。
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
$ 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 与编辑
$ 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 发布与回滚
$ 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 调试与节点操作
$ 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 可以只看调度失败事件。
工作负载 YAML 实战
本章给出可直接用于生产的完整 YAML 模板,字段全部采用当前稳定 API 版本。建议作为你的个人清单收藏。
15.1 生产级 Deployment
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 与挂载
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 模板
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
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:弹性与可用性护栏
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 在线。它不防节点宕机等非自愿故障,但能防止运维操作一把把服务全部排空。
应用发布策略与 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:
$ 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 保持一致:
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]。
$ 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 函数库:
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(生态中几乎所有中间件都提供 Chart);只是想给同一套 YAML 打环境补丁(dev/staging/prod 的镜像、副本数差异)选 Kustomize(kubectl 原生 -k 支持,无模板语言)。二者并不冲突:Helm 支持 post-rendering 调用 Kustomize 对渲染结果再加工。
故障排查、etcd 备份与灾备恢复
排障能力是运维与开发的分水岭。本章给出 Kubernetes 最常见故障的标准化排查路径,全部基于你已学的原理,而非背命令。
17.1 通用排障心法:自顶向下五层
- 对象层:
get -o wide看状态、describe看 Events; - 工作负载层:日志(含
--previous)、退出码、探针; - 调度层:Pending → 资源/污点/亲和/PVC;
- 网络层:DNS → Endpoints → kube-proxy → CNI → NetworkPolicy;
- 节点层:kubelet/containerd/磁盘/内核(
kubectl describe node、dmesg)。
17.2 Pod 常见状态速查表
| 现象 | 根因域 | 关键命令与修复 |
|---|---|---|
| Pending | 调度不出去 | describe 看 Events:资源不足 / 污点不容忍 / PVC 未绑定 / 亲和无匹配;扩容节点或放宽约束 |
| ImagePullBackOff | 镜像 | 核对镜像名与 tag、imagePullSecret、仓库网络;crictl pull 在节点上复现 |
| CrashLoopBackOff | 应用反复退出 | logs --previous 看退出原因:配置缺失、权限、数据库不通、启动命令错误;BackOff 是递增等待不是新故障 |
| OOMKilled (137) | 内存超 limit | describe 确认 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
$ 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,可直接用集群内证书操作:
# 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 负责底层兜底,二者互补。
二次开发入门: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 控制器骨架
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、镜像构建全部工程化:
$ 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 框架):
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 告警。学完这些,你就具备了平台工程师的核心能力。
集群生命周期与 GitOps 交付
19.1 发行版选型:从 kubeadm 到托管 K8s
| 方案 | 特点 | 适用 |
|---|---|---|
| kubeadm | 官方引导工具,各组件原生、可定制性强,运维责任全在自己 | 自建数据中心、需要深度掌控、CKA 考试 |
| k3s / RKE2 | 单二进制、资源占用低(k3s);RKE2 面向政府/安全合规,默认加固 | 边缘、IoT、分支机构、中小团队 |
| EKS / GKE / AKS | 托管控制平面,节点自管或全托管(Fargate/Autopilot),与云 IAM/网络深度集成 | 云上生产主流,降低控制平面运维 |
| OpenShift | Red 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 与托管集群一致):
- 升级前:阅读目标版本 release notes 的 deprecated/removed API(用
kubectl deprecations或 pluto 工具扫描清单);备份 etcd;确认所有 PDB 合理、应用可容忍滚动;先在测试集群验证。 - 升级控制平面:逐台进行(
kubeadm upgrade plan→apply→ 升级 kubelet/kubectl 包 → uncordon),一次只动一个控制平面节点。 - 升级工作节点:逐台 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
| 维度 | Argo CD | Flux |
|---|---|---|
| 形态 | 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 毕业项目)
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]
可观测性体系:指标、日志、链路
"监控(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 必备模式
# 容器 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
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。
弹性伸缩体系与 FinOps 成本治理
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
- 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 成本 | 建立责任归属 |
| rightsizing | VPA 建议 + 超卖比治理 + 清理无主资源 | 10%–40% |
| Spot/抢占实例 | Karpenter 按 Pod 中断容忍度混合供给 Spot(最高可省 90% 单价)[9] | 无状态批处理可达 60%+ |
| 弹性调度 | HPA/KEDA + 节点自动缩容,夜间/周末缩零非生产环境 | 30%–70%(非生产) |
| 架构优化 | 合并碎片化集群、用 ARM/Graviton、GP3/标准盘替换高端盘 | 10%–20% |
表 21-1 Kubernetes FinOps 五大杠杆
Spot 实例可能被 2 分钟通知后回收。无状态、可中断、副本跨实例类型分布的工作负载(配合多 AZ、PDB、Karpenter node disruption handling)才适合;有状态主库、单点服务不要放。
安全、合规与多租户
安全是贯穿整个栈的体系工程:从容器镜像、到 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。
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,通过命名空间标签一键施加三级基线:
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.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)、规模上限与爆炸半径控制。三种拓扑:
- 松散联邦:各集群独立,上层用 Argo CD ApplicationSet/Flux 统一投递配置,用全局 LB(Anycast/GSLB)做跨集群入口——最简单、最主流;
- 控制面联邦:Karmada / KubeFed 式,把应用一次提交、按策略分发到成员集群,支持副本迁移与故障转移;
- 服务网格多集群:信任域联邦,东西向 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
关键理念:平台是产品,开发者是内部用户;用 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。
参考资料与延伸阅读
本书编写时参考了 Kubernetes 官方文档与 CNCF 生态项目的最新资料(2025–2026 年版本),以下为核心来源,建议按顺序延伸阅读:
- Kubernetes 官方文档(概念、组件、API 参考与版本说明),kubernetes.io/docs;以及 Kubernetes 1.32–1.35 Release Notes,github.com/kubernetes/kubernetes CHANGELOG。
- Kubernetes 架构与控制平面组件参考:kube-apiserver、etcd、scheduler、kubelet 官方概念页,Components of Kubernetes。
- 容器运行时与 CRI:containerd 文档与 Kubernetes CRI 说明,containerd/containerd;CRI 规范 kubernetes/cri-api。
- Kubernetes 网络模型与 CNI:Cluster Networking;Cilium 文档与 eBPF 数据面说明 docs.cilium.io;Calico 文档 docs.tigera.io。
- 存储体系:持久卷、StorageClass、CSI 规范,Kubernetes Storage Concepts;CSI 接口仓库 container-storage-interface/spec。
- 调度器框架与资源管理:Scheduling, Preemption and Eviction;资源管理与 QoS Manage Resources for Containers。
- Helm 3/4 文档与版本生命周期公告,helm.sh/docs;Helm 4 发布说明 helm.sh/blog。
- OpenTelemetry 官方文档与 eBPF Instrumentation(OBI/Beyla 捐赠)进展,opentelemetry.io/docs;Grafana Beyla grafana.com/oss/beyla-ebpf。
- Karpenter 文档及 2025 年 CNCF 毕业公告,karpenter.sh/docs;OpenCost 成本分摊规范 opencost.io。
- 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 的官方考纲。