学习目标
完成本课后,你将能够:
- 理解 K8s 节点初始化每个操作的底层原因(不仅知道"做什么",还知道"为什么"和"不做会怎样")
- 独立完成从裸机到 K8s-ready 的全部节点配置
- 遇到初始化故障时知道从哪里排查
前置知识
- Lesson 01-03 的容器与 K8s 架构知识
- Linux 基本系统管理能力
环境规划
在开始任何操作之前,先明确你的集群环境规划。以下是本课程使用的实际环境:
| 角色 | 主机名 | IP 地址 | 配置建议 |
|---|---|---|---|
| Master (控制面) | master-01 | 172.16.11.104 | 2C4G 起步 |
| Worker (工作节点) | worker-01 | 172.16.11.105 | 2C4G 起步 |
| Worker (工作节点) | worker-02 | 172.16.11.107 | 2C4G 起步 |
[!TIP] 如果你的实验环境 IP 不同,请在下面所有操作中替换为你实际的 IP 地址。建议先在纸上画好你的节点规划表再开始操作。
1. 克隆虚拟机后的 Machine-ID 重置
做什么
bash# 所有节点均须执行
sudo rm -f /etc/machine-id /var/lib/dbus/machine-id
sudo dbus-uuidgen --ensure=/etc/machine-id
sudo dbus-uuidgen --ensure
为什么
machine-id 是 Linux 系统的全局唯一标识符。当你在 Proxmox VE 或 VMware 中通过"克隆"方式创建多台虚拟机时,所有克隆出来的 VM 会共享完全相同的 machine-id。
Kubernetes 的 CNI 网络插件(如 Calico)依赖 machine-id 来区分不同的节点。如果多个节点的 machine-id 相同,Calico 会认为它们是同一台机器,导致:
- 路由表混乱,形成"路由黑洞"
- Pod 跨节点通信完全失败
kubectl get nodes可能只显示一个节点
不做会怎样
集群搭建时看起来正常,但部署 CNI 后 Pod 网络不通。这个 Bug 极其隐蔽,因为错误表现为网络问题,而根因却在 machine-id 上,排查方向完全错误。
验证
bash# 在每台节点上分别执行,确认 ID 各不相同
cat /etc/machine-id
2. 主机名设置与 Hosts 解析
做什么
bash# === 在 172.16.11.104 上执行 ===
sudo hostnamectl set-hostname master-01
# === 在 172.16.11.105 上执行 ===
sudo hostnamectl set-hostname worker-01
# === 在 172.16.11.107 上执行 ===
sudo hostnamectl set-hostname worker-02
bash# 所有节点均须执行 —— 写入集群的主机名解析表
cat <<EOF | sudo tee -a /etc/hosts
172.16.11.104 master-01
172.16.11.105 worker-01
172.16.11.107 worker-02
EOF
为什么
Kubernetes 使用主机名(hostname)作为节点的身份标识。当 Kubelet 启动时,它会以当前主机名向 API Server 注册自己。如果两台机器的主机名相同(比如克隆后都叫 ubuntu),后注册的节点会覆盖前一个,导致 kubectl get nodes 只显示一个节点。
写入 /etc/hosts 是为了让所有节点能通过主机名互相解析到 IP 地址。虽然内网 DNS 也能做到,但直接写 hosts 更可靠——不依赖任何外部服务。在 K8s 集群中,控制面需要频繁通过主机名与各节点通信,如果 DNS 服务短暂故障,整个集群的管理能力就会瘫痪。
验证
bash# 验证主机名已生效
hostname
# 验证能通过主机名 ping 通其他节点
ping -c 2 master-01
ping -c 2 worker-01
ping -c 2 worker-02
3. 关闭 Swap(虚拟内存交换分区)
做什么
bash# 所有节点均须执行
# 立即关闭当前的 Swap
sudo swapoff -a
# 永久关闭:注释掉 /etc/fstab 中的 swap 行,防止重启后恢复
sudo sed -i '/swap/s/^/#/' /etc/fstab
为什么
Kubelet 默认拒绝在启用了 Swap 的节点上运行。这是 Kubernetes 的一个硬性设计要求,原因是:
- 调度准确性:K8s 的 Scheduler 根据节点的真实内存容量来调度 Pod。如果存在 Swap,系统会将一部分内存数据换出到磁盘(磁盘速度比内存慢 100~1000 倍),但 Scheduler 并不知道这一点,它仍然认为节点有足够的物理内存,导致过度调度。
- 性能不可预测:当 Pod 的内存被交换到磁盘时,应用的响应时间会从毫秒级飙升到秒级,这在生产环境中是不可接受的。
- OOMKilled 机制失效:K8s 依赖 Cgroups 的内存限制来触发 OOMKilled 进行资源回收。如果有 Swap,进程在超出内存限制后不会被杀死,而是慢慢把数据交换到磁盘,导致整台机器性能崩塌。
不做会怎样
Kubelet 启动时直接报错退出:
"Running with swap on is not supported, please disable swap"
验证
bash# 确认 Swap 已关闭(Swap 行应该全是 0)
free -h
# total used free
# Swap: 0B 0B 0B
4. 加载内核模块与设置网络参数
做什么
bash# 所有节点均须执行
# 声明需要在系统启动时自动加载的内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 立即加载这两个模块(不用等重启)
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置内核网络参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 立即生效
sudo sysctl --system
为什么——逐项解释
overlay 模块
OverlayFS 是容器的分层文件系统驱动(我们在 Lesson 02 已经手动实验过)。Containerd 默认使用 OverlayFS 作为容器的存储后端,用来组装镜像的多层只读层和容器的可写层。
不加载会怎样:Containerd 无法创建容器的根文件系统,容器启动失败。
br_netfilter 模块
这个模块使 Linux 网桥(bridge)上的流量能够被 iptables 规则处理。
背景:Kubernetes 的 Service 网络依赖 iptables(或 IPVS)来做流量转发。当 Pod 的流量经过 Linux 网桥时,如果没有 br_netfilter 模块,这些流量会绕过 iptables 规则,导致 Service 的 ClusterIP 无法被正确转发。
不加载会怎样:Pod 可以直接通过 IP 互相通信,但通过 Service 名称或 ClusterIP 访问时会超时。
net.bridge.bridge-nf-call-iptables = 1
开启后,网桥上的 IPv4 流量会经过 iptables 的 FORWARD 链。这是 br_netfilter 模块加载后需要配合开启的内核参数。
net.bridge.bridge-nf-call-ip6tables = 1
同上,但针对 IPv6 流量。
net.ipv4.ip_forward = 1
允许本机作为"路由器"转发不属于自己的 IP 数据包。在 Kubernetes 中,Pod 的流量需要跨节点转发(比如 Worker-01 上的 Pod 要访问 Worker-02 上的 Pod),节点必须充当路由器角色。
不开启会怎样:Pod 跨节点通信的数据包到达节点后被内核直接丢弃,跨节点 Pod 完全不通。
验证
bash# 验证模块已加载
lsmod | grep -E "overlay|br_netfilter"
# 验证内核参数已生效
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward
# 输出应该都是 = 1
5. 安装 Containerd 容器运行时
做什么
bash# 所有节点均须执行
# 安装 containerd 及依赖
sudo apt-get update
sudo apt-get install -y containerd apt-transport-https ca-certificates curl gpg
# 生成默认配置文件
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
# 关键修改 1:启用 SystemdCgroup
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 关键修改 2:替换 sandbox 镜像为阿里云源
sudo sed -i "s|registry.k8s.io/pause|registry.aliyuncs.com/google_containers/pause|g" /etc/containerd/config.toml
# 重启 containerd 并设置开机自启
sudo systemctl restart containerd
sudo systemctl enable containerd
为什么——为什么不用 Docker?
从 Kubernetes 1.24 开始,K8s 正式移除了对 Docker 的内置支持(dockershim)。原因是 Docker 的架构过于臃肿:
使用 Docker 时的调用链:
Kubelet → dockershim → dockerd → containerd → runc → 容器进程
使用 Containerd 时的调用链:
Kubelet → containerd → runc → 容器进程
直接使用 Containerd 跳过了 Docker 中间层,减少了不必要的性能开销和故障点。
为什么要设置 SystemdCgroup = true?
Kubelet 默认使用 systemd 作为 Cgroup 驱动。如果 Containerd 仍然使用默认的 cgroupfs 驱动,两者会产生冲突——系统中同时存在两套 Cgroup 管理机制,导致资源限制和回收出现不一致。
不设置会怎样:Kubelet 日志报错:
"Failed to run kubelet" err="failed to run Kubelet: misconfiguration: kubelet cgroup driver: \"systemd\" is different from docker cgroup driver: \"cgroupfs\""
为什么要替换 sandbox 镜像?
每个 Pod 启动时,Containerd 会先创建一个极小的 pause 容器(也叫 sandbox 容器/基础设施容器)来占住 Pod 的网络命名空间。默认的 pause 镜像托管在 registry.k8s.io,国内无法直接访问。
不替换会怎样:在我们实际部署中遇到了这个问题——所有控制面组件(API Server、etcd、Scheduler)反复崩溃重启,kubelet 日志中出现大量 DeadlineExceeded 错误。根因就是 containerd 在尝试拉取 registry.k8s.io/pause:3.10.1 时超时。
[!WARNING] containerd 的配置文件中有两个容易混淆的字段:
sandbox(旧式的 CRI 插件配置)和sandbox_image(新版配置)。它们可能同时存在,导致修改了一个但另一个仍然指向官方源。正确的做法是用containerd config default重新生成完整配置,然后做全局替换。
验证
bash# 验证 containerd 运行正常
sudo systemctl status containerd
# 验证配置中 SystemdCgroup 已开启
grep "SystemdCgroup" /etc/containerd/config.toml
# 验证 sandbox 镜像已替换为阿里云源
containerd config dump | grep sandbox
# 测试拉取一个镜像
sudo crictl pull registry.aliyuncs.com/google_containers/pause:3.10
6. 安装 kubeadm / kubelet / kubectl
做什么
bash# 所有节点均须执行
# 导入阿里云 Kubernetes 仓库的 GPG 密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/Release.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# 添加阿里云 Kubernetes 仓库
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
# 安装三大组件
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
# 锁定版本,防止系统自动升级导致版本不兼容
sudo apt-mark hold kubelet kubeadm kubectl
# 启动 kubelet(此时它会反复重启,这是正常的,因为还没有初始化集群)
sudo systemctl enable --now kubelet
三大组件是什么
| 组件 | 作用 | 安装在哪里 |
|---|---|---|
| kubeadm | 集群初始化/加入工具(只在搭建时使用) | 所有节点 |
| kubelet | 节点代理,负责管理 Pod 的实际运行 | 所有节点 |
| kubectl | 命令行管理工具(查询/操作集群) | 通常只需在 Master 和管理机上 |
为什么要锁定版本?
K8s 的版本兼容有严格要求:kubelet 版本不能高于 API Server 版本,控制面组件之间的版本偏差不能超过一个小版本。如果系统自动把 kubelet 从 1.30 升级到了 1.31 而 API Server 还在 1.30,集群可能出现不兼容问题。
验证
bash# 确认三个组件都已安装
kubeadm version
kubelet --version
kubectl version --client
故障排查速查表
| 症状 | 可能原因 | 排查命令 |
|---|---|---|
| kubelet 启动后立即退出 | Swap 未关闭 | free -h 检查 Swap 是否为 0 |
| kubelet 报 cgroup driver 不匹配 | SystemdCgroup 未设为 true | grep SystemdCgroup /etc/containerd/config.toml |
| containerd 启动失败 | config.toml 语法错误 | journalctl -u containerd -n 20 |
| 节点间 hostname 无法解析 | /etc/hosts 未写入 | ping worker-01 |
| overlay 模块加载失败 | 内核版本过低 | uname -r 确认内核版本 ≥ 4.x |
检查点 —— 真实验证输出
完成本课所有操作后,在每个节点上执行以下验证。下面展示的是我们在实验环境 master-01 (172.16.11.104) 上实际执行的输出结果:
1. Machine-ID
bashyxwa@master-01:~$ cat /etc/machine-id
3802fa0de21719ecf7212a0169e6e5fc
每台节点的输出应该是一个 32 位的十六进制字符串,且三台节点各不相同。如果两台节点的值一样,说明克隆后没有重置 machine-id。
2. 主机名
bashyxwa@master-01:~$ hostname
master-01
应该显示你在
hostnamectl set-hostname中设置的名称。
3. Swap 状态
bashyxwa@master-01:~$ free -h
total used free shared buff/cache available
Mem: 3.8Gi 964Mi 173Mi 2.1Mi 3.0Gi 2.9Gi
Swap: 0B 0B 0B
关键看 Swap 那一行,三个值必须全部是
0B。如果不是 0,说明swapoff -a没有执行或/etc/fstab中的 swap 条目没有注释掉。
4. 内核模块
bashyxwa@master-01:~$ lsmod | grep -E "overlay|br_netfilter"
br_netfilter 32768 0
bridge 425984 1 br_netfilter
overlay 212992 11
必须同时看到
br_netfilter和overlay两行。bridge是br_netfilter的依赖模块,自动加载。最后一列的数字表示模块被引用的次数,overlay的11说明有 11 个容器正在使用 OverlayFS。
5. 内核网络参数
bashyxwa@master-01:~$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
三个值必须全部等于
1。如果为0,说明sysctl --system没有执行或/etc/sysctl.d/k8s.conf文件内容有误。
6. Containerd 运行状态
bashyxwa@master-01:~$ sudo systemctl is-active containerd
active
必须输出
active。如果是inactive或failed,用journalctl -u containerd -n 20查看错误日志。
7. SystemdCgroup 配置
bashyxwa@master-01:~$ grep "SystemdCgroup" /etc/containerd/config.toml
SystemdCgroup = true
必须是
true。如果是false或找不到这一行,说明sed替换没有生效。
8. Sandbox 镜像配置
bashyxwa@master-01:~$ sudo containerd config dump | grep sandbox | head -2
sandbox = 'registry.aliyuncs.com/google_containers/pause:3.10.1'
sandboxer = 'podsandbox'
sandbox 的值必须指向阿里云镜像(
registry.aliyuncs.com),而不是默认的registry.k8s.io。注意这里用的是containerd config dump(运行时实际配置),不是直接看配置文件——只有 dump 的输出才是 containerd 真正使用的值。
9. 三大组件版本
bashyxwa@master-01:~$ kubelet --version
Kubernetes v1.30.14
yxwa@master-01:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"30", GitVersion:"v1.30.14", GitCommit:"9e18483918821121abdf9aa82bc14d66df5d68cd", GitTreeState:"clean", BuildDate:"2025-06-17T18:34:53Z", GoVersion:"go1.23.10", Compiler:"gc", Platform:"linux/amd64"}
三个组件的版本号必须一致(这里都是 v1.30.14)。
kubeadm version输出的详细信息中,Platform: linux/amd64表示这是 64 位 x86 架构的构建版本。
✅ 以上全部验证通过后,你的节点已经完全准备好,可以进入下一课——集群初始化。
Comments NOTHING