Lesson 04:生产环境基线 —— 节点初始化的每一步为什么

admin 发布于 14 天前 1 次阅读


学习目标

完成本课后,你将能够:

  • 理解 K8s 节点初始化每个操作的底层原因(不仅知道"做什么",还知道"为什么"和"不做会怎样")
  • 独立完成从裸机到 K8s-ready 的全部节点配置
  • 遇到初始化故障时知道从哪里排查

前置知识

  • Lesson 01-03 的容器与 K8s 架构知识
  • Linux 基本系统管理能力

环境规划

在开始任何操作之前,先明确你的集群环境规划。以下是本课程使用的实际环境:

角色主机名IP 地址配置建议
Master (控制面)master-01172.16.11.1042C4G 起步
Worker (工作节点)worker-01172.16.11.1052C4G 起步
Worker (工作节点)worker-02172.16.11.1072C4G 起步

[!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 的一个硬性设计要求,原因是:

  1. 调度准确性:K8s 的 Scheduler 根据节点的真实内存容量来调度 Pod。如果存在 Swap,系统会将一部分内存数据换出到磁盘(磁盘速度比内存慢 100~1000 倍),但 Scheduler 并不知道这一点,它仍然认为节点有足够的物理内存,导致过度调度。
  2. 性能不可预测:当 Pod 的内存被交换到磁盘时,应用的响应时间会从毫秒级飙升到秒级,这在生产环境中是不可接受的。
  3. 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 未设为 truegrep 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 架构的构建版本。

✅ 以上全部验证通过后,你的节点已经完全准备好,可以进入下一课——集群初始化。

此作者没有提供个人介绍。
最后更新于 2026-04-22