---
url: /courses/cloud-platform-build-management/lab-16/index.md
---
# 实训 16：Kubernetes 滚动发布、故障与回滚

## 1. 任务情境

课程站点当前运行 v1。你需要使用 Deployment 滚动发布 v2，记录修订历史并验证 Service 页面。随后把错误镜像作为新修订提交，保留 rollout 超时、ImagePullBackOff 和事件证据；确认 v2 仍在服务后，明确回滚到最后一个已验证修订并完成同路回归。

## 2. 环境与边界

| 项目 | 课堂要求 |
|---|---|
| 控制端 | WSL 或教师机本地终端 |
| 集群 | 教师指定的第 16 次课隔离 K3s/k3d |
| 命名空间 | `cloud-course-16` |
| Deployment/Service | `lesson16-web` |
| 正确镜像 | `nginx:1.27-alpine` |
| 故障镜像 | `nginx:lesson16-missing`，仅用于本课隔离环境 |
| 允许变更 | 本课清单、修订、错误镜像和本课回滚 |
| 禁止操作 | 其他命名空间、系统组件、共享工作负载、全局删除 |

报告不得包含 kubeconfig、令牌、证书、完整地址和其他命名空间数据。

## 3. 准备实验目录

```bash
[WSL] mkdir -p "$HOME/cloud-course/lab-16"
```

创建个人目录。

```bash
[WSL] cp -R starter/. "$HOME/cloud-course/lab-16/"
```

复制清单、验收和清理脚本。

```bash
[WSL] cd "$HOME/cloud-course/lab-16"
```

进入本课目录。

起始文件：

```txt
lab-16/
├── manifests/
│   ├── namespace.yaml
│   ├── service.yaml
│   ├── configmap-v1.yaml
│   ├── configmap-v2.yaml
│   ├── deployment-v1.yaml
│   └── deployment-v2.yaml
├── verify.sh
└── cleanup.sh
```

## 4. 步骤一：上下文与清单预检

```bash
[WSL] kubectl config current-context
```

与教师公布的第 16 次课上下文逐字核对。

```bash
[WSL] kubectl get nodes
```

确认至少一个节点 Ready。

```bash
[WSL] kubectl get namespace cloud-course-16
```

首次执行应为 NotFound。若已经存在，先联系教师，不接管未知资源。

```bash
[WSL] kubectl apply --dry-run=client \
  -f manifests/namespace.yaml \
  -f manifests/configmap-v1.yaml \
  -f manifests/service.yaml \
  -f manifests/deployment-v1.yaml
```

预检 v1 对象。

```bash
[WSL] kubectl diff -f manifests/deployment-v1.yaml
```

在 Namespace 创建后，可用 diff 预览 Deployment 与当前对象的差异。diff 返回 1 通常表示存在差异，不等于命令故障。

检查点：

* replicas 为 3；
* `maxUnavailable: 0`、`maxSurge: 1`；
* Pod 标签为 v1；
* ConfigMap 引用为 v1；
* Service 选择 app 标签，不固定版本；
* 探针和资源限制存在。

## 5. 步骤二：发布并验收 v1

```bash
[WSL] kubectl apply \
  -f manifests/namespace.yaml \
  -f manifests/configmap-v1.yaml \
  -f manifests/service.yaml \
  -f manifests/deployment-v1.yaml
```

创建 v1 基线。

```bash
[WSL] kubectl rollout status deployment/lesson16-web \
  -n cloud-course-16 --timeout=120s
```

等待三个副本就绪。

```bash
[WSL] kubectl rollout history deployment/lesson16-web -n cloud-course-16
```

预期 revision 1 为 `lesson16 release v1`。

```bash
[WSL] kubectl exec -n cloud-course-16 deploy/lesson16-web -- \
  wget -q -O - http://lesson16-web | grep 'release='
```

预期 `release=v1`。

## 6. 步骤三：滚动发布 v2

```bash
[WSL] kubectl apply --dry-run=client \
  -f manifests/configmap-v2.yaml \
  -f manifests/deployment-v2.yaml
```

先检查 v2 对象能够生成。

```bash
[WSL] kubectl apply \
  -f manifests/configmap-v2.yaml \
  -f manifests/deployment-v2.yaml
```

提交 v2 内容与 Pod 模板。

```bash
[WSL] kubectl rollout status deployment/lesson16-web \
  -n cloud-course-16 --timeout=120s
```

记录新旧副本交接过程和成功行。

```bash
[WSL] kubectl rollout history deployment/lesson16-web -n cloud-course-16
```

预期同时出现 v1 与 v2。

```bash
[WSL] kubectl get deployment,replicaset,pods \
  -n cloud-course-16 -l app.kubernetes.io/name=lesson16-web -o wide
```

记录 Deployment 3/3、新 ReplicaSet 三个副本和旧 ReplicaSet 0 个副本。

```bash
[WSL] kubectl exec -n cloud-course-16 deploy/lesson16-web -- \
  wget -q -O - http://lesson16-web | grep 'release='
```

预期 `release=v2`。

截图 1 包含 rollout success、v1/v2 历史、3/3 和 v2 页面。

## 7. 步骤四：注入错误镜像修订

故障前记录：

```bash
[WSL] kubectl get deployment lesson16-web -n cloud-course-16 \
  -o jsonpath='image={.spec.template.spec.containers[0].image}{" version="}{.spec.template.metadata.labels.app\.kubernetes\.io/version}{"\n"}'
```

预期正确镜像和 v2 标签。

在同一次 patch 中写入故障说明和错误镜像：

```bash
[WSL] kubectl patch deployment lesson16-web -n cloud-course-16 \
  --type=strategic \
  -p '{"metadata":{"annotations":{"kubernetes.io/change-cause":"lesson16 fault invalid image"}},"spec":{"template":{"spec":{"containers":[{"name":"web","image":"nginx:lesson16-missing"}]}}}}'
```

不要把“先 annotate、再 set image”拆成两步，否则可能让当前修订的 change-cause 变得难以解释。

```bash
[WSL] kubectl rollout status deployment/lesson16-web \
  -n cloud-course-16 --timeout=15s
```

预期超时，退出码非 0。

```bash
[WSL] kubectl get deployment,pods \
  -n cloud-course-16 -l app.kubernetes.io/name=lesson16-web -o wide
```

预期三个旧 Pod 仍 1/1 Running，一个新 Pod 先出现 `ErrImagePull`，重试后进入 `ImagePullBackOff`；Deployment READY 3/3、UP-TO-DATE 1、AVAILABLE 3。

```bash
[WSL] fault_pod="$(kubectl get pods -n cloud-course-16 \
  -l app.kubernetes.io/name=lesson16-web \
  -o jsonpath='{range .items[?(@.status.containerStatuses[0].ready==false)]}{.metadata.name}{"\n"}{end}' \
  | head -n 1)"
```

只从本课标签中取得尚未就绪的故障 Pod，不依赖它当前处于哪一个镜像拉取重试阶段。

```bash
[WSL] kubectl describe pod -n cloud-course-16 "$fault_pod"
```

在 Events 中寻找 Failed、not found、ErrImagePull 和 ImagePullBackOff。

```bash
[WSL] kubectl exec -n cloud-course-16 deploy/lesson16-web -- \
  wget -q -O - http://lesson16-web | grep 'release='
```

预期仍返回 v2，说明旧副本暂时保住用户路径；不能据此宣告故障发布成功。

截图 2 包含超时、坏 Pod、事件和仍返回 v2 的用户路径。

## 8. 步骤五：选择并执行回滚

```bash
[WSL] kubectl rollout history deployment/lesson16-web -n cloud-course-16
```

确认修订 2 是最后一个已验证的 v2，修订 3 是错误镜像。

```bash
[WSL] kubectl rollout undo deployment/lesson16-web \
  -n cloud-course-16 --to-revision=2
```

明确回到修订 2。

```bash
[WSL] kubectl rollout status deployment/lesson16-web \
  -n cloud-course-16 --timeout=120s
```

等待回滚完成。Deployment 达到 3/3 后，故障 Pod 还可能短暂处于退出过程；继续观察，直到本课 Pod 全部为 `1/1 Running` 再运行最终验收。

```bash
[WSL] kubectl rollout history deployment/lesson16-web -n cloud-course-16
```

观察 v2 模板成为新的修订 4。修订号前进是正常现象。

```bash
[WSL] kubectl get deployment lesson16-web -n cloud-course-16 \
  -o jsonpath='image={.spec.template.spec.containers[0].image}{" version="}{.spec.template.metadata.labels.app\.kubernetes\.io/version}{"\n"}'
```

预期 `image=nginx:1.27-alpine version=v2`。

```bash
[WSL] kubectl exec -n cloud-course-16 deploy/lesson16-web -- \
  wget -q -O - http://lesson16-web | grep 'release='
```

预期 `release=v2`。

```bash
[WSL] bash verify.sh
```

运行完整自动验收。

截图 3 包含回滚成功、历史、正确模板、3/3 和 v2 页面。

## 9. 故障排查对照

| 现象 | 优先证据 | 可能范围 | 不要先做 |
|---|---|---|---|
| rollout 超时 | Deployment、ReplicaSet、Pods | 新修订未完成 | 重装集群 |
| ImagePullBackOff | Pod Events、镜像字段 | 镜像名、仓库、凭据或网络 | 删除旧可用 Pod |
| Running 但 0/1 | readiness 事件、应用日志 | 路径、端口、启动时长 | 删除探针 |
| 新旧版本混合过久 | strategy、readyReplicas、events | 就绪或资源门禁 | 全局 scale |
| 回滚完成但页面不对 | 当前模板、ConfigMap、Service 请求 | 独立对象未恢复 | 宣告全系统恢复 |

## 10. 清理

```bash
[WSL] bash cleanup.sh
```

只删除 `cloud-course-16`。

```bash
[WSL] kubectl get namespace cloud-course-16
```

预期 NotFound。

```bash
[WSL] kubectl get namespaces
```

确认其他命名空间仍在。教师统一删除本课 k3d 集群。

## 11. 人工检查

教师还会核对：

* v1、v2、故障和回滚的时间顺序是否清楚；
* CHANGE-CAUSE 是否与对应 Pod 模板变更一致；
* 错误镜像期间旧 v2 是否仍提供 Service 路径；
* 是否把 ImagePullBackOff 证据与镜像故障假设对应；
* 回滚是否明确选择修订 2；
* 当前镜像、v2 标签、3/3、端点和页面是否全部恢复；
* 是否说明 Deployment 回滚不覆盖数据库和独立对象；
* 清理是否只触及本课命名空间。

## 12. 报告与提交

填写：

```txt
班级_学号_姓名_第16次课_Kubernetes发布回滚报告.docx
```

至少包含：

1. 上下文、命名空间和清单边界；
2. v1 基线；
3. v2 滚动过程、历史和页面；
4. 错误镜像的超时、对象状态和事件；
5. 为什么旧 v2 暂时仍可用；
6. 回滚目标、动作和完整回归；
7. 自动验收、人工检查和清理；
8. Deployment 回滚的能力边界。

只上传 Word 到智慧职教“第16次课”。
