K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

系统准备 — 每一步都有原因

在安装 K8s 之前,操作系统需要满足一些前置条件。这不是"照着做",每一步都有明确的技术原因。 | 角色 | IP | 主机名 | 位置 | 配置 | |------|-----|--------|------|------| | Master | 107.148.176.193 | us480851516617a | LA | 16C/16G/50G |

Markdownphase-0/02-os-preparation.md2026年4月9日 09:53

系统准备 — 每一步都有原因

在安装 K8s 之前,操作系统需要满足一些前置条件。这不是"照着做",每一步都有明确的技术原因。

我们的 5 台机器

角色 IP 主机名 位置 配置
Master 107.148.176.193 us480851516617a LA 16C/16G/50G
Worker-1 107.148.164.118 us590068728056 LA 16C/16G/50G
Worker-2 154.9.27.60 cp-3 LA 16C/16G/50G
Worker-3 38.76.221.17 hk652699382121 HK 4C/8G/80G
Worker-4 154.219.104.66 wk-1 HK 16C/16G/150G

以下操作需要在所有 5 台机器上执行。


1. 关闭 Swap

swapoff -a
sed -i '/swap/d' /etc/fstab

这两条命令做了什么?

  • swapoff -a:立即关闭所有 swap 分区(-a = all)
  • sed -i '/swap/d' /etc/fstab:从 /etc/fstab(开机自动挂载配置)中删除 swap 行,防止重启后 swap 恢复

为什么 K8s 要求关闭 Swap?

核心原因:kubelet 的内存管理假设没有 swap。

Linux 的 swap 机制是:当物理内存不够时,把不常用的内存页写到磁盘(swap 分区),腾出内存给其他进程。这对普通服务器是有用的——宁可慢一点也比 OOM(Out Of Memory)杀进程好。

但在 K8s 中:

  1. 资源调度依赖精确的内存计量。 你在 Pod 中声明 requests.memory: 256Mi,Scheduler 会据此判断节点是否有足够内存。如果有 swap,一个 Pod 可能实际占了 2GB 内存(大部分在 swap 里),但 Scheduler 不知道,还会往这个节点塞更多 Pod。

  2. 性能不可预测。 swap 的 I/O 速度比内存慢 1000 倍以上。一个 Pod 的延迟可能突然从 1ms 飙到 100ms,因为它的内存页被 swap 到了磁盘。这对容器化应用是不可接受的。

  3. OOM 反而是期望行为。 K8s 的设计哲学是:如果 Pod 超出内存限制(limits.memory),直接 OOMKill 然后重启,而不是用 swap 苟活。快速失败 + 自动恢复比"慢慢死"要好。

面试加分点: K8s 1.22 开始实验性支持 swap(需要开启 feature gate NodeSwap),但默认仍然要求关闭。面试时可以提到这个进展,说明你关注社区动态。

验证结果

# 应该无输出,表示没有 swap
swapon --show

# 或者用 free 查看,Swap 行应该全是 0
free -h

我们 5 台机器的实际验证结果:所有节点 swap 均为 0,确认关闭成功。


2. 加载内核模块

cat <<EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

这些命令做了什么?

  • cat <<EOF > /etc/modules-load.d/k8s.conf:创建配置文件,让系统启动时自动加载这两个内核模块
  • modprobe overlay:立即加载 overlay 模块
  • modprobe br_netfilter:立即加载 br_netfilter 模块

overlay 模块是什么?为什么需要?

overlay 是容器使用的文件系统驱动。

容器镜像由多个"层"(layer)组成。比如一个 Node.js 应用的镜像:

Layer 3: COPY app.js /app/        (你的代码,很小)
Layer 2: RUN npm install           (依赖包,较大)
Layer 1: FROM node:18-slim         (基础OS+Node,很大)

OverlayFS 让这些只读层可以"叠加"起来,再加一个可写层在最上面:

┌────────────────────┐
│ 可写层(容器运行时) │  ← 容器写的文件放这里
├────────────────────┤
│ Layer 3: app.js    │  ← 只读
├────────────────────┤
│ Layer 2: node_mods │  ← 只读
├────────────────────┤
│ Layer 1: node:18   │  ← 只读
└────────────────────┘

好处:

  • 多个容器可以共享相同的只读层,节省磁盘和内存
  • 只有修改的文件才会被"复制上来"(Copy-on-Write),效率很高
  • containerd 默认使用 overlayfs 作为存储驱动

如果不加载 overlay 模块,containerd 就无法创建容器的文件系统。

br_netfilter 模块是什么?为什么需要?

br_netfilter 让 Linux 网桥上的流量经过 iptables 规则。

这需要理解 K8s 的网络模型:

  1. 每个 Pod 有自己的 IP 地址
  2. 同一节点上的 Pod 通过 Linux 网桥(bridge)通信
  3. 跨节点的 Pod 通过 overlay 网络(如 Calico/Flannel)通信
  4. Service 的流量转发靠 iptables 规则

问题来了:默认情况下,经过 Linux 网桥的流量不会被 iptables 处理。也就是说,同一节点上的 Pod 之间的流量,不会经过 kube-proxy 设置的 Service iptables 规则。

br_netfilter 模块的作用就是:让桥接流量也走 netfilter/iptables,确保 Service 的网络规则在节点内部也能生效。

面试深度题: "为什么 Pod A 访问 Service ClusterIP 时,如果 Pod B(后端)恰好在同一个节点上,流量也能正确路由?" 答案就是 br_netfilter。

验证结果

# 确认模块已加载
lsmod | grep br_netfilter
lsmod | grep overlay

我们 5 台机器的实际验证结果:所有节点 br_netfilter 和 overlay 模块均已加载。


3. 配置内核网络参数

cat <<EOF > /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

sysctl --system

这些参数分别是什么意思?

net.bridge.bridge-nf-call-iptables = 1

"桥接的 IPv4 流量要经过 iptables 规则"。这是 br_netfilter 模块的配套开关——加载了模块还不够,还需要显式开启这个行为。

不设置的后果: 同节点 Pod 之间访问 Service ClusterIP 可能不通,因为 iptables 的 DNAT 规则没有被应用。

net.bridge.bridge-nf-call-ip6tables = 1

同上,但针对 IPv6。即使你目前不用 IPv6,也建议开启——未来万一有 IPv6 的 Pod,不会因为这个被坑。

net.ipv4.ip_forward = 1

"允许本机转发 IP 数据包"。

默认情况下,Linux 收到一个目的地不是自己的数据包,会直接丢弃。但在 K8s 中,节点需要充当 Pod 的"路由器":

Pod A (10.244.1.5) → Node 1 → [IP forward] → Node 2 → Pod B (10.244.2.3)

Node 1 收到 Pod A 发给 10.244.2.3 的包,这个 IP 不是 Node 1 的,如果不开启 ip_forward,包就被丢了。

不设置的后果: 跨节点的 Pod 通信完全不通。

sysctl --system

重新加载所有 /etc/sysctl.d/ 下的配置文件,让设置立即生效(不需要重启)。

验证结果

sysctl net.bridge.bridge-nf-call-iptables
sysctl net.bridge.bridge-nf-call-ip6tables
sysctl net.ipv4.ip_forward

我们 5 台机器的实际验证结果:所有节点 ip_forward=1,内核网络参数均已正确设置。


4. 安装容器运行时(containerd)

# 安装 containerd
apt-get update
apt-get install -y containerd

# 生成默认配置
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

# 修改关键配置:使用 systemd cgroup 驱动
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 重启并设置开机启动
systemctl restart containerd
systemctl enable containerd

为什么选 containerd?

参见 01-k8s-architecture.md 中的解释。简单回顾:

  • K8s 1.24+ 不再支持 Docker 作为运行时
  • containerd 是 Docker 的核心组件之一,被独立出来
  • 更轻量、更直接、没有多余的中间层

SystemdCgroup = true 是什么?为什么必须设置?

这是一个非常容易踩的坑,也是面试常问的点。

背景知识 — cgroup(Control Groups):

cgroup 是 Linux 内核的资源隔离机制。它限制和追踪进程的 CPU、内存、I/O 使用。容器的资源限制(limits.cpu: 500m)底层就是靠 cgroup 实现的。

cgroup 有两种驱动:

驱动 说明
cgroupfs 直接操作 cgroup 文件系统(/sys/fs/cgroup/
systemd 通过 systemd 管理 cgroup

问题:kubelet 和 containerd 必须使用同一种 cgroup 驱动。

现代 Ubuntu(包括我们用的 24.04)使用 systemd 作为 init 系统,systemd 默认管理 cgroup。如果 containerd 用 cgroupfs 驱动,而 kubelet 用 systemd 驱动,两者会打架——各自创建不同的 cgroup 层级,导致资源统计混乱,严重时节点会变成 NotReady。

所以:两边都用 systemd 驱动,设置 SystemdCgroup = true

面试回答模板: "containerd 和 kubelet 必须使用相同的 cgroup 驱动。在使用 systemd 的发行版上(绝大多数现代 Linux),两者都应设为 systemd 驱动。如果不一致,会导致资源管理冲突,节点不稳定。"

验证结果

# 确认 containerd 运行正常
systemctl is-active containerd

# 确认 SystemdCgroup 设置
grep SystemdCgroup /etc/containerd/config.toml

我们 5 台机器的实际验证结果:

节点 containerd SystemdCgroup
107.148.176.193 (Master) active, v2.2.1 true
107.148.164.118 (W1) active, v2.2.1 true
154.9.27.60 (W2) active, v2.2.1 true
38.76.221.17 (W3) active, v2.2.1 true
154.219.104.66 (W4) active, v1.7.24 true

注意:W4 的 containerd 版本较旧(1.7.24),这是因为该机器之前安装过。不影响 K8s 1.30 的使用。


5. 安装 kubeadm、kubelet、kubectl

# 安装依赖
apt-get install -y apt-transport-https ca-certificates curl gpg

# 添加 Kubernetes 官方 GPG 密钥
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
  | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 添加 Kubernetes apt 仓库
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' \
  > /etc/apt/sources.list.d/kubernetes.list

# 安装
apt-get update
apt-get install -y kubelet kubeadm kubectl

# 锁定版本,防止 apt upgrade 意外升级
apt-mark hold kubelet kubeadm kubectl

这三个组件分别是什么?

组件 角色 在哪运行 类比
kubeadm 集群安装工具 安装时用,装完基本不用了 "装修队"——房子装好就走了
kubelet 节点代理 每个节点,作为 systemd 服务常驻运行 "物业管家"——一直在,管理本节点的容器
kubectl 命令行客户端 你的操作终端(可以在任何机器上) "遥控器"——远程操控集群

为什么用 apt-mark hold

K8s 集群升级是一个严格的流程(先升 Master,再逐个升 Worker),不能让 apt upgrade 随机把某个节点的 kubelet 升到新版本。版本不一致会导致 API 不兼容。

K8s 版本偏差策略:

  • kubelet 可以比 API Server 低最多 2 个次版本(如 API Server 1.30,kubelet 可以是 1.28-1.30)
  • kubelet 不能高于 API Server
  • kubectl 可以比 API Server 高或低 1 个次版本

我们选择 v1.30 的原因: 这是一个稳定的版本,后续可以练习升级到 v1.31。

为什么用 pkgs.k8s.io 而不是 apt.kubernetes.io

K8s 社区从 2023 年开始将包仓库从 Google 托管的 apt.kubernetes.io 迁移到社区托管的 pkgs.k8s.io。旧仓库已停止更新新版本。

验证结果

kubeadm version -o short
kubelet --version
kubectl version --client -o short

我们 5 台机器的实际验证结果:所有节点均为 kubeadm v1.30.14。


6. VPN 安全检查

在我们的两台 VPN 节点上,确认以上操作没有影响 VPN 服务:

# 检查 Xray 进程
systemctl is-active web-gateway

# 检查端口
ss -tlnp | grep -E ':443|:40000'

实际验证结果:

VPN 节点 web-gateway 443 端口 40000 端口
107.148.176.193 active 正常监听 正常监听
107.148.164.118 active 正常监听 正常监听

OS 准备工作全部完成,VPN 未受影响。


总结:为什么这些前置条件缺一不可

操作 不做的后果
关闭 swap kubelet 拒绝启动 / 内存调度不准确
加载 overlay 模块 containerd 无法创建容器文件系统
加载 br_netfilter 同节点 Pod 之间的 Service 访问不通
ip_forward=1 跨节点 Pod 通信不通
bridge-nf-call-iptables=1 桥接流量跳过 iptables,Service 规则失效
containerd SystemdCgroup cgroup 驱动冲突,节点不稳定
apt-mark hold 意外升级导致版本不兼容,集群崩溃

接下来

操作系统准备就绪,下一步是初始化 K8s 集群。

03-container-runtime.md (已合并到本文档第 4 节) → 04-cluster-init.md