HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 概览

    • K8s 实战学习实验室
    • 服务访问清单
    • K8s-Lab 学习总纲、仓库评估与专家路线图
  • 课程正文

    • 环境验证与第一课:认识你的真实集群
    • 第二课:kubectl apply 之后,到底发生了什么
    • 第三课:调度器如何选节点,为什么 Pod 会 Pending
    • 第四课:Kubernetes 网络、协议分层、VXLAN/IPIP/WireGuard 原理与排障
    • 第五课:NetworkPolicy、零信任网络与流量边界
    • 第六课:身份、认证、授权、准入与 ServiceAccount / RBAC 原理
    • 第七课:ConfigMap 与 Secret 注入模型、更新机制与安全边界
    • 第八课:存储持久化、PV / PVC / StorageClass 与 NFS 原理
    • 第九课:StatefulSet、Headless Service、稳定身份与存储原理
    • 第十课:探针、滚动更新、优雅终止与 PDB 原理
    • 第十一课:requests / limits、QoS、OOM 与驱逐原理
    • 第十二课:HPA、自动扩缩容、指标链路与副本伸缩原理
    • 第十三课:Service、EndpointSlice、kube-proxy、CoreDNS 与服务发现原理
    • 第十四课:Ingress-nginx、反向代理、Host / Path、NodePort 与北南向流量原理
    • 第十五课:HTTPS、TLS、SNI、证书信任与 Ingress 终止原理
    • 第十六课:cert-manager、Ingress 自动签发、证书生命周期与 ACME 工作流原理
    • 第十七课:ACME、Let's Encrypt、HTTP-01 / DNS-01、Orders / Challenges 与生产限制原理
    • 第十八课:大模型全生态,从数据到训练到部署到治理原理
    • 第十九课:大模型数据集、清洗、标注、切分、版本管理与质量治理原理
    • 第二十课:大模型训练、SFT、LoRA、Checkpoint、Adapter 与模型产物原理
    • 第二十一课:大模型推理、量化、KV Cache、vLLM、吞吐/延迟与部署发布链路原理
  • 实验操作记录

    • 本次仓库审查操作记录与命令原理
    • 本轮操作记录:环境验证、集群基线盘点与故障样本采集
    • 本轮操作记录:kubectl apply 主链路实验
    • 本轮操作记录:调度实验与 Pending 排查
    • 本轮操作记录:Kubernetes 网络原理、协议对比与调试实验
    • 本轮操作记录:NetworkPolicy 与零信任网络实验
    • 本轮操作记录:身份、认证、授权、准入实验
    • 本轮操作记录:ConfigMap 与 Secret 注入、更新与安全边界实验
    • 本轮操作记录:存储持久化、PV / PVC / StorageClass 与 NFS 实验
    • 本轮操作记录:StatefulSet、Headless Service 与稳定身份实验
    • 本轮操作记录:探针、滚动更新、优雅终止与 PDB 实验
    • 本轮操作记录:资源模型、QoS、OOM 与 CPU 节流实验
    • 本轮操作记录:HPA 自动扩缩容实验
    • 本轮操作记录:Service、EndpointSlice、CoreDNS 与服务发现排障实验
    • 本轮操作记录:Ingress-nginx、NodePort 与北南向流量实验
    • 本轮操作记录:HTTPS、TLS、自签证书与 Ingress 实验
    • 本轮操作记录:cert-manager 安装、CA 签发与 Ingress 自动证书实验
    • 本轮操作记录:ACME staging、HTTP-01 失败样本与排障实验
    • 本轮操作记录:大模型全生态与基础原理科普文撰写
    • 本轮操作记录:大模型数据集样本与治理文档编写
    • 本轮操作记录:大模型训练与模型产物概念文撰写
    • 本轮操作记录:大模型推理与服务发布概念文撰写

集群初始化 — 从裸机到 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 logs、kubectl 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.193  →  10.10.0.1  (Master)
107.148.164.118  →  10.10.0.2  (Worker-1)
154.9.27.60      →  10.10.0.3  (Worker-2)
38.76.221.17     →  10.10.0.4  (Worker-3)
154.219.104.66   →  10.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本机私钥,不能泄露
ListenPortUDP 监听端口,所有 peer 通过这个端口通信
PublicKey对端公钥,用于加密验证
AllowedIPs允许从这个 peer 收到的源 IP 范围,也决定路由
Endpoint对端的公网地址
PersistentKeepaliveNAT 穿透必须,否则 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

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

参数作用存在哪
advertiseAddressAPI Server 绑定监听的 IP只在 Master 本机
controlPlaneEndpointWorker 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
CiliumeBPF 加速、强大的安全策略复杂,需要较新内核

我们选 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 配置