主题
界面
主题
《Docker Compose 与多容器编排》讲 Compose(单机编排);本节讲 K8s 的灵魂:声明式 API + 控制器循环。
★ 一句话 ✓✓:
你只描述「期望几个副本」,剩下的由控制器不停对比「期望 vs 实际」并补齐 ✓★ 本节的实测把三件事跑出来 ✓✓:
① 自愈:删掉 1 个 Pod → 控制器自动补到 3 个 ✓✓
② 扩缩容:**全程没有一条命令是「创建 Pod」**(累计创建 6 个 / 删除 3 个)✓✓
③ 滚动更新:可用副本数变化 [3, 3, 3, 3, 3],**最低可用数 3** ✓✓
对比"一次性重启":**最低可用数 0** ✗✗(服务中断)先看"一次性重启"的代价 ✗✗:
Recreate(停掉全部,再起新的):
开始:可用 3 个 ✓
停掉全部 → **可用 0 个** ✗✗(用户在这段时间里全部请求失败)
新的全部起来 → 可用 3 个
RollingUpdate(本节的实测):可用副本数变化 [3, 3, 3, 3, 3]
→ **最低可用数 3** ✓✓(用户完全无感)★ 这就是"滚动更新是默认策略"的原因 ✓✓
★ 两种思路的对比 ✓✓:
命令式(docker run 的思路) | 声明式(K8s 的思路) | |
|---|---|---|
| 你告诉系统 | "做什么步骤" | "最终要什么状态" ✓✓ |
| 例子 | docker run -d --name app1 ... | spec: { replicas: 3 } |
| 挂了怎么办 | 没人管(要自己写脚本补)✗✗ | 控制器自动补齐 ✓✓ |
★ 声明式的三个好处 ✓✓:
| # | 好处 | 说明 |
|---|---|---|
| ① | 自愈 ★★★ | 控制器不停对齐,挂了自己补 ✓✓ |
| ② | 可重复 ★★★ | 同一份 YAML 在任何集群结果一致 ✓✓ |
| ③ | 可版本化 ★★★ | YAML 进 Git → 配置有了审计与回滚 ✓✓ |
★ 一条认识 ✓✓:K8s 里几乎所有东西都是"声明 + 控制器" ✓: Deployment(要几个副本)、Service(要暴露哪些 Pod)、HPA(要几个副本才够用)、 Ingress(要哪些路由)全都是 ✓
实测 ✓✓:
期望 3 个副本 → 实际:[pod-1(v1,就绪) pod-2(v1,就绪) pod-3(v1,就绪)]
手动杀掉 1 个 Pod(模拟节点故障/进程崩溃):
删除后实际存活:2 个 ✗
控制器 reconcile 后:3 个 → [pod-1, pod-3, pod-4] ✓✓
(累计创建 4 个,删除 0 个)
把期望改成 5(扩容)→ reconcile 后:5 个
把期望改成 2(缩容)→ reconcile 后:2 个
累计:创建 6 个 / 删除 3 个 —— **全过程没有一条命令是「创建 Pod」** ✓✓★★★ 这条最容易被忽略的经验 ✓✓:
在 K8s 里不要手改 Pod ✗✗
· 你创建/删除的 Pod 会被控制器"纠正回来"(就是本实验做的事)✗
· 所以:改 Deployment 的期望值,而不是改 Pod ✓✓
· (这也是为什么
kubectl edit pod常常"不生效"——它被重建了)✓
★ 控制器循环的三个要素 ✓✓:
① 期望状态(spec.replicas = 3)
② 实际状态(当前有几个 Pod 活着)
③ 调和动作(reconcile:少了就建、多了就删)✓✓实测对比 ✓✓:
| 策略 | 可用副本数曲线 | 最低可用数 |
|---|---|---|
| Recreate ✗✗ | 3 → 0 → 3 | 0 ✗✗(服务中断) |
| RollingUpdate ✓✓ | [3, 3, 3, 3, 3] | 3 ✓✓(用户无感) |
★ 两个参数的含义(记住这两个就够) ✓✓:
| 参数 | 含义 | 调大的后果 |
|---|---|---|
maxUnavailable ★★★ | 更新时最多几个不可用 | 更新快,但可用数下降 ⚠️ |
maxSurge ★★★ | 更新时最多超出期望几个 | 可用数不掉,但多占资源 ✓ |
★★★ 一条最容易出事故的配置 ✓✓:
maxUnavailable与maxSurge不能同时为 0 ✗✗(K8s 会拒绝)——因为那意味着"不能删旧的、也不能建新的",更新永远无法推进 ✗
推荐:单副本服务设
maxUnavailable=0, maxSurge=1✓✓否则更新期间会有几秒到几十秒完全不可用 ✗✗
★ 还有两个必须配的(否则滚动更新会"假成功") ✓✓:
| 配置 | 不配的后果 |
|---|---|
readinessProbe ★★★ | 新 Pod 还没就绪就被算作可用 → 请求失败 ✗✗ |
preStop + 优雅停机 ★★★ | 旧 Pod 被 kill 时正在处理的请求丢失 ✗✗ |
★ 三种探针的分工 ✓✓:
| 探针 | 检查什么 | 失败后的动作 |
|---|---|---|
livenessProbe ★★★ | 进程还活着吗 | 重启容器 ✗ |
readinessProbe ★★★ | 能接流量了吗 | 从 Service 摘除(不重启)✓ |
startupProbe ★★ | 启动完成了吗 | 重启容器(仅启动期) |
★★★ 最经典的事故(真实发生过无数次) ✗✗:
Spring Boot 启动要 40 秒,而 livenessProbe 配了 initialDelaySeconds: 10
→ 10 秒时探针失败 → **容器被杀** → 重启 → 又是 10 秒失败 → **无限重启** ✗✗
表现:Pod 一直在 CrashLoopBackOff,日志看起来"刚启动就被杀" ✗★ 三种正确做法(任选,推荐前两个) ✓✓:
| 做法 | 说明 |
|---|---|
用 startupProbe 保护启动期 ★★★ | 启动完成前不跑 liveness ✓✓ |
| liveness 只做"活着"的检查 ★★★ | 别在 liveness 里查数据库/依赖 ✓✓ |
把 initialDelaySeconds 调大 | 治标(启动变慢又会挂)⚠️ |
★★ 另一条同样重要的原则 ✓✓:
liveness 要"简单"(只看进程),readiness 可以"严格"(查依赖) ✓✓
· liveness 查数据库 → 数据库抖动会让所有 Pod 一起重启 ✗✗(雪崩)
· readiness 查数据库 → 正确的行为:摘除实例,等数据库恢复 ✓✓
(这与 《Sentinel 限流与熔断》 的"雪崩"是同一个道理)✓
★ 排障顺序 ✓✓:
1. kubectl get pods → **是什么状态**(Pending / CrashLoopBackOff / Running 但没就绪)
2. kubectl describe pod → **为什么**(看 Events:拉镜像失败?探针失败?资源不足?)✓✓
3. kubectl logs → **应用自己怎么说** ✓
4. kubectl get events → **集群层面发生了什么**(调度失败、驱逐)✓✓★ 三种典型状态对应的病因 ✓✓:
| 状态 | 常见原因 |
|---|---|
Pending ★★ | 资源不足 / 没有可用节点 / PVC 没绑定 ✓ |
ImagePullBackOff ★★★ | 镜像名错 / 私有仓库没配 Secret ✓✓ |
CrashLoopBackOff ★★★ | 应用启动失败 / 探针配错(5.4) ✓✓ |
★ 必会的命令(八个覆盖 90% 日常) ✓✓:
kubectl get pods -w # 看状态(-w 持续观察)
kubectl describe pod <name> # ★ 排障第一命令
kubectl logs -f <pod> # 看日志
kubectl exec -it <pod> -- sh # 进容器
kubectl apply -f deploy.yaml # 声明式应用
kubectl rollout status deploy/xxx # 看滚动更新进度
kubectl rollout undo deploy/xxx # ★ 回滚
kubectl get events --sort-by=.lastTimestamp # ★ 最近发生了什么| 对象 | 解决什么 |
|---|---|
| Pod ★★ | 最小调度单位(一个或多个容器) |
| Deployment ★★★ | 管理副本数与滚动更新(本节的控制器)✓✓ |
| Service ★★★ | 给一组 Pod 一个稳定的访问入口(Pod 的 IP 会变)✓✓ |
| Ingress ★★ | 七层路由(域名/路径 → Service)✓ |
| ConfigMap / Secret ★★★ | 配置与密钥注入(不写进镜像)✓✓ |
| HPA ★★ | 按 CPU/QPS 自动扩缩容 ✓ |
★★ 一条容易被忽略的 ✓✓:Pod 的 IP 是会变的(重建就换)—— 所以不能把 Pod IP 写进配置 ✗✗ —— 要用 Service 的名字(它相当于 《Nacos 注册与配置中心》 里的服务发现)✓✓
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3 # ★ 期望状态(控制器会一直对齐它)
strategy:
rollingUpdate:
maxUnavailable: 0 # ★ 单副本服务的关键配置
maxSurge: 1
template:
spec:
containers:
- name: app
image: registry/order-service:1.2.3 # ★ 固定版本,不用 latest
readinessProbe: # ★ 就绪探针(能接流量吗)
httpGet: { path: /actuator/health/readiness, port: 8080 }
initialDelaySeconds: 10
livenessProbe: # ★ 存活探针(只看进程)
httpGet: { path: /actuator/health/liveness, port: 8080 }
initialDelaySeconds: 40
resources:
requests: { cpu: 200m, memory: 512Mi } # ★ 不配会被调度到爆满的节点
limits: { cpu: 1, memory: 1Gi } # ★ 不配会拖垮同节点其他 Podkubectl set image deploy/order-service app=registry/order-service:1.2.4
kubectl rollout status deploy/order-service # ★ 盯着更新进度
# 发现问题:
kubectl rollout undo deploy/order-service # ★ 一秒回滚★★ 这条命令是 K8s 最值钱的功能之一 ✓✓ —— 它让"发布出问题"从"通宵排查"变成"按一下回滚" ✓✓
livenessProbe:
httpGet: { path: /health/db, port: 8080 } # ✗ 数据库抖动 → 所有 Pod 一起重启代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/container/K8sControllerLesson.java
运行:
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.container.K8sControllerLesson实测输出(关键部分) ✓:
===== 2. 控制器循环 =====
期望 3 个副本 → 实际:[pod-1(v1,就绪) pod-2(v1,就绪) pod-3(v1,就绪)]
手动杀掉 1 个 Pod:
删除后实际存活:2 个 ✗
控制器 reconcile 后:3 个 → [pod-1, pod-3, pod-4] ✓✓(累计创建 4 个,删除 0 个)
把期望改成 5(扩容)→ reconcile 后:5 个
把期望改成 2(缩容)→ reconcile 后:2 个
累计:创建 6 个 / 删除 3 个 —— **全过程没有一条命令是「创建 Pod」** ✓✓
===== 3. 滚动更新 vs 一次性重启 =====
① Recreate:停掉全部 → **可用 0 个** ✗✗
**最低可用数:0** —— 用户在这段时间里全部请求失败
② RollingUpdate:可用副本数变化 [3, 3, 3, 3, 3]
**最低可用数:3** ✓✓(始终有实例在服务)★ 三条可落地的结论 ✓:
| # | 结论 | 依据 |
|---|---|---|
| ① | 声明式 + 控制器 = 自愈 | 删了就补 ✓✓ |
| ② | 改 Deployment,不要改 Pod | 全过程没有"创建 Pod"的命令 ✓ |
| ③ | 滚动更新把"可用数 0"变成"可用数 3" | [3,3,3,3,3] ✓✓ |
replicas 从 3 改成 0 再改回来:观察 Pod 全部删除与重建 ✓;kubectl get pods -w:观察新 Pod 在几秒内出现 ✓✓;maxUnavailable: 100%:观察更新期间服务中断 ✗✗;ImagePullBackOff 与 describe 里的原因 ✓✓;initialDelaySeconds 改小:观察 CrashLoopBackOff ✗✗。| # | 错误 | 说明 |
|---|---|---|
| 1 | 手改 Pod ✗✗ | 会被控制器纠正回来 ✓✓ |
| 2 | liveness 里查数据库/依赖 ✗✗ | 依赖抖动 → 全量重启 ✓✓ |
| 3 | 不配 readinessProbe ✗✗ | 滚动更新会失败请求 ✓ |
| 4 | maxUnavailable 与 maxSurge 都为 0 ✗✗ | 更新卡死 ✓ |
| 5 | 用 latest 标签 ✗✗ | 不可重复(《Docker 基础与镜像容器》)✓ |
| 6 | 不配 requests / limits ✗✗ | 调度失衡或拖垮同节点 ✓ |
| 7 | 把 Pod IP 写进配置 ✗✗ | 重建就变 ✓✓ |
目标:亲手看到"自愈"与"滚动更新"。
步骤:
K8sControllerLesson,记录可用数曲线 ✓✓;kubectl rollout status,观察滚动过程 ✓;kubectl rollout undo 回滚 ★★★。验收方式:能说出"你的服务该配几个副本、探针怎么配、发布怎么回滚"。
扩展任务:设计一个"零中断发布"方案:
maxUnavailable=0, maxSurge=1 ✓✓;readinessProbe 确保新 Pod 就绪才接流量 ✓;preStop 让旧 Pod 先摘除再退出(sleep 10 给调用方刷新时间)✓✓;PodDisruptionBudget(防止节点维护时同时驱逐太多)★★spec:
replicas: 1
strategy:
rollingUpdate:
maxUnavailable: 1
maxSurge: 0单副本服务这样配会发生什么? 5. (排障)Pod 处于 CrashLoopBackOff,你的排查顺序是什么?
replicas ✓✓★readiness 查依赖(摘除实例),liveness 只看进程 ✓✓★maxUnavailable: 1 意味着"可以先停掉那唯一的 Pod",maxSurge: 0 意味着"不能先建新的" → 可用数从 1 掉到 0 ✗✓ —— 正确配法:maxUnavailable: 0, maxSurge: 1 ✓✓kubectl describe pod <name> 看 Events ✓✓ → kubectl logs <pod> --previous(看上次崩溃前的日志)✓✓ → 常见原因:应用启动失败(配置错/依赖连不上)、探针配错(initialDelaySeconds 太小)、 资源不够(OOMKilled)✓kubectl rollout undo 的价值 ★★★。《CI/CD 流水线设计》:让"发现问题"的时间尽可能短。