---
url: /courses/kubernetes-cluster/02-kind-multinode-plan/index.md
---
# 第二节 kind 多节点模拟集群规划与创建

::: tip 项目目标
规划并创建一套包含 1 个控制平面节点与 2 个工作节点的 kind 多节点 Kubernetes 集群，完成宿主机与集群节点间的端口映射设计。掌握国内 Docker 镜像加速器配置与代理镜像源查找技巧，深入学习 `kind-config.yaml` 详细配置语法与底层网络连通机制，并通过进入节点内部观察底层容器与删除重建试验，验证集群配置的可复现性。
:::

## 2.1 拓扑规划：为什么必须使用多节点集群

在上一课中，我们确认了 Docker、kind 和 kubectl 工具链正常可用。如果直接运行不带任何配置参数的 `kind create cluster`，kind 默认会创建一个单节点（Single-node）集群。

单节点集群虽然启动迅速，但在学习 Kubernetes 核心能力时存在严重局限：

* **无法观察真正的 Pod 调度过程**：`kube-scheduler` 没有任何挑选余地，所有工作负载只能堆叠在唯一的节点上。
* **无法体验节点故障与驱逐机制**：当某个工作节点发生故障或进入维护状态（`cordon`/`drain`）时，无法观察 Pod 如何被自动重调度到其他健康节点。
* **角色模糊**：控制平面（Control Plane）的核心组件与普通业务 Pod 混跑在同一个容器中，无法建立清晰的集群架构边界。

为了在单机环境下真实模拟生产集群的角色分工，本课将规划并构建一套 **1 控制平面 + 2 工作节点** 的多节点拓扑架构。

| 拓扑规格 | 节点组成 | 适用学习场景 | 核心价值 |
| --- | --- | --- | --- |
| **标准多节点（本课采用）** | 1 × control-plane2 × worker | 跨节点调度、Pod 负载均衡、节点故障驱逐、维持服务可用性 | 完整展示 Kubernetes 多节点协同机制 |
| **基础双节点** | 1 × control-plane1 × worker | 角色分离、基础调度观察 | 资源紧张时的备选方案 |
| **默认单节点** | 1 × control-plane | 极简 API 操作测试 | 无法完成多节点调度与驱逐实验 |

## 2.2 架构原理：kind 的 Node-in-Container 运行机制

### 2.2.1 kind 节点内部包含了什么

kind（Kubernetes in Docker）的核心理念是 **“用 Docker 容器伪装成物理/虚拟机节点”**（Node-in-Container）。

当你通过 kind 创建集群时：

1. Docker Engine 会根据配置启动数个容器，每个容器拥有独立的网络命名空间（Network Namespace）、cgroup 和存储卷。
2. 每个节点容器内部都运行着 **containerd**（作为节点级容器运行时）和 **kubelet**（作为节点 Agent）。
3. 对于 **控制平面容器**（`control-plane`），containerd 会进一步拉取并运行 `kube-apiserver`、`etcd`、`kube-scheduler` 和 `kube-controller-manager` 等的核心静态 Pod。
4. 对于 **工作节点容器**（`worker`），kubelet 负责接收控制平面的指令，并通过节点内部的 containerd 拉取并运行你部署的业务 Pod。

```mermaid
flowchart LR
  subgraph CP["yunfan-control-plane (控制平面节点)"]
    direction TB
    K1[kubelet] --> C1[containerd]
    C1 --> StaticPods["etcd / API Server / Scheduler"]
  end

  subgraph W1["yunfan-worker (工作节点 1)"]
    direction TB
    K2[kubelet] --> C2[containerd]
    C2 --> PodA["业务 Pod A"]
  end

  subgraph W2["yunfan-worker2 (工作节点 2)"]
    direction TB
    K3[kubelet] --> C3[containerd]
    C3 --> PodB["业务 Pod B"]
  end

  CP <--> W1
  CP <--> W2
```

观察下面的交互面板，理解请求提交后，控制平面中的 `Scheduler` 如何决定将 Pod 分发至不同的 Worker 容器节点：

## 2.3 网络设计：三层网络与端口映射穿透路径

### 2.3.1 区分 Kubernetes 中的三层网络

kind 集群内部存在三层相互隔离的网络空间，必须清晰区分它们的范围与职责：

| 地址空间 | 作用域 | 地址范围示例 | 说明与注意事项 |
| --- | --- | --- | --- |
| **Docker Bridge 网络** | 宿主机 ↔ kind 节点容器 | `172.18.0.0/16` | 由 Docker 自动分配，保证宿主机能与节点容器通信，节点容器间相互连通 |
| **Pod CIDR (网段)** | 集群内部 Pod 间通信 | `10.244.0.0/16` | 由 CNI 插件分配，Pod 在集群内拥有唯一 IP，但**无法直接从宿主机访问** |
| **Service CIDR (虚拟网段)** | 集群内 Service 负载均衡 | `10.96.0.0/16` | Kubernetes 虚拟 IP 地址，仅在集群内部通过 iptables/IPVS 转发有效 |

### 2.3.2 宿主机到集群内部的端口穿透链

由于 Pod IP（`10.244.x.x`）处于容器内部网络，宿主机外部（如电脑上的浏览器）默认无法直接访问它。

在 kind 配置中，我们需要通过 `extraPortMappings` 声明端口转发规则，将宿主机的端口映射到控制平面节点容器中，为后续的 NodePort Service 暴露访问入口：

```text
[宿主机浏览器] http://127.0.0.1:18080
       │ (宿主机端口)
       ▼
[Docker 端口映射] extraPortMappings
       │ (节点容器端口 30080/TCP)
       ▼
[kind 节点容器] yunfan-control-plane:30080
       │ (NodePort Service 路由)
       ▼
[Pod 容器] 10.244.1.5:80 (业务响应)
```

观察下面的端口穿透与数据包流向动画，理解数据包如何从宿主机一步步到达 Pod：

## 2.4 配置编写：声明式 `kind-config.yaml` 详细语法

在项目根目录下，直接创建集群配置文件 `kind-config.yaml`。我们将节点角色、网段规划与端口映射统一写入声明式 YAML 文件中。

### 2.4.1 配置文件的完整结构

在 Kubernetes 中，声明式配置能够保证环境的**可复现性**。以下是包含 1 控制平面和 2 工作节点的标准多节点配置：

```yaml title="kind-config.yaml"
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: yunfan
networking:
  ipFamily: ipv4
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/16"
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30080
        hostPort: 18080
        listenAddress: "127.0.0.1"
        protocol: TCP
  - role: worker
  - role: worker
```

### 2.4.2 `kind-config.yaml` 各配置项语法与作用详解

| 配置层级 / 字段 | 数据类型 | 必需 | 示例 / 默认值 | 详细作用与底层原理解释 |
| --- | --- | --- | --- | --- |
| **`kind`** | 字符串 | **是** | `Cluster` | 标识配置文件的顶层资源类型，指示 kind CLI 解析该 YAML 为一个集群创建配置文件。 |
| **`apiVersion`** | 字符串 | **是** | `kind.x-k8s.io/v1alpha4` | 指定 kind 配置 API 的版本，决定后续字段的合法性校验规则。 |
| **`name`** | 字符串 | **是** | `yunfan` | 指定新建集群的唯一标识名。创建后 Docker 容器名将带有 `yunfan-` 前缀，`kubectl context` 命名为 `kind-yunfan`。 |
| **`networking`** | 对象 | 否 | - | 集群网络架构配置块。包含 Pod 和 Service 网段分配规则。 |
| **`networking.podSubnet`** | CIDR 字符串 | 否 | `10.244.0.0/16` | 指定 CNI 插件为所有 Pod 分配 IP 时的地址池范围。需注意不能与宿主机本地 VPN 或物理网段冲突。 |
| **`networking.serviceSubnet`** | CIDR 字符串 | 否 | `10.96.0.0/16` | 指定 Kubernetes 集群内部虚拟 Service IP (ClusterIP) 的分配池范围。 |
| **`nodes`** | 数组列表 | **是** | - | 声明要创建的 kind 节点容器列表。每一个列表项对应 Docker Engine 中的一个容器实例。 |
| **`nodes[].role`** | 枚举字符串 | **是** | `control-plane` / `worker` | 定义节点的集群角色。`control-plane` 会启动 API Server 和 etcd，`worker` 仅启动基础运行环境。 |
| **`extraPortMappings`** | 数组列表 | 否 | - | 端口映射规则列表（类似于 `docker run -p`）。**仅在控制平面或目标节点生效**。 |
| **`containerPort`** | 整数 (Port) | **是** | `30080` | 节点容器内部监听的端口号。后续第 8 课 NodePort Service 的 `nodePort` 需与此端口完全匹配。 |
| **`hostPort`** | 整数 (Port) | **是** | `18080` | 宿主机本地物理机暴露的端口号。宿主机用户将通过 `127.0.0.1:18080` 进行网络访问。 |
| **`listenAddress`** | IP 字符串 | 否 | `127.0.0.1` | 宿主机端口绑定的 IP 地址。显式指定为 `127.0.0.1` 可提高开发环境安全性，防止暴露给公网或局域网。 |
| **`protocol`** | 枚举字符串 | 否 | `TCP` / `UDP` | 传输层网络协议，默认为 `TCP`。 |

## 2.5 集群创建与国内网络镜像加速实战

### 2.5.1 配置 Docker 镜像加速器与寻找国内源

在创建 kind 集群时，kind 需要拉取完整的 Kubernetes 节点镜像（如 `kindest/node:v1.35.0`，体积约 1.2GB）。因网络环境限制，直接拉取常导致超时。

::: tabs#docker-config
@tab 方法一：配置 Docker Engine 镜像加速器

在 Docker 配置文件 `daemon.json` 中配置国内加速镜像站列表：

**1. 修改配置文件**：

* **Docker Desktop (macOS / Windows)**: 打开 Docker 界面 ⚙️ **Settings** -> **Docker Engine**，在 JSON 配置中补充 `"registry-mirrors"` 项。
* **Linux 物理机/虚拟机**: 修改 `/etc/docker/daemon.json`：

```json
{
  "registry-mirrors": [
    "https://docker.anyhub.us.kg",
    "https://dockerhub.icu",
    "https://docker.m.daocloud.io"
  ]
}
```

**2. 重启 Docker 服务**：

* **Docker Desktop**: 点击界面右下角的 **Apply & restart**。
* **Linux**: 执行 `sudo systemctl daemon-reload && sudo systemctl restart docker`。

**3. 在宿主机上执行拉取**：
配置生效后，直接运行标准拉取命令，加速器会自动拦截并加速下载：

```bash
docker pull kindest/node:v1.35.0
```

@tab 方法二：查找并拉取代理镜像重命名 (推荐)

如果无法更改全局镜像加速器，可以通过国内镜像代理站手动拉取并转换 Tag。

**如何查找镜像对应的国内源地址？**
kind 默认节点镜像官方全名为 `docker.io/kindest/node:v1.35.0`。国内代理站（如 DaoCloud 镜像代理 `m.daocloud.io` 或 华为云镜像代理 `swr.cn-north-4.myhuaweicloud.com`）的通用映射规则为：**`代理域名 / 官方全名`**。

**为什么必须使用 `docker tag` 重新命名？**
第三方代理拉取后，镜像在本地镜像库中的名称带有代理前缀（`m.daocloud.io/docker.io/kindest/node:v1.35.0`）。而 kind 在创建集群时，默认只寻找标准名字 `kindest/node:v1.35.0`。如果直接拉取而不重命名，kind 将无法识别本地已有镜像，依然会尝试连接国外源。通过 `docker tag` 建立标准别名，才能保证 kind 直接精准调用本地镜像。

在终端中依次执行以下命令：

```bash
# 1. 使用国内代理前缀拉取节点镜像
docker pull m.daocloud.io/docker.io/kindest/node:v1.35.0

# 2. 将镜像重新打标签 (tag) 还原为 kind 认可的标准名称
docker tag m.daocloud.io/docker.io/kindest/node:v1.35.0 kindest/node:v1.35.0
```

:::

### 2.5.2 执行集群创建命令

在终端中执行以下命令，使用 `kind-config.yaml` 配置文件启动多节点集群：

```bash
kind create cluster --config kind-config.yaml --image kindest/node:v1.35.0 --wait 5m
```

命令参数逐项解析：

* `kind create cluster`: kind 创建集群的基础子命令。
* `--config kind-config.yaml`: 指定刚才编写的 YAML 配置文件路径。
* `--image kindest/node:v1.35.0`: 明确指定 Kubernetes 节点镜像版本。如果刚才已拉取并重命名镜像，kind 会直接使用本地镜像而无需再次联网。
* `--wait 5m`: 设定最高等待超时时间为 5 分钟。若控制平面组件在此时间内未就绪，命令会自动终止并报错。

## 2.6 操作与探秘：从双层视角验证集群

集群创建成功后，我们需要分别从 **Docker 视角** 和 **Kubernetes 视角** 验证节点运行状态，并深入控制平面内部查看运行组件。

:::: steps

1. **核对 Docker Engine 内部运行的节点容器**

   运行以下命令，查看 Docker 正在运行的容器：

   ```bash
   docker ps --filter name=yunfan --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
   ```

   **预期输出**：
   你应该能看到 3 个容器，其中 `yunfan-control-plane` 容器的 Ports 列显式映射了 `127.0.0.1:18080->30080/tcp`。

2. **验证 kubectl 集群节点状态**

   运行以下命令，查看 Kubernetes 认可的节点列表及其角色：

   ```bash
   kubectl get nodes -o wide
   ```

   **预期输出**：

   ```text
   NAME                   STATUS   ROLES           AGE   VERSION   INTERNAL-IP   OS-IMAGE
   yunfan-control-plane   Ready    control-plane   2m    v1.35.0   172.18.0.2    Debian GNU/Linux 12
   yunfan-worker          Ready    <none>          2m    v1.35.0   172.18.0.3    Debian GNU/Linux 12
   yunfan-worker2         Ready    <none>          2m    v1.35.0   172.18.0.4    Debian GNU/Linux 12
   ```

3. **探秘节点内部：使用 `crictl` 观察控制平面原生 Pod**

   进入 `yunfan-control-plane` 节点容器内部，直接调用节点容器运行时的客户端 `crictl`：

   ```bash
   docker exec -it yunfan-control-plane crictl ps
   ```

   **原理说明与预期观察**：
   该命令直接打入了 Docker 容器内部。你会发现节点容器内部正在运行着 `kube-apiserver`、`kube-scheduler`、`etcd` 和 `kindnet` 等系统级容器！这直接印证了 Node-in-Container 的机制。
   ::::

## 2.7 演练与验证：删除集群并按配置一键重建

为了证明你的 `kind-config.yaml` 具备**可复现的声明式基础设施**能力，我们将执行一次删除与重建演练。

### 2.7.1 删除集群

执行以下命令彻底销毁当前集群：

```bash
# 删除名称为 yunfan 的 kind 集群
kind delete cluster --name yunfan
```

删后检查：

```bash
# 确认集群名已被注销，节点容器已被销毁
kind get clusters
docker ps --filter name=yunfan
```

### 2.7.2 一键重建集群

使用保存好的 `kind-config.yaml` 重新创建集群：

```bash
kind create cluster --config kind-config.yaml --image kindest/node:v1.35.0 --wait 5m
```

创建完成后再次运行验证：

```bash
kubectl config current-context
kubectl get nodes
```

**成功标准**：

* 节点快速重建完成，状态全部恢复为 `Ready`；
* 无需手动重新配置网络或端口，声明式配置自动复原了 1 控制平面 + 2 工作节点的完整拓扑。

## 2.8 实验交付物与排错速查

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

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

1. **配置文件**：项目根目录下的 `kind-config.yaml`。
2. **第一张截图 `02-create-nodes.png`**：同一终端画面中包含 `docker ps --filter name=yunfan` 与 `kubectl get nodes -o wide` 的执行结果。
3. **第二张截图 `02-crictl-explore.png`**：运行 `docker exec -it yunfan-control-plane crictl ps` 成功展示控制平面内部容器的终端画面。

{{reflection-checkpoint:kind-multinode-delivery-check}}

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

| 故障现象 | 可能原因 | 修复排查路径 |
| --- | --- | --- |
| `yaml: line ... error` | YAML 缩进格式错误（使用了 Tab 或层级错乱） | 检查缩进，确保使用纯空格，核对 `nodes` 与 `extraPortMappings` 层级 |
| `address already in use` | 本机 `18080` 端口已被其他程序占用 | 使用 `lsof -i :18080` (macOS/Linux) 或 `Get-NetTCPConnection` (Win) 找出占用进程或更换 hostPort |
| `kind create` 卡在等待节点响应 | 镜像拉取缓慢或 Docker 资源不足 | 使用 `docker pull` / `docker tag` 先行拉取镜像；检查 Docker 内存限制是否 ≥ 4GB |
| `kubectl` 无法连接到 API Server | 当前 context 切换到了其他集群 | 运行 `kubectl config use-context kind-yunfan` 修正目标集群上下文 |

::: details 挑战任务：探索控制平面的静态 Pod 清单
运行 `docker exec -it yunfan-control-plane bash` 进入控制平面节点容器，查看 `/etc/kubernetes/manifests/` 目录下的配置文件。这里的 YAML 文件是节点 kubelet 用来自动加载并启动 `kube-apiserver`、`etcd` 等核心组件的静态 Pod 清单（Static Pod）。你可以尝试打开其中一个文件，观察 Kubernetes 最核心的控制平面组件是如何通过声明式配置定义的。
:::

## 本章小测

{{assessment:kind-multinode-plan}}

## 本章小结

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

1. **单节点与多节点拓扑的本质区别**：理解了多节点是观察跨节点调度与故障驱逐的物理基础。
2. **kind 的 Node-in-Container 底层架构**：掌握了 Docker 容器如何作为 Kubernetes 节点，以及节点内部 containerd 和 kubelet 的协作模式。
3. **三层网络与端口映射**：理清了宿主机端口 ↔ 节点容器端口 ↔ Service ↔ Pod IP 的数据包传递链路。
4. **`kind-config.yaml` 详细配置语法**：掌握了声明式 `kind` 配置文件中 `networking`、`nodes` 以及 `extraPortMappings` 的各项参数含义与底层作用。
5. **镜像加速与国内源查找实战**：掌握了 Docker daemon 镜像加速配置规则与代理源名称转换逻辑。
6. **镜像预载与集群快速重建**：掌握了本地预载节点镜像的方法，并通过进入节点内部探秘和删除重建，验证了基础设施的可复现性。

下一课中，我们将深入探究 Kubernetes 控制平面的调度决策机制、CNI 网络连通性测试以及工作节点的封锁（`cordon`）与驱逐（`drain`）实战！
