---
url: /courses/cloud-platform-build-management/15-k3s-workloads-service/index.md
---
# 第十五次课：K3s 入门——从 Pod 到 Service

## 进入本课环境

* `[Windows PowerShell] wsl ~`：打开本机默认的 WSL2 Ubuntu；本课使用准备好的本地隔离集群，不需要 SSH 登录 ECS。若个人电脑无法运行集群，使用教师提供的演示环境或实训数据。

Compose 用一个文件描述多容器应用，Kubernetes 则进一步把“应用应该是什么样”交给控制面持续维护。本课先不追求复杂集群，只在准备好的本地隔离环境里发布一个 Nginx 站点，观察副本如何从 1 变成 3，以及 Service 怎样找到这些 Pod。

::: tip 关键提醒
Kubernetes 的核心不是背命令，而是提交期望状态，再通过对象状态和实际请求，确认控制器是否把现实推进到了期望状态。
:::

学完这一课，做到五件事：

1. 区分节点、Pod、Deployment 和 Service 的职责。
2. 使用命名空间隔离本课资源。
3. 用清单创建工作负载，并从状态、就绪和请求三个层次验收。
4. 把 Deployment 从 1 个副本扩到 3 个，观察 Pod 和 EndpointSlice 的变化。
5. 根据 `describe`、事件和探针状态排查常见启动问题。

## 15.1 从期望状态开始

先创建本课使用的隔离集群（如尚未创建）：

```bash
[WSL] k3d cluster create xpk-lesson15 --servers 1 --agents 0
```

实训包清单文件位于 `starter/manifests/`（从智慧职教课程资源区下载 `lab-15-starter.zip` 解压到 `~/cloud-course/lab-15/`）。

先确认集群上下文和命名空间，再提交工作负载清单。单副本站点可访问后，把副本数从 1 调到 3，并同时观察 Deployment、Pod 和 EndpointSlice。

如果页面打不开，不要只盯着 Pod 的 `STATUS`。标签、Service 选择器、端点、事件和探针分别回答不同问题，后面的案例会按这条顺序取证。

## 15.2 先做资源门禁，再决定把 K3s 装在哪里

根据 K3s 官方说明，Control Plane 节点至少需要 2 核 CPU 与 2 GB 内存，而且这还没算后续业务 Pod 的开销。在测试云主机 B 上，虽然配置有 4 核 CPU 与 3.7 GiB 内存，但预检发现此时可用内存仅剩 1.6 GiB 左右，且上面已运行了其他容器。

```bash
[ECS] nproc
```

查逻辑 CPU 核心数。云主机 B 返回 `4`，满足最低需求。

```bash
[ECS] free -h
```

看内存总量与剩余。评估能否部署集群要看 `available`，而不是只盯 `free`，更不能只拿总物理内存去硬套官方门槛。

```bash
[ECS] docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
```

查看宿主机现有容器。发现已有业务在线时，本课会主动终止在远端 ECS 部署集群的计划，既不抢占已有资源，也不去强行停止未知服务。

::: warning 安全边界
符合官方最低配置并不等于适合与既有业务混部。最低要求没算学生工作负载、镜像缓存和演练余量。资源预检不过时应当更换环境，而不是强行部署。
:::

本课实操统一使用 OrbStack + k3d 隔离环境。k3d 可以在 Docker 容器里快速拉起轻量 K3s，适合随用随建、用完即清理。个人电脑无法稳定运行集群时，可以使用演示环境、实训数据或学校实验平台完成报告。

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

查看 `kubectl` 当前连接的 Cluster Context。在敲任何变更命令前，先看清上下文，防止把测试清单误发到了其他集群中。

```bash
[WSL] kubectl get nodes -o wide
```

查看节点状态、Kubernetes 版本、内部 IP 与容器运行时。节点显示 `Ready` 说明集群控制面正常，但应用启动还没做完。

## 15.3 四个核心对象各自解决什么

先把各对象的关系与边界梳理清楚：

| 对象 | 本课实例名称 | 主要职责 | 常见的认识误区 |
|---|---|---|---|
| Node | `k3d-xpk-lesson15-server-0` | 提供 CPU、内存与容器运行环境 | 节点 Ready，业务不一定能访问 |
| Pod | 自动生成的 `lesson15-web-...` | 运行具体的 Nginx 容器 | Pod IP 和名字随时会变，不能作稳定入口 |
| Deployment | `lesson15-web` | 维护指定的副本数与 Pod 模板 | 副本 3/3，Service 选择器可能没配上 |
| Service | `lesson15-web` | 提供固定虚拟 IP/DNS，把请求路由到对应 Pod | ClusterIP 默认只在集群内部可达 |

Pod 是 Kubernetes 调度的最小基本单位。在生产实践中，极少直接手动创建孤立 Pod，而是交由 Deployment 统一管控——因为手动建的 Pod 被杀掉后不会自动恢复。

Deployment 的关键字段结构：

```yaml
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: lesson15-web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: lesson15-web
```

`replicas: 1` 表示期望一个副本。`selector.matchLabels` 必须与 Pod 模板标签匹配，Deployment 才知道哪些 Pod 属于自己。

Service 使用同一标签：

```yaml
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: lesson15-web
  ports:
    - port: 80
      targetPort: http
```

`ClusterIP` 提供集群内部稳定入口。`targetPort: http` 指向容器中命名为 `http` 的端口；它比直接重复数字更容易阅读，也能减少改端口时的遗漏。

{{guided-demo:lesson-15-object-chain}}

## 15.4 用命名空间和清单建立单副本基线

本课清单放在 `starter/manifests/`，包括 Namespace（命名空间）、ConfigMap（配置映射）、Deployment 和 Service。先只读预览：

```bash
[WSL] kubectl kustomize starter/manifests
```

展开 `kustomization.yaml` 引用的全部资源，不提交到集群。检查命名空间是否为 `cloud-course-15`，镜像、标签、探针和资源限制是否符合本课要求。

```bash
[WSL] kubectl apply --dry-run=client -k starter/manifests
```

在客户端检查对象能否生成。`--dry-run=client` 不创建资源，离服务端真正通过准入、拉镜像、启动运行还有距离。

```bash
[WSL] kubectl apply -k starter/manifests
```

把期望状态提交给当前集群。`apply` 会显示哪些对象被创建或更新。

```bash
[WSL] kubectl rollout status deployment/lesson15-web -n cloud-course-15 --timeout=120s
```

等待 Deployment 达到可用状态，最多 120 秒。出现 `successfully rolled out` 后再继续，不要用连续重复 `apply` 掩盖启动问题。

```bash
[WSL] kubectl get nodes -o wide
```

确认节点 Ready 和实际 K3s 版本。

```bash
[WSL] kubectl get pod,service -n cloud-course-15 -o wide
```

同时查看 Pod 与 Service。单副本基线应包含一个 `1/1 Running` 的 Pod；Service 类型应为 `ClusterIP`，并显示标签选择器。

2026-07-28 在本地隔离 k3d 集群中的真实结果：

## 15.5 Service 怎样找到 Pod

Service 不根据 Pod 名称找后端，而是根据标签选择器持续选择 Pod。Kubernetes 会把结果写入 EndpointSlice（端点切片——记录当前就绪后端地址的对象）。

```bash
[WSL] kubectl get endpointslice -n cloud-course-15 \
  -l kubernetes.io/service-name=lesson15-web -o wide
```

只查看属于 `lesson15-web` Service 的 EndpointSlice。单副本时预期有一个就绪端点地址。

```bash
[WSL] kubectl exec -n cloud-course-15 deploy/lesson15-web -- \
  wget -q -O - http://lesson15-web
```

在工作负载 Pod 内访问 Service DNS 名称。预期页面包含 `K3s service ready`。这条路径证明集群 DNS、Service、端点和 Nginx 返回内容能够串起来，但它仍然只是集群内部访问。

::: tip 关键提醒
Service 的稳定性来自名称、虚拟地址和标签选择，不来自某个固定 Pod。应用扩容或 Pod 重建后，Service 名称不需要改变。
:::

## 15.6 从 1 扩容到 3，要看三类变化

```bash
[WSL] kubectl scale deployment/lesson15-web -n cloud-course-15 --replicas=3
```

把 Deployment 的期望副本数改为 3。命令返回 `scaled` 只说明修改请求被接受，还没有证明三个副本都可用。

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

等待控制器创建新 Pod，并让副本通过就绪探针。

```bash
[WSL] kubectl get deployment,pods -n cloud-course-15 -o wide
```

检查 Deployment 是否为 `3/3`，以及三个 Pod 是否都为 `1/1 Running`。Pod IP 不同是正常现象。

```bash
[WSL] kubectl get endpointslice -n cloud-course-15 \
  -l kubernetes.io/service-name=lesson15-web -o wide
```

检查 Service 后端是否从一个增加到三个。若 Deployment 已经 3/3 但端点数为 0，优先检查 Service 选择器与 Pod 标签是否匹配。

## 15.7 不要只盯着 STATUS：按症状选择证据

| 现象 | 第一条检查 | 重点字段 | 下一步 |
|---|---|---|---|
| Pod 为 Pending（调度等待） | `kubectl describe pod` | Events、调度原因、资源请求 | 检查节点资源和调度约束 |
| Pod 为 ImagePullBackOff（镜像拉取失败） | `kubectl describe pod` | 镜像名、拉取错误 | 核对镜像和网络，不上传凭据截图 |
| Pod 为 Running 但 0/1 | `kubectl describe pod` | Readiness、探针事件 | 检查路径、端口和启动时间 |
| Service 无端点 | `kubectl get svc,pod --show-labels` | selector 与 labels | 修正不匹配的一侧并回归 |
| Service 有端点但请求失败 | `kubectl exec ... wget` | DNS、HTTP、应用响应 | 再看日志和容器监听 |

```bash
[WSL] kubectl describe deployment lesson15-web -n cloud-course-15
```

查看 Deployment 条件、滚动过程和近期事件。输出较长，报告只截取与当前结论有关的部分。

```bash
[WSL] kubectl get events -n cloud-course-15 \
  --sort-by=.metadata.creationTimestamp
```

按时间查看本命名空间事件。事件会被轮转，适合定位近期问题，不是永久审计记录。

```bash
[WSL] kubectl logs -n cloud-course-15 deploy/lesson15-web --tail=30
```

查看一个工作负载 Pod 的最近 30 行日志。多个副本出现问题时，应先用 `kubectl get pods` 找到具体 Pod，再逐个比较。

{{chat-lab:lesson-15-kubernetes-evidence}}

## 15.8 可恢复故障：让 Service 暂时选不中 Pod

本课只修改课程命名空间中的 Service 选择器，并先保存原值：

```bash
[WSL] kubectl get service lesson15-web -n cloud-course-15 -o yaml \
  > lesson15-service.before.yaml
```

保存故障前的 Service 清单，作为恢复依据。文件不含密钥，但报告只需记录保存动作，不上传整个集群配置。

```bash
[WSL] kubectl patch service lesson15-web -n cloud-course-15 \
  --type=merge -p '{"spec":{"selector":{"app.kubernetes.io/name":"wrong-label"}}}'
```

把选择器改成不存在的标签。Service 对象仍然存在，三个 Pod 也仍是 Running，但 EndpointSlice 会变成没有后端。

```bash
[WSL] kubectl get service,pods,endpointslice -n cloud-course-15 --show-labels
```

并排检查选择器、Pod 标签与端点。原始现象应保留在报告中，再执行修复。

```bash
[WSL] kubectl apply -f starter/manifests/service.yaml
```

用版本控制中的原始清单恢复 Service。`kubectl get -o yaml` 保存的文件包含 `resourceVersion` 等服务端字段，适合取证；未经清理时不应直接把它当恢复清单，否则可能因资源已经变化而冲突。

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

重新检查上下文、命名空间、Deployment、就绪 Pod、Service 选择器、端点数量和页面内容。恢复后必须沿原来的 Service 请求路径回归。

::: warning 安全边界
不要删除其他命名空间，不要修改集群系统组件，也不要用 `kubectl delete all --all -A` 之类的全局清理命令。
:::

## 15.9 清理与提交

```bash
[WSL] kubectl delete namespace cloud-course-15 --wait=true
```

只删除本课命名空间，等待其中资源完成清理。演示集群按课程环境统一保留或删除，不要操作未知集群。

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

预期返回 `NotFound`。这是本课资源已经离开当前集群的证据。

完整步骤见[实训 15：K3s 工作负载、Service 与扩缩容](./lab-15.md)。

提交文件名：

```txt
班级_学号_姓名_第15次课_K3s入门报告.docx
```

只提交一个 Word 到智慧职教“第15次课”。截图要包含命令、关键结果、环境和证明范围；不要提交 kubeconfig、令牌、证书、完整集群配置或其他命名空间数据。

{{reflection-checkpoint:lesson-15-k3s-ready}}

## 15.10 第十五次课小测

{{assessment:lesson-15-check}}

## 15.11 小结

* 资源门禁先决定环境；云主机 B 没有足够安全余量，因此本课改用本地隔离 k3d。
* Pod 运行容器，Deployment 维护副本，Service 用标签选择一组就绪后端。
* Node Ready、Pod Running、Deployment 3/3、Service 有端点和 HTTP 成功分别提供不同层次的证据。
* 扩容请求被接受后，还要等待就绪并检查 Pod 与 EndpointSlice。
* Service 无端点时先比选择器和标签，不要盲目重启整个集群。
* 清理只删除本课命名空间，并用 NotFound 复核。

下一次课将在新的隔离集群中完成 Kubernetes 滚动发布、错误版本诊断与回滚。

## 15.12 资料来源

以下资料在 2026-07-28 核对：

* [K3s：Requirements](https://docs.k3s.io/installation/requirements)
* [K3s：Resource Profiling](https://docs.k3s.io/reference/resource-profiling)
* [K3s：Quick-Start Guide](https://docs.k3s.io/quick-start)
* [k3d：Using Config Files](https://k3d.io/stable/usage/configfile/)
* [Kubernetes：Pods](https://kubernetes.io/docs/concepts/workloads/pods/)
* [Kubernetes：Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
* [Kubernetes：Service](https://kubernetes.io/docs/concepts/services-networking/service/)
* [Kubernetes：EndpointSlices](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)
* [Kubernetes：Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)

{{assistant-invite:lesson-15-finish}}
