Skip to content

Kubernetes 入门 ​

S4-027选修高级约 90 分钟JDK 21企业开发进阶 · 容器与编排

1. 本节定位 ​

《Docker Compose 与多容器编排》讲 Compose(单机编排);本节讲 K8s 的灵魂:声明式 API + 控制器循环。

★ 一句话 ✓✓:

text
你只描述「期望几个副本」,剩下的由控制器不停对比「期望 vs 实际」并补齐 ✓

★ 本节的实测把三件事跑出来 ✓✓:

text
① 自愈:删掉 1 个 Pod → 控制器自动补到 3 个 ✓✓
② 扩缩容:**全程没有一条命令是「创建 Pod」**(累计创建 6 个 / 删除 3 个)✓✓
③ 滚动更新:可用副本数变化 [3, 3, 3, 3, 3],**最低可用数 3** ✓✓
   对比"一次性重启":**最低可用数 0** ✗✗(服务中断)

2. 学习目标 ​

  • 能说清声明式与命令式的区别,以及声明式的三个好处 ★★★;
  • 能描述控制器循环(对比期望与实际 → 采取行动) ★★★;
  • 知道"不要手改 Pod,要改 Deployment" ★★★;
  • 能说清滚动更新与一次性重启的差别,以及两个参数的含义 ★★★;
  • 知道三种探针的分工与"liveness 查依赖"的灾难 ★★★;
  • 能按顺序排障(get → describe → logs → events) ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看"一次性重启"的代价 ✗✗:

text
Recreate(停掉全部,再起新的):
  开始:可用 3 个 ✓
  停掉全部 → **可用 0 个** ✗✗(用户在这段时间里全部请求失败)
  新的全部起来 → 可用 3 个

RollingUpdate(本节的实测):可用副本数变化 [3, 3, 3, 3, 3]
  → **最低可用数 3** ✓✓(用户完全无感)

★ 这就是"滚动更新是默认策略"的原因 ✓✓

5. 核心概念 ​

5.1 声明式 vs 命令式 ★★★ ​

★ 两种思路的对比 ✓✓:

命令式(docker run 的思路)声明式(K8s 的思路)
你告诉系统"做什么步骤""最终要什么状态" ✓✓
例子docker run -d --name app1 ...spec: { replicas: 3 }
挂了怎么办没人管(要自己写脚本补)✗✗控制器自动补齐 ✓✓

★ 声明式的三个好处 ✓✓:

#好处说明
①自愈 ★★★控制器不停对齐,挂了自己补 ✓✓
②可重复 ★★★同一份 YAML 在任何集群结果一致 ✓✓
③可版本化 ★★★YAML 进 Git → 配置有了审计与回滚 ✓✓

★ 一条认识 ✓✓:K8s 里几乎所有东西都是"声明 + 控制器" ✓: Deployment(要几个副本)、Service(要暴露哪些 Pod)、HPA(要几个副本才够用)、 Ingress(要哪些路由)全都是 ✓

5.2 控制器循环:自愈与扩缩容 ★★★ ​

实测 ✓✓:

text
  期望 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 常常"不生效"——它被重建了)✓

★ 控制器循环的三个要素 ✓✓:

text
① 期望状态(spec.replicas = 3)
② 实际状态(当前有几个 Pod 活着)
③ 调和动作(reconcile:少了就建、多了就删)✓✓

5.3 滚动更新:可用数 0 与可用数 3 的差别 ★★★ ​

实测对比 ✓✓:

策略可用副本数曲线最低可用数
Recreate ✗✗3 → 0 → 30 ✗✗(服务中断)
RollingUpdate ✓✓[3, 3, 3, 3, 3]3 ✓✓(用户无感)

★ 两个参数的含义(记住这两个就够) ✓✓:

参数含义调大的后果
maxUnavailable ★★★更新时最多几个不可用更新快,但可用数下降 ⚠️
maxSurge ★★★更新时最多超出期望几个可用数不掉,但多占资源 ✓

★★★ 一条最容易出事故的配置 ✓✓:

maxUnavailable 与 maxSurge 不能同时为 0 ✗✗(K8s 会拒绝)——

因为那意味着"不能删旧的、也不能建新的",更新永远无法推进 ✗

推荐:单副本服务设 maxUnavailable=0, maxSurge=1 ✓✓

否则更新期间会有几秒到几十秒完全不可用 ✗✗

★ 还有两个必须配的(否则滚动更新会"假成功") ✓✓:

配置不配的后果
readinessProbe ★★★新 Pod 还没就绪就被算作可用 → 请求失败 ✗✗
preStop + 优雅停机 ★★★旧 Pod 被 kill 时正在处理的请求丢失 ✗✗

5.4 三种探针:配错就是"反复重启" ★★★ ​

★ 三种探针的分工 ✓✓:

探针检查什么失败后的动作
livenessProbe ★★★进程还活着吗重启容器 ✗
readinessProbe ★★★能接流量了吗从 Service 摘除(不重启)✓
startupProbe ★★启动完成了吗重启容器(仅启动期)

★★★ 最经典的事故(真实发生过无数次) ✗✗:

text
Spring Boot 启动要 40 秒,而 livenessProbe 配了 initialDelaySeconds: 10
→ 10 秒时探针失败 → **容器被杀** → 重启 → 又是 10 秒失败 → **无限重启** ✗✗
表现:Pod 一直在 CrashLoopBackOff,日志看起来"刚启动就被杀" ✗

★ 三种正确做法(任选,推荐前两个) ✓✓:

做法说明
用 startupProbe 保护启动期 ★★★启动完成前不跑 liveness ✓✓
liveness 只做"活着"的检查 ★★★别在 liveness 里查数据库/依赖 ✓✓
把 initialDelaySeconds 调大治标(启动变慢又会挂)⚠️

★★ 另一条同样重要的原则 ✓✓:

liveness 要"简单"(只看进程),readiness 可以"严格"(查依赖) ✓✓

· liveness 查数据库 → 数据库抖动会让所有 Pod 一起重启 ✗✗(雪崩)

· readiness 查数据库 → 正确的行为:摘除实例,等数据库恢复 ✓✓

(这与 《Sentinel 限流与熔断》 的"雪崩"是同一个道理)✓

5.5 排障:四个命令与三种典型状态 ★★★ ​

★ 排障顺序 ✓✓:

text
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% 日常) ✓✓:

bash
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   # ★ 最近发生了什么

5.6 K8s 的核心对象速查 ★★ ​

对象解决什么
Pod ★★最小调度单位(一个或多个容器)
Deployment ★★★管理副本数与滚动更新(本节的控制器)✓✓
Service ★★★给一组 Pod 一个稳定的访问入口(Pod 的 IP 会变)✓✓
Ingress ★★七层路由(域名/路径 → Service)✓
ConfigMap / Secret ★★★配置与密钥注入(不写进镜像)✓✓
HPA ★★按 CPU/QPS 自动扩缩容 ✓

★★ 一条容易被忽略的 ✓✓:Pod 的 IP 是会变的(重建就换)—— 所以不能把 Pod IP 写进配置 ✗✗ —— 要用 Service 的名字(它相当于 《Nacos 注册与配置中心》 里的服务发现)✓✓

6. 递进示例 ​

6.1 最小示例:一个 Deployment ​

yaml
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 }    # ★ 不配会拖垮同节点其他 Pod

6.2 业务示例:滚动更新与回滚 ​

bash
kubectl 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 最值钱的功能之一 ✓✓ —— 它让"发布出问题"从"通宵排查"变成"按一下回滚" ✓✓

6.3 反例:给 Pod 配 liveness 查数据库 ​

yaml
livenessProbe:
  httpGet: { path: /health/db, port: 8080 }   # ✗ 数据库抖动 → 所有 Pod 一起重启

7. 完整可运行代码与实测 ​

代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/container/K8sControllerLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.container.K8sControllerLesson

实测输出(关键部分) ✓:

text
===== 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] ✓✓

8. Debug 观察点 ​

  1. 把 replicas 从 3 改成 0 再改回来:观察 Pod 全部删除与重建 ✓;
  2. 删除一个 Pod 并 kubectl get pods -w:观察新 Pod 在几秒内出现 ✓✓;
  3. 给 Deployment 配 maxUnavailable: 100%:观察更新期间服务中断 ✗✗;
  4. 故意写错镜像名:观察 ImagePullBackOff 与 describe 里的原因 ✓✓;
  5. 把 liveness 的 initialDelaySeconds 改小:观察 CrashLoopBackOff ✗✗。

9. 常见错误 ​

#错误说明
1手改 Pod ✗✗会被控制器纠正回来 ✓✓
2liveness 里查数据库/依赖 ✗✗依赖抖动 → 全量重启 ✓✓
3不配 readinessProbe ✗✗滚动更新会失败请求 ✓
4maxUnavailable 与 maxSurge 都为 0 ✗✗更新卡死 ✓
5用 latest 标签 ✗✗不可重复(《Docker 基础与镜像容器》)✓
6不配 requests / limits ✗✗调度失衡或拖垮同节点 ✓
7把 Pod IP 写进配置 ✗✗重建就变 ✓✓

10. 动手实验 ​

目标:亲手看到"自愈"与"滚动更新"。

步骤:

  1. 跑 K8sControllerLesson,记录可用数曲线 ✓✓;
  2. 在本地用 kind / minikube 起一个集群(或用在线 Playground)★;
  3. 部署一个 3 副本的 Deployment,删 Pod 观察自愈 ✓✓;
  4. 更新镜像版本并 kubectl rollout status,观察滚动过程 ✓;
  5. 把新版本改坏,kubectl rollout undo 回滚 ★★★。

验收方式:能说出"你的服务该配几个副本、探针怎么配、发布怎么回滚"。

扩展任务:设计一个"零中断发布"方案:

  • 3 副本 + maxUnavailable=0, maxSurge=1 ✓✓;
  • readinessProbe 确保新 Pod 就绪才接流量 ✓;
  • preStop 让旧 Pod 先摘除再退出(sleep 10 给调用方刷新时间)✓✓;
  • 配 PodDisruptionBudget(防止节点维护时同时驱逐太多)★★

11. 练习题 ​

  1. 判断:K8s 里可以手动删除 Pod 来"减一个副本"。(对 / 错)
  2. 填空:声明式的三个好处是 、、______。
  3. 简答:为什么"liveness 探针里查数据库"是危险的?
  4. (配置阅读)
yaml
spec:
  replicas: 1
  strategy:
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 0

单副本服务这样配会发生什么? 5. (排障)Pod 处于 CrashLoopBackOff,你的排查顺序是什么?

参考答案(要点)
  1. 错。控制器会立刻补一个回来 ✗ —— 要减副本应该改 replicas ✓✓★
  2. 自愈;可重复;可版本化 ✓
  3. 因为依赖抖动会让所有 Pod 同时重启 ✗✗ —— 原本只是"数据库暂时不可用",结果变成"服务全挂" ✓ —— 正确做法:readiness 查依赖(摘除实例),liveness 只看进程 ✓✓★
  4. 更新期间服务会完全中断 ✗✗ —— maxUnavailable: 1 意味着"可以先停掉那唯一的 Pod",maxSurge: 0 意味着"不能先建新的" → 可用数从 1 掉到 0 ✗✓ —— 正确配法:maxUnavailable: 0, maxSurge: 1 ✓✓
  5. kubectl describe pod <name> 看 Events ✓✓ → kubectl logs <pod> --previous(看上次崩溃前的日志)✓✓ → 常见原因:应用启动失败(配置错/依赖连不上)、探针配错(initialDelaySeconds 太小)、 资源不够(OOMKilled)✓

12. 掌握标准 ​

  • [ ] 能说清声明式与命令式的区别与三个好处 ★★★;
  • [ ] 能描述控制器循环的三要素 ★★★;
  • [ ] 知道"改 Deployment,不要改 Pod" ★★★;
  • [ ] 能说清滚动更新的两个参数与推荐配置 ★★★;
  • [ ] 知道三种探针的分工与"liveness 查依赖"的灾难 ★★★;
  • [ ] 能按四步顺序排障,并认出三种典型状态 ★★★;
  • [ ] 知道 kubectl rollout undo 的价值 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《CI/CD 流水线设计》:让"发现问题"的时间尽可能短。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引