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

Repository Reading Site

集群初始化 — 从裸机到 5 节点集群

我们的服务器都是云 VPS,公网 IP 通过 NAT 映射,本机网卡上只有内网 IP: Kubernetes 的 kubelet 需要通过 `--node-ip` 告诉 Master "我的 IP 是什么",API Server 用这个 IP 来连接 kubelet(比如 `kubectl logs`、`kubectl exec`)。 WireGuard 是

Markdownphase-0/04-cluster-init.md2026年4月9日 10:50

集群初始化 — 从裸机到 5 节点集群

跨机房网络方案:WireGuard 虚拟内网

问题:公网 IP 不在本地网卡上

我们的服务器都是云 VPS,公网 IP 通过 NAT 映射,本机网卡上只有内网 IP:

# 107.148.176.193 (Master) 上实际看到的:
$ ip addr show
inet 10.2.207.3/24    # 只有这个内网 IP
# 公网 IP 107.148.176.193 是 NAT 网关映射的,不在本机

# 38.76.221.17 (HK Worker) 上也是:
inet 10.4.225.2/24    # 内网 IP

# 但有些机器公网 IP 直接绑定在网卡上:
# 154.9.27.60:  inet 154.9.27.60/24   ← 公网IP在网卡上
# 154.219.104.66: inet 154.219.104.66/24

为什么这是个问题?

Kubernetes 的 kubelet 需要通过 --node-ip 告诉 Master "我的 IP 是什么",API Server 用这个 IP 来连接 kubelet(比如 kubectl logskubectl exec)。

  • 如果用内网 IP(10.x.x.x):不同机房的内网不互通
  • 如果用公网 IP:kubelet 要求这个 IP 必须在本地网卡上,NAT 场景下会报错
Failed to set some node status fields:
  node IP: "38.76.221.17" not found in the host's network interfaces

解决方案:WireGuard 组虚拟内网

WireGuard 是 Linux 内核级 VPN,在每台机器上创建一个虚拟网卡 wg0,分配一个统一的私有 IP:

机器公网 IP            WireGuard IP (wg0 网卡)
107.148.176.19310.10.0.1  (Master)
107.148.164.11810.10.0.2  (Worker-1)
154.9.27.6010.10.0.3  (Worker-2)
38.76.221.1710.10.0.4  (Worker-3)
154.219.104.6610.10.0.5  (Worker-4)

WireGuard IP 在本地网卡上(wg0 接口),kubelet 接受它。流量通过加密隧道走公网传输。

WireGuard 安装和配置

# 安装(所有节点)
apt-get install -y wireguard-tools

# 生成密钥(每台机器上执行)
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
chmod 600 /etc/wireguard/privatekey

每台机器的配置文件 /etc/wireguard/wg0.conf(以 Master 为例):

[Interface]
Address = 10.10.0.1/24        # 本机的 WireGuard IP
PrivateKey = <本机私钥>
ListenPort = 51820             # WireGuard 监听端口

[Peer]
PublicKey = <Worker-1 的公钥>
AllowedIPs = 10.10.0.2/32     # 只路由这个对端的 WireGuard IP
Endpoint = 107.148.164.118:51820  # 对端的公网 IP + 端口
PersistentKeepalive = 25      # 每 25 秒发心跳,保持 NAT 映射

# ... 其他 Peer 同理

关键参数解释:

参数 说明
Address 本机虚拟 IP,写在 wg0 网卡上
PrivateKey 本机私钥,不能泄露
ListenPort UDP 监听端口,所有 peer 通过这个端口通信
PublicKey 对端公钥,用于加密验证
AllowedIPs 允许从这个 peer 收到的源 IP 范围,也决定路由
Endpoint 对端的公网地址
PersistentKeepalive NAT 穿透必须,否则 NAT 映射超时后连接断开
# 启动并设置开机自启
systemctl enable --now wg-quick@wg0

验证结果

From Master (10.10.0.1):
  → 10.10.0.2 (Worker-1, LA): 1.45ms   ← 同城
  → 10.10.0.3 (Worker-2, LA): 1.69ms   ← 同城
  → 10.10.0.4 (Worker-3, HK): 155ms    ← 跨洋
  → 10.10.0.5 (Worker-4, HK): 158ms    ← 跨洋

面试考点: 为什么选 WireGuard?

  • 内核级实现,性能接近裸机(比 OpenVPN 快很多)
  • 代码量极小(约 4000 行),攻击面小
  • 使用现代密码学(Curve25519, ChaCha20, Poly1305)
  • 配置简单,无需 CA 证书体系

kubeadm init — 初始化控制平面

配置文件

直接用命令行参数容易出错,推荐用配置文件:

# /root/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "10.10.0.1"    # API Server 绑定的 IP(WireGuard IP)
  bindPort: 6443
nodeRegistration:
  kubeletExtraArgs:
    node-ip: "10.10.0.1"          # kubelet 上报的节点 IP

---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.30.14"
controlPlaneEndpoint: "10.10.0.1:6443"  # ★ 关键:Worker join 时连接的地址
networking:
  podSubnet: "10.244.0.0/16"       # Pod 网段,必须与 CNI 配置匹配
  serviceSubnet: "10.96.0.0/12"    # Service ClusterIP 网段
apiServer:
  certSANs:                        # API Server TLS 证书的 SAN
    - "107.148.176.193"            # 公网 IP(外部访问用)
    - "10.10.0.1"                  # WireGuard IP
    - "10.2.207.3"                 # 原始内网 IP
    - "127.0.0.1"                  # localhost

每个参数的意义

advertiseAddress vs controlPlaneEndpoint

这是最容易搞混的两个参数:

参数 作用 存在哪
advertiseAddress API Server 绑定监听的 IP 只在 Master 本机
controlPlaneEndpoint Worker join 时连接的地址,写入 cluster-info ConfigMap 全集群共享

我们踩过的坑: 第一次 init 时没设 controlPlaneEndpoint,导致 cluster-info 里存了内网 IP 10.2.207.3。Worker join 时先通过我们指定的公网 IP 连上了 API Server(bootstrap 阶段),但随后从 cluster-info 读取到内网 IP 并切换过去——HK 的 Worker 根本连不到 LA 的内网 IP,join 超时。

教训: controlPlaneEndpoint 必须设为所有节点都能到达的地址。

podSubnet: "10.244.0.0/16"

K8s 中每个 Pod 都有自己的 IP 地址,这些 IP 从 Pod 网段中分配。

  • 这个网段不能和节点 IP、Service IP、WireGuard IP 冲突
  • 必须和 CNI 插件的配置一致(Calico 默认用 192.168.0.0/16,我们指定 10.244.0.0/16 需要让 Calico 也知道)
  • /16 表示有 65536 个 IP 可用,足够大规模集群

serviceSubnet: "10.96.0.0/12"

Service ClusterIP 的范围。/12 表示有 ~100 万个 ClusterIP 可用。

第一个 ClusterIP 10.96.0.1 会自动分配给 kubernetes 这个默认 Service(即 API Server)。

certSANs — 证书的 Subject Alternative Names

API Server 使用 HTTPS,需要 TLS 证书。证书中的 SAN 字段列出了"这个证书对哪些地址有效"。

如果你用 https://107.148.176.193:6443 访问 API Server,但证书的 SAN 里没有这个 IP,kubectl 会报证书错误。所以我们把所有可能的访问地址都加进去。

执行初始化

kubeadm init --config /root/kubeadm-config.yaml

初始化过程做了什么(按顺序):

  1. Preflight checks — 检查端口、swap、容器运行时等
  2. Pull images — 拉取 K8s 组件镜像(API Server、etcd、Scheduler 等)
  3. Generate certificates — 为 API Server、etcd、kubelet 等生成 TLS 证书和密钥
  4. Generate kubeconfig — 为各组件生成访问 API Server 的配置文件
  5. Write static Pod manifests — 在 /etc/kubernetes/manifests/ 写入 YAML 文件
  6. Start kubelet — kubelet 检测到 manifests 目录中的文件,启动 static Pod
  7. Wait for API Server healthy — 等待 API Server 就绪
  8. Upload config — 将配置存储到 ConfigMap(kubeadm-config、kubelet-config)
  9. Mark control-plane — 给 Master 节点加标签和 taint
  10. Generate bootstrap token — 生成 Worker 加入集群的 token
  11. Install addons — 安装 CoreDNS 和 kube-proxy

面试考点 — Static Pod: K8s 的控制平面组件(API Server、etcd、Scheduler、Controller Manager)是作为"Static Pod"运行的。kubelet 直接监控 /etc/kubernetes/manifests/ 目录,发现 YAML 就启动对应的容器。这些 Pod 不受 API Server 管理(因为 API Server 还没启动呢),是 kubelet 独立管理的。


安装 CNI — Calico

为什么需要 CNI?

kubeadm init 之后,Master 节点状态是 NotReady,CoreDNS Pod 是 Pending

NAME              STATUS     ROLES           AGE
us480851516617a   NotReady   control-plane   22s

原因:K8s 本身不提供 Pod 网络。它定义了网络模型(每个 Pod 一个 IP,Pod 之间可直接通信),但具体实现交给 CNI(Container Network Interface)插件。

没有 CNI = Pod 没有 IP = 无法调度 = 节点 NotReady。

为什么选 Calico?

CNI 插件 优势 劣势
Calico 支持 NetworkPolicy、BGP、IPIP、VXLAN 资源占用略高
Flannel 简单轻量 不支持 NetworkPolicy
Cilium eBPF 加速、强大的安全策略 复杂,需要较新内核

我们选 Calico 因为它支持 NetworkPolicy(后续要练习网络隔离),同时足够稳定。

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml

安装后状态变化

安装前:Master NotReady,CoreDNS Pending
安装后:Master Ready,CoreDNS Running,Calico DaemonSet 在每个节点部署 calico-node

Worker Join — 加入集群

Join 配置文件

# /root/kubeadm-join-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
discovery:
  bootstrapToken:
    apiServerEndpoint: "10.10.0.1:6443"   # Master 的 WireGuard IP
    token: "<bootstrap-token>"
    caCertHashes:
      - "sha256:<hash>"                    # CA 证书指纹,防止中间人攻击
nodeRegistration:
  kubeletExtraArgs:
    node-ip: "10.10.0.X"                  # 本节点的 WireGuard IP

Join 过程详解

  1. Worker 连接 10.10.0.1:6443,用 bootstrap token 做初始认证
  2. 获取 CA 证书,验证 hash 匹配(防止连到假冒的 API Server)
  3. 发送 CSR(Certificate Signing Request)请求自己的 kubelet 证书
  4. API Server 自动签发证书
  5. kubelet 获得证书后,正式注册为集群节点
  6. kubelet 开始汇报节点状态,接收 Pod 调度

面试考点 — TLS Bootstrap: Worker 加入集群时的"鸡生蛋"问题:kubelet 需要证书才能和 API Server 通信,但证书是 API Server 签发的。Bootstrap Token 是解决方案——一个临时的低权限凭证,只够用来发送 CSR。

踩过的坑:Cilium eBPF 残留

154.9.27.60 和 154.219.104.66 之前运行过 Cilium CNI。Cilium 使用 eBPF 在内核层面劫持 connect() 系统调用,实现 Service IP 到 Pod IP 的转换。

问题: kubeadm reset 只清理 K8s 文件和 iptables,但不清理 eBPF 程序。Cilium 的 cil_sock4_connect BPF 程序仍然挂载在 cgroup 上,拦截所有 TCP 连接,把 ClusterIP 10.96.0.1 错误地 DNAT 到旧的后端 IP。

排查过程:

  1. curl https://10.10.0.1:6443/healthz → OK(直连)
  2. curl https://10.96.0.1:443/healthz → 超时(ClusterIP 不通)
  3. tcpdump 发现 SYN 包被发往了错误的 IP 10.0.63.3
  4. iptables 规则正确(DNAT 到 10.10.0.1:6443),且 0 pkts 命中
  5. bpftool prog list 发现 Cilium eBPF 程序仍在运行
  6. Detach BPF 程序后 ClusterIP 恢复正常

解决方法:

# 查看挂载的 BPF 程序
bpftool cgroup show /sys/fs/cgroup

# 清除 Cilium BPF 文件
rm -rf /sys/fs/bpf/tc /sys/fs/bpf/cilium

# 清空 nftables 规则(可能有 Cilium 链)
nft flush ruleset

# 清空 conntrack
conntrack -F

教训: 在复用之前运行过其他 K8s/CNI 的机器时,kubeadm reset 是不够的。必须额外清理:

  • eBPF 程序(bpftool
  • nftables 规则(nft flush ruleset
  • CNI 配置(rm -rf /etc/cni/net.d
  • conntrack 表(conntrack -F
  • 残留的 Calico/Cilium 数据(/var/lib/calico/var/lib/cilium

最终集群状态

NAME              STATUS   ROLES           VERSION    INTERNAL-IP
cp-3              Ready    <none>          v1.30.14   10.10.0.3   (Worker-2, LA)
hk652699382121    Ready    <none>          v1.30.14   10.10.0.4   (Worker-3, HK)
us480851516617a   Ready    control-plane   v1.30.14   10.10.0.1   (Master, LA)
us590068728056    Ready    <none>          v1.30.14   10.10.0.2   (Worker-1, LA)
wk-1              NotReady <none>          v1.30.14   10.10.0.5   (Worker-4, HK, 待恢复)

4/5 节点 Ready(Worker-4 机器暂时失联,恢复后清理 Cilium BPF 残留即可)。

VPN 服务(107.148.176.193 和 107.148.164.118 上的 Xray + WARP)全程正常运行,未受影响。


接下来

集群搭建完成后,需要给节点打标签和 taint,为后续的调度练习做准备。

05-cni-networking.md — CNI 网络原理深入 → 06-node-setup.md — 节点标签、Taint 和 kubeconfig 配置