---
url: /courses/kubernetes-cluster/05-pod-engineering/index.md
---
# 第五节 Pod 工程化管理

::: tip 项目目标
编写并部署包含 Init 容器与资源限制的工程化 Pod 清单 `yunfan-web-pod.yaml`；深入理解 Pause 容器网络共享机制、Init 容器串行逻辑以及基于 `requests`/`limits` 自动劃分的 QoS 服务质量等级；掌握 `kubectl logs`、`exec`、`describe` 多层级诊断命令，理解 Pod 的不可变性规则与 `patch` 补丁机制。
:::

## 5.1 底层原理解析：为什么容器之上还需要 Pod

在单机 Docker 环境中，我们直接运行独立的容器（`docker run`）。但在 Kubernetes 中，调度的最小原子单元不是单独的容器，而是 **Pod**。

Pod 解决的核心痛点是：**多容器紧密协同（Co-located Containers）时的共享与生命周期管理**。

### 5.1.1 Pause 容器与 Pod 共享机制

当你创建一个 Pod 时，Kubernetes 底层首先会启动一个隐式的 **Pause 容器（又称 Infrastructure Container）**：

1. **共享网络空间（Network Namespace）**：Pause 容器率先启动并申请唯一的 Pod IP。Pod 内的所有业务容器与 Init 容器都加入（`netns`）到 Pause 容器的网络命名空间中。因此，Pod 内的不同容器可以通过 `localhost` 直接通信，但也共享端口空间（端口不能冲突）。
2. **共享存储卷（Volumes）**：通过挂载同一个 `emptyDir` 存储卷，Init 容器生成的数据文件可以无缝传递给主应用容器使用。

```mermaid
flowchart TB
  subgraph Pod["Pod 边界 (共享网络与存储)"]
    Pause["底层 Pause 容器\n(持有 Pod IP / 10.244.1.5)"]
    InitC["Init 容器: prepare-page\n(写入 /work/index.html)"]
    AppC["主容器: nginx\n(读取 /usr/share/nginx/html)"]
    Vol[("共享 Volume: emptyDir")]

    InitC -.->|1. 串行写入数据| Vol
    AppC -.->|2. 启动读取数据| Vol
    InitC ===|共享 NetNS / localhost| Pause
    AppC ===|共享 NetNS / localhost| Pause
  end
```

观察下面的交互面板，体会 Init 容器与主应用容器的执行顺序以及 `emptyDir` 卷的数据交换关系：

***

## 5.2 配置文件编写：Init 容器与资源限制

在项目根目录下直接创建 Pod 声明文件 `yunfan-web-pod.yaml`。

### 5.2.1 完整配置文件：`yunfan-web-pod.yaml`

```yaml title="yunfan-web-pod.yaml"
apiVersion: v1
kind: Pod
metadata:
  name: yunfan-web-pod
  namespace: yunfan-shop
  labels:
    app.kubernetes.io/name: yunfan-web
    app.kubernetes.io/part-of: yunfan-mall
    tier: frontend
    env: lab
spec:
  restartPolicy: Always
  initContainers:
    - name: prepare-page
      image: busybox:1.36.1
      command:
        - sh
        - -c
        - |
          printf '%s\n' '<h1>Yunfan Mall Web Pod Lab</h1>' > /work/index.html
      volumeMounts:
        - name: web-content
          mountPath: /work
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      resources:
        requests:
          cpu: 50m
          memory: 32Mi
        limits:
          cpu: 200m
          memory: 128Mi
      ports:
        - name: http
          containerPort: 80
      volumeMounts:
        - name: web-content
          mountPath: /usr/share/nginx/html
  volumes:
    - name: web-content
      emptyDir: {}
```

在终端中部署该 Pod：

```bash
kubectl apply -f yunfan-web-pod.yaml
kubectl wait -n yunfan-shop --for=condition=Ready pod/yunfan-web-pod --timeout=120s
```

### 5.2.2 配置核心字段与底层机制解析

* **`initContainers`（Init 初始化容器列表）**：
  * **串行顺序执行**：Init 容器在主容器（`containers`）启动之前运行。如果定义了多个 Init 容器，它们会**按顺序依次执行**。
  * **退出码要求**：每一个 Init 容器必须运行成功并返回退出码 `0`。如果 Init 容器执行失败（如网络超时或命令报错），主容器**绝不会启动**，Pod 会根据 `restartPolicy` 不断重启该 Init 容器。
* **`volumes` 与 `emptyDir`**：
  * `emptyDir` 是在节点分配给 Pod 时创建的临时空目录。Init 容器挂载到 `/work` 并写入 `index.html`，Nginx 容器挂载到 `/usr/share/nginx/html` 直接读取。只要 Pod 在该节点运行，数据就一直保留；但 Pod 一旦被删除，`emptyDir` 内的数据将被彻底清空。

***

## 5.3 资源限制 (cgroup) 与 QoS 服务质量等级

在 Kubernetes 中，`resources` 字段包含 `requests` 和 `limits`，它们由 Linux 内核的 **cgroup** 机制进行底层硬约束。

### 5.3.1 `requests` 与 `limits` 的本质区别

* **`requests`（资源请求量）**：
  * **作用阶段**：**调度阶段 (kube-scheduler)**。
  * **原理**：调度器在选择节点时，会计算节点上已分配 Pod 的 `requests` 总和，确保节点剩余空闲容量 ≥ 新 Pod 的 `requests`。如果所有节点都不满足，Pod 将停留在 `Pending` 状态。
  * **单位**：`cpu: 50m` 表示 0.05 个 CPU 核心（千分之一核毫核），`memory: 32Mi` 表示 32 兆字节。
* **`limits`（资源上限）**：
  * **作用阶段**：**运行阶段 (Linux 内核 cgroup)**。
  * **原理**：节点 kubelet 会将 `limits` 翻译为 cgroup 限制规则（`cfs_quota_us` 与 `memory.limit_in_bytes`）。
  * **超限后果**：
    * **CPU 超限**：**CPU 节流 (Throttling)**。计算速度变慢，但容器不会被杀掉。
    * **内存超限**：**内存 OOM (Out Of Memory)**。Linux 内核直接发送 `SIGKILL` 信号终止容器，`kubectl describe` 会显示退出状态码 `137` 与 `OOMKilled`！

### 5.3.2 自动划分的 3 种 QoS (Quality of Service) 服务质量等级

Kubernetes 会根据 Pod 中所有容器的 `requests` 和 `limits` 设置，自动为 Pod 划定 **QoS 等级**。当宿主机节点遭遇严重内存不足时，QoS 等级直接决定了 Pod 被杀死驱逐的优先级：

| QoS 等级 | 判定条件 | 资源保障度 | OOM 驱逐优先级 |
| --- | --- | --- | --- |
| **`Guaranteed` (最高保障)** | Pod 内每个容器都显式设置了 CPU 和 Memory 的 requests 和 limits，且 **`requests == limits`**。 | 拥有最高资源优先级，绝不轻易被驱逐。 | **最后被杀死**（仅当系统内核濒临崩溃时） |
| **`Burstable` (中等保障)** | 不满足 Guaranteed 条件，但至少有一个容器设置了 CPU 或 Memory 的 requests。 | 允许资源在 limits 范围内突发使用。 | **次要杀死目标**（当超出 requests 时优先被杀） |
| **`BestEffort` (尽力而为)** | Pod 内所有容器均**未设置**任何 requests 和 limits。 | 不提供任何资源保障，占用宿主机闲置资源。 | **最先被杀死**（节点内存紧张时第一顺位） |

***

## 5.4 诊断排查：逐层递进定位 Pod 异常

当 Pod 发生故障时，需要按照**声明 Spec → 调度 Events → 节点执行 → 容器内部**的顺序提取证据：

```mermaid
flowchart TD
  A[Pod 发生异常] --> B[1. kubectl get pod -o wide/yaml\n检查声明与状态]
  B --> C[2. kubectl describe pod\n查看 Events 事件列表]
  C --> D[3. kubectl logs -c <container>\n读取容器标准输出日志]
  D --> E[4. kubectl exec -it -- <cmd>\n打入运行中容器执行诊断]
```

观察下面的排查演示面板，体会各级诊断命令在定位不同阶段故障时的分工：

### 5.4.1 四大常用诊断命令实操详解

在终端中执行以下诊断命令，验证 `yunfan-web-pod`：

**1. 查看 Pod 调度节点与详细 YAML 状态**：

```bash
kubectl get pod yunfan-web-pod -n yunfan-shop -o wide
```

**2. 提取事件与生命周期日志 (`kubectl describe`)**：

```bash
kubectl describe pod yunfan-web-pod -n yunfan-shop
```

**命令解析与关键观察点**：
查看末尾的 `Events:` 区域。你将看到 `Scheduled`（调度成功）、`Pulling`（拉取镜像）、`Created`（创建 Init 容器）、`Started`（启动 Nginx 容器）的完整事件链。

**3. 读取指定容器的运行日志 (`kubectl logs`)**：

```bash
# 查看 Init 容器 prepare-page 的历史日志
kubectl logs yunfan-web-pod -n yunfan-shop -c prepare-page

# 查看主容器 nginx 的运行日志
kubectl logs yunfan-web-pod -n yunfan-shop -c nginx
```

**命令参数解析**：

* `-c prepare-page`: 当 Pod 中包含多个容器（Init 容器或多应用容器）时，必须使用 `-c` 指定要读取的容器名。
* `--previous`: （可选参数）如果容器因崩溃重启，使用该参数可以读取容器上一次崩溃退出前的残余日志。

**4. 深入运行容器内部执行诊断命令 (`kubectl exec`)**：

在 Nginx 容器内访问本机端口，验证 Init 容器写入的网页内容：

```bash
kubectl exec -n yunfan-shop yunfan-web-pod -c nginx -- wget -qO- http://127.0.0.1
```

**预期输出**：
返回 Init 容器生成的 HTML 内容：`<h1>Yunfan Mall Web Pod Lab</h1>`。这直接证明了网络与共享 `emptyDir` 卷的正确性。

***

## 5.5 变更管理：Pod 不可变性与 `patch` 补丁机制

### 5.5.1 Pod 的不可变性 (Immutability) 规则

在 Kubernetes 中，**Pod 一旦创建成功，绝大多数 `spec` 属性是不允许原地修改的**！

* **允许原位修改的字段（少数）**：
  * `spec.containers[*].image`（允许原地更新镜像）
  * `spec.initContainers[*].image`
  * `spec.activeDeadlineSeconds`
  * `spec.tolerations`
* **禁止修改的字段**：`spec.containers[*].resources`、`ports`、`env`、`volumes` 等。如果要修改这些字段，必须删除旧 Pod 并重新创建，或者通过后续课程中的 Deployment 控制器进行滚动更新。

### 5.5.2 三种 Patch 补丁类型与 `kubectl patch` 实战

当我们只需要修改 Pod 的元数据（如 `labels` 或 `annotations`）时，可以使用 `kubectl patch`。

| 补丁类型 (`--type`) | 标准说明 | 使用场景示例 |
| --- | --- | --- |
| **`strategic` (默认)** | 战略合并补丁，根据 K8s 内部结构智能合并（如追加列表项）。 | 修改标准 Kubernetes 对象字段。 |
| **`merge` (JSON Merge Patch)** | 遵循 RFC 7396，直接按 JSON 键值进行覆盖或合并。 | 简易修改 `metadata.annotations` 等字典结构。 |
| **`json` (JSON Patch)** | 遵循 RFC 6902，使用操作数组（如 `op: replace`）。 | 精确替换特定数组下标的元素。 |

使用 `--type=merge` 为 `yunfan-web-pod` 补全元数据注解：

```bash
kubectl patch pod yunfan-web-pod \
  -n yunfan-shop \
  --type=merge \
  -p '{"metadata":{"annotations":{"yunfan.example/reviewed":"true"}}}'
```

**验证补丁结果**：

```bash
kubectl get pod yunfan-web-pod \
  -n yunfan-shop \
  -o jsonpath='{.metadata.annotations.yunfan\\.example/reviewed}{"\n"}'
```

**预期输出**：`true`。验证成功后，必须将该 `annotation` 手动回写并更新到 `yunfan-web-pod.yaml` 文件中，保证仓库清单与集群状态一致。

***

## 5.6 阶段验收与排错速查

### 5.6.1 本课提交成果清单

完成本课实训后，提交以下三项成果即可：

1. **配置文件**：项目根目录下的 `yunfan-web-pod.yaml`。
2. **第一张截图 `05-pod-status.png`**：同一终端画面中包含 `kubectl get pod yunfan-web-pod -o wide` 与 `kubectl describe pod yunfan-web-pod` 中展现 Init 状态、QoS Class 及 Requests/Limits 的结果。
3. **第二张截图 `05-pod-diagnose.png`**：同一终端画面中包含 `kubectl logs -c prepare-page`、`kubectl exec` 验证 HTTP 输出以及 `kubectl patch` 执行成功的画面。

{{reflection-checkpoint:pod-engineering-check}}

### 5.6.2 常见错误与排错矩阵

| 故障状态 (STATUS) | 可能原因 | 修复排查路径 |
| --- | --- | --- |
| **`Pending`** | 节点 CPU/内存 `requests` 不满足或资源不足 | 运行 `kubectl describe pod` 查看 Events 确认 `Insufficient cpu/memory`；调小 requests 值。 |
| **`ImagePullBackOff`** | 镜像名拼写错误、tag 不存在或国内网络拉取超时 | 查看 Events 确认拉取报错信息；使用上一课的 `docker pull` + `docker tag` 先行在宿主机加载镜像。 |
| **`CrashLoopBackOff`** | 容器启动命令直接退出（退出码为 0 或非 0）或应用代码崩溃 | 运行 `kubectl logs <pod> --previous` 查看容器崩溃前的异常堆栈；检查 `command` 拼写。 |
| **`Init:0/1` / `Init:Error`** | Init 容器命令报错退出，无法生成共享数据 | 运行 `kubectl logs <pod> -c <init-container-name>` 专门查看 Init 容器的错误日志。 |
| **`OOMKilled`** | 容器实际使用的内存峰值超越了 `limits.memory` 设定值 | 运行 `kubectl describe pod` 查看 Last State 中的 Exit Code 137；适度调大 `limits.memory` 上限。 |

::: details 挑战任务：探索静态 Pod 与普通 Pod 的诊断差异
回忆第 2 课中控制平面的静态 Pod（如 `kube-apiserver-yunfan-control-plane`）。试运行 `kubectl delete pod kube-apiserver-yunfan-control-plane -n kube-system`，观察该 Pod 是否会被彻底删除？为什么由 kubelet 静态管理的镜像 Pod 会在删除后立刻自动恢复？
:::

### 5.6.3 清理练习 Pod

完成验收后，删除本课的测试 Pod，避免占用后两课的 Deployment 端口：

```bash
kubectl delete pod yunfan-web-pod -n yunfan-shop
```

***

## 本章小测

{{assessment:pod-engineering}}

## 本章小结

完成本课后，你已经掌握了：

1. **Pod 底层共享机制**：理解了 Pause 容器为 Pod 内多容器提供共享 Network Namespace 与 `emptyDir` 共享卷的底层逻辑。
2. **Init 容器生命周期**：掌握了 Init 容器的串行顺序执行、退出码校验及数据传递场景。
3. **资源限制与 QoS 体系**：理清了 `requests`（调度匹配）与 `limits`（内核 cgroup 限制）的差异，以及由此划分的 `Guaranteed`、`Burstable`、`BestEffort` 3 种 QoS 服务质量等级与 OOM 驱逐规则。
4. **四级诊断排查体系**：熟练运用 `get -o yaml`、`describe` (Events)、`logs -c` 以及 `exec` 诊断运行中的容器。
5. **Pod 不可变性与 Patch 机制**：理解了 Pod 核心属性不可原位修改的规则，掌握了 `kubectl patch --type=merge` 增量更新元数据的方法。

下一课中，我们将把独立 Pod 模板交给 **Deployment 控制器**，实现多副本管理、自愈恢复、滚动更新与版本一键回滚！
