⎈ K8s 进阶实战 · 第二册
第一册带你「看懂 K8s 怎么转」,这一册解决另一半问题:把服务配得稳、跟团队协作、用对工具链—— 这些是入职一个云原生组之后,你每天真正会碰的活。
健康探针:别让三种探针互相误杀
没有探针,K8s 只知道「进程在不在」,不知道「它能不能干活」。进程活着但连不上数据库 → 还在接流量 → 用户看到 500。探针就是让 K8s 从「看进程」升级到「看健康」。
三种探针各司其职:livenessProbe(存活)失败 → 重启容器;readinessProbe(就绪)失败 → 把 Pod 从 Service 的 Endpoints 摘除、不再给它流量(但不重启);startupProbe(启动)在它通过之前,暂时禁用另外两个探针,专门保护慢启动的应用。
「进程活着」≠「能对外服务」。Java 应用启动要 60 秒、依赖的数据库偶尔抖动、代码偶发死锁——这些状态都需要 K8s 区别对待:该等的等、该摘流量的摘、该重启的重启。
几乎每个对外服务都该配 readiness(滚动更新不丢请求的关键);启动慢的配 startup;能通过重启自愈的死锁场景配 liveness。
三种探测方式:httpGet(调个健康接口)、tcpSocket(端口能连上就行)、exec(在容器里跑命令,退出码 0 算通过)。👇
liveness 失败 = 重启(治「卡死」);readiness 失败 = 摘流量(治「暂时不可用」)。最致命的错误:把 liveness 指向数据库连接——数据库一抖,所有 Pod 同时被判死、集体重启 → 雪崩。liveness 只该探「我自己这个进程健不健康」。
containers:
- name: order
image: shop/order:v1.2
# ① 启动探针:给慢启动一个保护期,通过前另两个探针不生效
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30 # 最多等 30 次 × 5s = 150 秒启动
periodSeconds: 5
# ② 就绪探针:没就绪就不接流量(从 Service Endpoints 摘除)
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 3 # 连续 3 次失败才摘除
# ③ 存活探针:卡死了重启我。注意——只探自己,别探数据库!
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
failureThreshold: 3| 关键参数 | 含义 | 踩坑点 |
|---|---|---|
| initialDelaySeconds | 容器启动后等多久开始第一次探测 | 配了 startupProbe 就不用它兜启动时间了 |
| periodSeconds | 每隔多久探一次 | 太密集给应用加压,太稀疏故障发现慢 |
| timeoutSeconds | 单次探测超时 | 默认 1s,接口偶尔慢就误判,酌情调大 |
| failureThreshold | 连续几次失败才算「坏了」 | 设成 1 太敏感,一次网络抖动就重启/摘流量 |
| successThreshold | 连续几次成功才算「好了」(readiness) | 恢复要稳,避免反复横跳 |
/health 写成「顺便 ping 一下数据库」,数据库一慢,全体 Pod 的 liveness 连续失败 → 全部重启 → 重启后又连不上 → 死循环。规矩:liveness 只查进程自身,依赖健康交给 readiness。第 1 章自测
三种探针别用混资源与 QoS:requests 决定调度,limits 决定生死
requests / limits 这两个字段,决定了你的 Pod 能不能被调度上、跑起来稳不稳、集群缺资源时第一个被干掉的是不是它。不懂它,就等着莫名其妙的 OOMKilled 和 Pending。
requests(请求量)是调度依据——Scheduler 按它找「装得下」的节点,是给你预留的保证量;limits(上限)是硬约束——超了要挨罚。两者共同决定 Pod 的 QoS 等级。
集群资源有限、多个应用挤一台机器。没有 requests,调度器不知道该把 Pod 放哪;没有 limits,一个内存泄漏的应用能把整台节点吃垮,拖死邻居。
CPU 用 500m(半核)、内存用 512Mi 这样表达。👇
CPU 超 limit = 被限流(throttle,减速但不杀);内存超 limit = OOMKilled(内核直接杀,exit code 137)。这是两种完全不同的惩罚——CPU 是可压缩资源,内存是不可压缩资源。
resources:
requests: # 调度按这个预留,也是被驱逐时的"护身符"
cpu: "250m"
memory: "256Mi"
limits: # 上限:CPU 超了限流,内存超了 OOMKill
cpu: "500m"
memory: "512Mi"2.1 QoS 三个等级 —— 决定谁先被驱逐
当节点内存告急,kubelet 会按 QoS 等级从低到高驱逐 Pod:
| QoS 等级 | 怎么才算 | 节点压力时 |
|---|---|---|
| Guaranteed | 每个容器 requests == limits,且 CPU 和内存都设了 | 最后才动它 —— 最稳 |
| Burstable | 设了 requests,但不满足 Guaranteed(如只设了 requests、或 limits > requests) | 中间,超出 requests 的部分优先被回收 |
| BestEffort | 啥都没设 | 第一个被杀 —— 最脆 |
-XX:MaxRAMPercentage=75.0 让 JVM 读容器的 limits。Node/Python 同理要注意别让运行时误判可用内存。第 2 章自测
资源与驱逐优雅上下线:滚动更新为什么会丢请求
「明明配了滚动更新,为什么每次发版都有几个 5xx?」十有八九是没处理好 Pod 的起与停。这一章讲清 initContainer(起之前)和优雅停机(停之时)。
initContainer(初始化容器)在主容器启动之前按顺序执行,全部成功了主容器才开始跑;优雅停机是 Pod 被删除时的一套收尾流程,让它「处理完手头的活再走」。
起之前常要做准备:等依赖就绪、拉配置、跑数据库迁移。停之时如果一刀切断,正在处理的请求就会失败——这就是发版丢请求的根因。
initContainer 管「起之前」(顺序、一次性、成功即退出);preStop + SIGTERM 管「停之时」。别把「等依赖」的逻辑塞进主容器的启动脚本,用 initContainer 更清晰。
3.1 Pod 终止的完整流程(丢请求就出在这)
sleep 5,等 Endpoints 传播完),再给主进程发 SIGTERM。应用应监听 SIGTERM:停止接新请求、把存量请求处理完、关闭连接。spec:
terminationGracePeriodSeconds: 30
initContainers:
- name: wait-db # 主容器起之前,先等数据库通
image: busybox:1.36
command: ['sh','-c','until nc -z mysql 3306; do sleep 2; done']
containers:
- name: order
image: shop/order:v1.2
lifecycle:
preStop:
exec:
# 先睡几秒,等 Endpoints 摘除传播到所有节点,再让应用退出
command: ['sh','-c','sleep 5']第 3 章自测
起与停sleep 5?弹性伸缩 HPA:副本数交给指标自己定
第一册里副本数 N 是你手写死的。真实流量有波峰波谷——大促要自动扩、深夜要自动缩省钱。HPA 就是那个「根据指标自动加减 Pod」的控制器(又一次调谐循环!)。
HPA(HorizontalPodAutoscaler)根据指标(CPU/内存利用率,或自定义的 QPS 等)自动增减 Deployment/StatefulSet 的副本数。
手动定 N 应付不了波动:定高了平时浪费钱,定低了高峰扛不住。HPA 让副本数跟着负载自动呼吸。
流量有明显波动的无状态服务(Web/API)。有状态服务扩缩要谨慎(涉及数据再平衡),一般不直接 HPA。
HPA = 加 Pod(水平,最常用);VPA = 调单个 Pod 的 requests/limits(垂直,与 HPA 基于 CPU 时会打架,少用);Cluster Autoscaler / Karpenter = 加节点(当 Pod 因资源不足 Pending 时)。三者是不同层次,常配合:HPA 加 Pod → 节点不够 → Cluster Autoscaler 加节点。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: order }
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # 目标:平均 CPU 用到 requests 的 60%第 4 章自测
自动扩缩kubectl top pod 看到数据。调度控制:让 Pod 落在对的地方
默认调度器会自己找节点,但很多时候你需要干预:GPU 任务只能上 GPU 机器、同一应用的副本要打散到不同可用区抗故障、升级节点时别一次干掉太多副本。这一章是这些手段的地图。
| 手段 | 谁挑谁 | 典型用途 |
|---|---|---|
| nodeSelector | Pod 挑节点(最简单) | 按标签选:只上 disktype=ssd 的节点 |
| nodeAffinity | Pod 挑节点(能力更强) | 硬要求(required)/软偏好(preferred),如「优先放 zone-a」 |
| pod(Anti)Affinity | Pod 挑 Pod | 同应用副本互斥打散 / 让缓存挨着业务放 |
| taint + toleration | 节点挑 Pod | 节点打「污点」排斥,只有带「容忍」的 Pod 能上(GPU 专用机、master 节点) |
| topologySpreadConstraints | 按拓扑均匀铺 | 副本在各 zone/node 均匀分布,抗单点/单区故障 |
| PodDisruptionBudget | 限制"同时挂几个" | 节点排空/升级时,保证最少可用副本数,别一次全干掉 |
spec:
# 同一应用的副本尽量分到不同节点(软反亲和)
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector: { matchLabels: { app: order } }
topologyKey: kubernetes.io/hostname
# 按可用区均匀铺,最多差 1 个
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector: { matchLabels: { app: order } }
# 容忍 GPU 节点的污点(否则上不去)
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"kubectl drain)做升级时,如果不配 PodDisruptionBudget,可能一下驱逐掉你 3 个副本里的 2 个,短暂只剩 1 个扛全部流量。配上 minAvailable: 2,K8s 会保证驱逐过程中始终有至少 2 个可用。第 5 章自测
放哪、别放哪、别一起挂Namespace 与配额:给团队划地盘
一个集群往往被多个团队、多个环境共用。Namespace 是逻辑分区,ResourceQuota 和 LimitRange 是防止某个团队「吃独食」把资源占光的护栏。
Namespace把集群逻辑切分成多个空间(如 dev / staging / prod,或按团队);ResourceQuota限制一个 ns 的总资源用量;LimitRange给 ns 内单个 Pod/容器设默认值和上下限。
共享集群里,一个失控的应用能把 CPU/内存吃光、拖垮所有邻居。配额就是给每个团队圈一块地,越界就被拒绝。
ResourceQuota = ns 的总量天花板(这个 ns 所有 Pod 加起来最多用 100 核);LimitRange = 单个对象的默认/边界(没写 resources 的容器自动补默认、单容器最多 4 核)。两者配合用。
apiVersion: v1
kind: ResourceQuota
metadata: { name: team-a-quota, namespace: team-a }
spec:
hard:
requests.cpu: "20" # 这个 ns 所有 Pod 的 requests 总和上限
requests.memory: 40Gi
limits.cpu: "40"
pods: "50"
---
apiVersion: v1
kind: LimitRange
metadata: { name: team-a-defaults, namespace: team-a }
spec:
limits:
- type: Container
default: { cpu: "500m", memory: 512Mi } # 没写 limits 时的默认
defaultRequest: { cpu: "100m", memory: 128Mi } # 没写 requests 时的默认第 6 章自测
地盘与护栏RBAC 与 ServiceAccount:谁能干什么
「你能不能删这个 Deployment」「这个 Pod 能不能读 Secret」——这些都由 RBAC 说了算。它是集群安全的第一道闸,也是很多入侵事故的突破口。
请求到 API Server 要过两关:认证(你是谁)→ 授权(你能干啥)。RBAC 就是最常用的授权机制,用「角色 + 绑定」描述权限。
集群是共享的,不能人人都是管理员。最小权限原则:每个人/每个程序只拿它干活必需的权限。
Role/ClusterRole 定义「能对哪些资源做哪些动作」,RoleBinding/ClusterRoleBinding 把角色绑给具体的 user / group / ServiceAccount。👇
Role = namespace 内,ClusterRole = 集群级(或跨 ns 复用)。User(人)由集群外部管理、K8s 里没有对应对象;ServiceAccount(SA)是 K8s 对象,专给 Pod / 程序访问 API 用。人用 kubeconfig,程序用 SA。
apiVersion: v1
kind: ServiceAccount
metadata: { name: reader-sa, namespace: team-a }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # 只在 team-a 这个 ns 内有效
metadata: { name: pod-reader, namespace: team-a }
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"] # 只读,不给 delete/create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding # 把 pod-reader 角色绑给 reader-sa
metadata: { name: bind-reader, namespace: team-a }
subjects:
- kind: ServiceAccount
name: reader-sa
roleRef: { kind: Role, name: pod-reader, apiGroup: rbac.authorization.k8s.io }# Pod spec 里指定用哪个 SA(不指定就是 ns 的 default SA) spec: serviceAccountName: reader-sa automountServiceAccountToken: false # 不需要访问 API 的 Pod,干脆别挂 token --- # 排查权限神器:我(或某个 SA)到底能不能做某件事? # kubectl auth can-i delete pods --as=system:serviceaccount:team-a:reader-sa
第 7 章自测
身份与权限NetworkPolicy:给 Pod 之间装防火墙
K8s 默认网络是「全通」的——任意 Pod 能访问任意 Pod,哪怕跨 namespace。生产合规常要求「数据库只允许后端访问」,这就得靠 NetworkPolicy 做 Pod 级的网络分段。
NetworkPolicy用标签选择器定义某组 Pod 的入站(ingress)/出站(egress)白名单:谁能连我、我能连谁。
默认全通意味着一个被攻破的前端 Pod 能直连数据库。零信任要求「非必要不放行」,把爆炸半径压到最小。
一个 Pod 只要被任意一条 NetworkPolicy 选中,它就从「默认全通」翻转为「默认全禁,只放行规则里写的」。没被任何 policy 选中的 Pod 仍然全通。这个「翻转」语义是最容易搞错的地方。
# ① 本 ns 默认拒绝所有入站(一旦有这条,未被放行的入站全断)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny-ingress, namespace: shop }
spec:
podSelector: {} # 选中本 ns 所有 Pod
policyTypes: ["Ingress"]
---
# ② 只放行:带 app=frontend 的 Pod → 访问 app=backend 的 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-frontend-to-backend, namespace: shop }
spec:
podSelector: { matchLabels: { app: backend } }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector: { matchLabels: { app: frontend } }
ports:
- { protocol: TCP, port: 8080 }第 8 章自测
网络分段Helm:K8s 的包管理器
真实项目里没人手写十几个裸 YAML。多环境(dev/prod)常常只有几个值不同——用模板 + 一份 values 覆盖,一条命令搞定安装/升级/回滚。Helm 之于 K8s,就像 apt 之于 Linux、npm 之于 Node。
Helm 用「Chart」打包一个应用:templates/(带占位符的 YAML 模板)+ values.yaml(默认值)+ Chart.yaml(元信息)。安装一次得到一个 Release(有版本历史)。
同一套应用要部署到 dev/staging/prod,差异往往只是副本数、镜像 tag、域名。模板 + 分环境 values,避免复制粘贴维护 N 份 YAML。
Chart = 模板包(静态制品);Release = 装到集群里的一个实例(运行态,可升级/回滚)。同一个 Chart 可以装出多个 Release。
# 用 prod 的值安装或升级(--install:没装过就装,装过就升级) helm upgrade --install order ./order-chart -f values-prod.yaml # 只改个别值 helm upgrade order ./order-chart --set image.tag=v1.3 --set replicaCount=5 # 上线前先渲染成纯 YAML 看看它到底会生成什么(读别人 chart 的利器) helm template order ./order-chart -f values-prod.yaml # 看历史、一键回滚到上一个版本 helm history order helm rollback order 3 helm list # 当前集群装了哪些 Release helm uninstall order # 卸载
values.yaml 里那几个值再 helm upgrade。看不懂一个复杂 chart 会生成什么?用 helm template 把它渲染成最终 YAML,一目了然。上线前 helm template | kubectl diff -f - 预览变更,能救命。第 9 章自测
包与实例helm template 在本地就把模板和 values 渲染成实际会 apply 的纯 YAML,不连集群、无副作用。读渲染结果远比对着带 {{ }} 的模板脑补靠谱,也是上线前配合 kubectl diff 做变更预览的基础。Kustomize:不用模板的「覆盖式」配置
和 Helm 走两条路:Helm 靠模板变量,Kustomize 靠「一份基础 YAML + 各环境打补丁覆盖」,全程保持纯 YAML、不引入模板语言。而且它内置在 kubectl 里(kubectl apply -k),零额外安装。
Kustomize 用 base/(一套基础 manifests)+ overlays/{dev,prod}/(各环境的差异补丁)组织配置,用覆盖/合并的方式生成最终 YAML。
不想学模板语法、想让 YAML 始终是能被工具校验的纯 YAML;只需表达「prod 相对 base 改了哪几处」。
Helm:模板 + 变量 + 包管理 + 版本回滚,生态现成 chart 多;Kustomize:纯 YAML 覆盖、原生集成、无变量、无「Release/回滚」概念。两者可以组合(Helm 出包、Kustomize 做环境覆盖),很多团队 Argo CD 里两种都在用。
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base # 引入基础 manifests
replicas:
- name: order
count: 5 # base 里是 2,prod 覆盖成 5
images:
- name: shop/order
newTag: v1.3 # 覆盖镜像 tag
patches:
- path: prod-resources.yaml # 用补丁改 resources 等更复杂的字段kubectl apply -k overlays/prod # 直接应用 prod overlay kubectl kustomize overlays/prod # 只渲染出最终 YAML 看看(不 apply)
第 10 章自测
两条配置路线GitOps 工作流:改生产 = 提 PR,不是 kubectl apply
第一册第 9 章讲过 GitOps 的思想(Git 是唯一事实来源)。这一章落到「你每天怎么操作」——在成熟团队里,你改生产的方式很可能不是敲 kubectl,而是提一个 Pull Request。
GitOps:集群该长什么样,以 Git 仓库里的 YAML 为唯一事实来源。Argo CD / Flux 这类工具持续对比「Git 里的期望」和「集群的实际」,有差异就自动同步。
可审计(谁改的、改了啥,git log 全有)、可回滚(git revert)、防漂移(有人偷偷 kubectl 改了,会被自动纠回)、防人肉误操作(生产变更强制走 review)。
CI(构建、测试、打镜像)负责「产出变化」,是 push 式;CD/GitOps(Argo CD 把期望状态同步进集群)负责「让集群变成期望的样子」,是 pull 式。发现没有——这又是控制器调谐:Git 扮演 etcd,Argo 扮演控制器。
11.1 你的一次日常变更长这样
git checkout -b bump-order-v1.3 # ① 开分支 # ② 改配置库里的 values / manifests(比如把 image tag 改成 v1.3) git commit -am "order: v1.2 -> v1.3" git push origin bump-order-v1.3 # ③ 推分支 # ④ 开 Pull Request,CI 自动校验(lint / diff / 策略检查) # ⑤ 同事 review 通过 → 合并到 main # ⑥ Argo CD 检测到 Git 变了 → 自动同步进集群 → 在 Argo UI 看健康状态 # 出问题?git revert 那个 commit,Argo 自动把集群同步回旧版本
第 11 章自测
协作方式kubectl edit 了生产的 Deployment 副本数,过一会儿发现被改回去了。为什么?kubectl 排错手艺:日常 90% 都在这
干活时你敲得最多的不是写 YAML,而是排错。把这套命令练成肌肉记忆——看到症状条件反射出命令——比背原理更能决定你上手快不快。(第一册第 8 章的决策树是「怎么想」,这一章是「用什么命令」。)
12.1 排错命令速查表
| 命令 | 看什么 | 什么时候用 |
|---|---|---|
| kubectl get pod -o wide | Pod 状态、在哪个节点、IP | 第一眼总览,看 STATUS / READY / RESTARTS |
| kubectl describe pod X | Events、调度/拉镜像/探针/挂载详情 | Pod 起不来、Pending、CrashLoop 先看它 |
| kubectl logs X -f | 应用自己的输出 | 应用报错;--previous 看上次崩溃前的日志 |
| kubectl exec -it X -- sh | 进容器里手动查 | 验证配置、连通性、文件是否存在 |
| kubectl get events --sort-by=.lastTimestamp | 全局事件时间线 | 「刚才集群发生了什么」 |
| kubectl port-forward X 8080:8080 | 本地直连某个 Pod/Service | 绕过 Ingress 单独验证某个服务 |
| kubectl top pod / node | 实时 CPU/内存用量 | 查 OOM、资源打满(需 metrics-server) |
| kubectl rollout status/undo deploy X | 发布进度 / 一键回滚 | 滚动更新卡住或发错版本 |
12.2 进阶:镜像没 shell 怎么进去查?
现代精简镜像(distroless)连 sh 都没有,kubectl exec 进不去。用 kubectl debug 往目标 Pod 注入一个临时容器(ephemeral container),带着工具进去、和目标容器共享进程/网络命名空间。
👇 还能 debug 整个节点、复制文件出来。
# 往正在跑的 Pod 注入一个带工具的临时容器(不用改镜像) kubectl debug -it order-xxx --image=busybox --target=order # 把文件从容器里捞出来 kubectl cp shop/order-xxx:/app/logs/err.log ./err.log # 用 kubectl 生成 YAML 模板(不真的创建),改改再 apply kubectl create deploy demo --image=nginx --dry-run=client -o yaml > demo.yaml # 查某个字段怎么写 kubectl explain deployment.spec.strategy
kubectl 默认作用在当前 context 和 default ns。以为在测试环境、其实连着生产,一个 delete 下去就出事。养成习惯:操作前 kubectl config current-context 确认集群,命令里显式带 -n <namespace>,高危操作先 --dry-run。alias k=kubectl;装 kubectx / kubens 快速切集群和 ns;给高危集群的 context 起个刺眼的名字(如 PROD-DANGER)。这些小习惯长期省下大量时间、避掉大事故。第 12 章自测
手艺活kubectl logs 却看不到报错。最该加什么?kubectl logs X --previous 才是上一个已崩溃容器的临终遗言,配合 kubectl describe 的 Events / Last State 定位死因。kubectl exec -it -- sh 报「executable not found」进不去,怎么办?kubectl debug 通过临时容器把调试工具「贴」进去,和目标共享进程/网络视图,不必改动或重发业务镜像——这是排查现代镜像的标准姿势。写你的第一个 Operator:从看懂调谐到实现调谐
这是「从会用到精通」最能体现功力的一跃:把第一册那句「声明式 + 控制器调谐」从看懂,变成亲手写一个。这一章不教你写完整代码,而是讲清写一个 controller 必须懂的机制和坑——不然写出来的东西会有一堆隐蔽 bug。
Operator = CRD(自定义资源)+ 自定义控制器。CRD 让 K8s API 认识一种新资源(如 kind: RedisCluster),控制器则 7×24 地把「实际状态」推向这个资源声明的「期望状态」——把资深运维的操作手册写成了代码。
StatefulSet 只给「稳定身份」,不懂业务:主从切换、备份恢复、扩容重分片这些领域知识它管不了。Operator 就是来装这些「懂业务的自动化」。
90% 的需求用现成的就行(Prometheus Operator、Redis Operator…);只有当你有独特的领域运维逻辑、现成工具满足不了时,才自己写。动手前先问一句:能不能用 Helm / 现成 Operator 解决?
13.1 写 controller 必须懂的 6 个机制(都是坑)
spec(我要 3 个副本),控制器把观察到的实际情况写回 status(当前就绪 2 个、conditions…)。spec/status 分离是 K8s API 的通用范式,也是别人 kubectl get 你的 CR 时看到的信息来源。func Reconcile(req) {
cr := get(req) // 读期望状态(我的 CRD 对象)
if cr.deleting { // 处理删除:先跑 finalizer 清理外部资源
cleanupExternal(); removeFinalizer(cr); return
}
want := cr.spec.replicas // 期望
have := countPods(cr) // 实际(从 Lister 缓存读)
if have < want { create(want - have) } // 缺就补
if have > want { delete(have - want) } // 多就删
cr.status.ready = have; update(cr.status) // 把实际写回 status
// 返回后,任何新变更会再次触发 Reconcile —— 这就是调谐循环
}第 13 章自测
实现调谐进阶轴一览:成熟专家的五条路
这一章只求「知道有这条轴、它解决什么问题」,为你入职后按方向深入留路标。你不需要全精通——不同角色(平台 / 业务 / SRE / 安全)往不同轴使劲。
🔐 安全深入
解决:光有 RBAC 和 NetworkPolicy 不够——镜像可信吗?谁能创建高危资源?密钥怎么管?
供应链cosign 签名SBOMTrivy 扫描OPA/GatekeeperKyvernoPod SecurityVault/External SecretsFalco
📊 可观测性深入
解决:几百个易逝的 Pod,出了问题去哪看、怎么提前告警、怎么定义「多稳算稳」?
PrometheusPromQLGrafanaAlertmanagerThanos/MimirLoki/ELKOpenTelemetryJaeger/TempoeBPF/HubbleSLO/错误预算
🕸 网络深入
解决:Pod 网络到底怎么通的?kube-proxy 在干嘛?七层治理谁来做?
CNICalico/Cilium(eBPF)kube-proxy iptables/IPVSCoreDNSGateway APIIstio/Linkerd
🛰 集群运维 / 规模化
解决:集群本身怎么搭、怎么升级不出事、怎么备份灾备、怎么控成本、多集群怎么管?
kubeadm集群升级/版本 skewetcd 备份VeleroCluster Autoscaler/KarpenterKubecostCluster API/Karmada
🏗 平台工程
解决:把上面这些能力包装成「业务团队自助使用」的内部平台,让开发者不必懂 K8s 细节也能上线。
Argo 全家桶CrossplaneKEDA(事件驱动扩缩)Knative(Serverless)Backstage/IDP
14.1 按你的角色指路
| 你进的组 | 日常重心 | 优先补哪几轴 |
|---|---|---|
| 平台 / 基础架构组 | 搭&运维给别人用的集群、写 Operator、管 CI/CD 底座 | 第 13 章 Operator + 网络深入 + 集群运维 + 平台工程 |
| 业务 / 应用组 | 把自己的服务配好、部署上去、排自己的故障 | 稳定篇(1-5)+ 工具篇(9-12)练熟即可 |
| SRE / 稳定性组 | 盯监控、容量、on-call、事故复盘 | 可观测性深入 + 集群运维 + 稳定篇 |
| 安全组 | 准入策略、供应链、权限治理 | 隔离篇(6-8)+ 安全深入这一整轴 |
🎓 两册读完,你手里有什么
第一册给你一张原理地图(K8s 怎么转),第二册给你一套干活的手艺(怎么配稳、怎么协作、怎么用工具、怎么扩展)。而贯穿两册的,始终是同一个思想——声明式 + 调谐:从 Deployment 到 HPA,从 GitOps 到你自己写的 Operator,都是它。
入职前 30 天的两个动作:① 在 kind/minikube 上把稳定篇每个配置项都亲手配一遍、故意搞坏再修好;② 上班第一周画出两张图——「一个外部请求怎么进到你的 Pod」和「一次代码提交怎么变成生产上的新版本」。画得出来,你就真的上手了。