3 节点 K8s 1.34 集群部署实录:Ubuntu 24.04 + Calico eBPF + Knative 就绪

背景与需求

在一台 MacBook Pro M4 Pro(24GB)上,通过 Parallels Desktop 创建 3 台 Ubuntu 24.04 ARM64 虚拟机,部署一套同时兼顾传统微服务高性能 Knative Serverless 的 Kubernetes 集群。

基础设施规划

节点 主机名 IP 角色
VM 1 k8s-master 10.211.55.6 control-plane
VM 2 k8s-worker1 10.211.55.7 worker
VM 3 k8s-worker2 10.211.55.8 worker

核心版本选型

  • Kubernetes:v1.34.8
  • Containerd:1.7.29(SystemdCgroup=true)
  • Calico:v3.29.2(Operator 模式,eBPF 数据面)
  • 内核:6.8.0-40-generic(BTF 已启用,eBPF CO-RE 可用)
  • Pod 网络:192.168.0.0/16(VXLAN CrossSubnet)
  • Service 网络:10.96.0.0/12

部署架构:模块化 Skill 脚本

将全套流程拆分为 3 个独立、幂等的 Shell 脚本,存放于项目仓库 boat/k8s-deploy/

Skill 1:01-all-nodes-env.sh — 通用环境配置

在所有节点(包括 Master)上执行,接收主机名作为参数。

核心功能:

bash 01-all-nodes-env.sh k8s-master   # Master 节点
bash 01-all-nodes-env.sh k8s-worker1  # Worker 节点
bash 01-all-nodes-env.sh k8s-worker2  # Worker 节点
  1. 主机名与 hosts 映射:自动修改 hostname,写入 /etc/hosts 集群 IP 映射
  2. 关闭 Swapswapoff -a + 注释 /etc/fstab
  3. 加载内核模块overlaybr_netfilternf_conntrack
  4. sysctl 优化(Serverless 关键):
    • fs.inotify.max_user_watches = 524288 ← Pod 冷启动感知
    • net.netfilter.nf_conntrack_max = 262144 ← 高弹缩连接跟踪
    • fs.file-max = 2097152 ← 短生命周期 Pod 句柄
  5. 安装依赖:socat、conntrack、ipset、ipvsadm
  6. 配置 Containerd
    • SystemdCgroup = true(与 K8s cgroup v2 对齐)
    • sandbox_image = registry.aliyuncs.com/google_containers/pause:3.10.1(国内加速)
  7. 安装 nerdctl + alias docker='nerdctl -n k8s.io'(兼容 Docker 肌肉记忆)
  8. 安装 K8s 1.34:通过 pkgs.k8s.io 官方源(国内 CDN 可达),使用 apt-mark hold 锁定版本

Skill 2:02-master-init.sh — 控制平面初始化

仅在 Master 执行一次。

bash 02-master-init.sh 10.211.55.6
  1. kubeadm init 指定 Pod CIDR + Service CIDR + 阿里云镜像仓库
  2. 配置 ~/.kube/config 和 root 用户凭证
  3. 部署 Calico Tigera Operator + Installation CR
  4. Calico eBPF 数据面切换(关键优化):
    bpfEnabled: true
    bpfExternalServiceMode: "DSR"
    bpfKubeProxyIptablesCleanupEnabled: true
    
  5. 输出 kubeadm join 完整令牌命令

Skill 3:03-worker-join.sh — 工作节点加入

在 Worker 节点上执行,支持交互式粘贴 join 命令或 auto 自动获取。


实战踩坑记录

坑 1:containerd 默认禁用 CRI 插件

现象kubeadm init 报错 unknown service runtime.v1.RuntimeService

原因containerd config default 生成的配置文件包含 disabled_plugins = ["cri"]

修复

sudo sed -i 's/disabled_plugins = \["cri"\]/disabled_plugins = []/' /etc/containerd/config.toml
sudo systemctl restart containerd

坑 2:Pause 镜像无法拉取

现象Failed to create sandbox: failed to pull image "registry.k8s.io/pause:3.8": i/o timeout

原因:国内无法直接访问 registry.k8s.io

修复:预配置 kubelet 的 pause 镜像为阿里云源

sudo mkdir -p /var/lib/kubelet
echo 'KUBELET_KUBEADM_ARGS="--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.10.1"' \
  | sudo tee /var/lib/kubelet/kubeadm-flags.env

坑 3:GPG 密钥导入 TTY 问题

现象gpg: cannot open '/dev/tty': No such device or address

原因:非交互 SSH 会话没有 TTY

修复:添加 --batch --yes 参数

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key \
  | sudo gpg --dearmor --batch --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

坑 4:Calico 镜像国内不可达

现象:calico-node 持续 ImagePullBackOff,所有 docker.io 镜像超时

原因:阿里云、DaoCloud 等通用镜像源不缓存 Calico 镜像;K8s APT 包可走官方 CDN,但镜像不行

解决方案:利用已有的腾讯云 CVM(配置了 mirror.ccs.tencentyun.com 镜像加速)

# 在腾讯云 CVM 上拉取(使用 Tencent 企业镜像)
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/node:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/typha:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/kube-controllers:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/csi:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/cni:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/node-driver-registrar:v3.29.2
docker pull --platform linux/arm64 mirror.ccs.tencentyun.com/calico/pod2daemon-flexvol:v3.29.2

# 导出为 tar(549MB)
docker save mirror.ccs.tencentyun.com/calico/* -o calico-arm64.tar

# 传输到 K8s 节点并导入 containerd
scp calico-arm64.tar k8s-master:/tmp/
ssh k8s-master "sudo ctr -n k8s.io images import /tmp/calico-arm64.tar"

# Calico Installation 指定镜像仓库
kubectl apply -f - <<EOF
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  registry: mirror.ccs.tencentyun.com
  calicoNetwork:
    ipPools:
    - cidr: 192.168.0.0/16
      encapsulation: VXLANCrossSubnet
EOF

坑 5:Containerd 镜像加速器配置演变

注意:containerd 1.5+ 已弃用 mirrors 配置,必须使用 config_path 模式。

正确配置方式:

# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

# /etc/containerd/certs.d/docker.io/hosts.toml
server = "https://docker.mirrors.ustc.edu.cn"

[host."https://registry-1.docker.io"]
  capabilities = ["pull", "resolve"]

坑 6:2GB 内存不足以跑 eBPF

现象:启用 eBPF 后 kube-controller-manager 和 kube-scheduler 频繁 CrashLoopBackOff,节点内存仅剩 86MB

原因:eBPF 程序 + Calico-node + etcd + apiserver + controller + scheduler 全跑在 2GB Master 上,内存耗尽

解决:关闭 eBPF(bpfEnabled: false),对于 2GB 节点,iptables 模式更稳定。eBPF 模式建议节点 ≥ 4GB。


部署 Web 应用到集群

boat/web/index.html(STM32 蓝牙遥控小船项目介绍页)部署到 K8s:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: boat-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: boat-web
  template:
    spec:
      containers:
      - name: nginx
        image: docker.io/library/nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: web-content
          mountPath: /usr/share/nginx/html
      volumes:
      - name: web-content
        hostPath:
          path: /var/www/boat
---
apiVersion: v1
kind: Service
metadata:
  name: boat-web
spec:
  type: NodePort
  selector:
    app: boat-web
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

访问地址http://10.211.55.7:30080/


最终集群状态

$ kubectl get nodes -o wide
NAME          STATUS   ROLES           AGE   VERSION   INTERNAL-IP
k8s-master    Ready    control-plane   91m   v1.34.8   10.211.55.6
k8s-worker1   Ready    <none>          90m   v1.34.8   10.211.55.7
k8s-worker2   Ready    <none>          90m   v1.34.8   10.211.55.8

$ kubectl get pods -A
NAMESPACE      NAME                                   READY   STATUS
calico-system  calico-node-*                          1/1     Running
calico-system  calico-typha-*                         1/1     Running
calico-system  csi-node-driver-*                      2/2     Running
default        boat-web-*                             1/1     Running
kube-system    coredns-*                              1/1     Running
kube-system    etcd-k8s-master                        1/1     Running
kube-system    kube-apiserver-k8s-master              1/1     Running

快速开始(全新环境部署)

# 1. 配置 SSH 免密登录
ssh-copy-id k8s-master && ssh-copy-id k8s-worker1 && ssh-copy-id k8s-worker2

# 2. 配置免密 sudo(在每台 VM 上)
echo "parallels ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/parallels

# 3. 克隆部署脚本
git clone https://gitee.com/caobin/boat.git
cd boat/k8s-deploy

# 4. 三台节点并行执行环境配置
ssh k8s-master "bash 01-all-nodes-env.sh k8s-master" &
ssh k8s-worker1 "bash 01-all-nodes-env.sh k8s-worker1" &
ssh k8s-worker2 "bash 01-all-nodes-env.sh k8s-worker2" &
wait

# 5. Master 初始化
ssh k8s-master "bash 02-master-init.sh 10.211.55.6"

# 6. Worker 加入(使用 Master 输出的 join 命令)
ssh k8s-worker1 "bash 03-worker-join.sh auto"
ssh k8s-worker2 "bash 03-worker-join.sh auto"

# 7. 验证
kubectl get nodes -o wide

总结

维度 选型 说明
容器运行时 containerd 1.7.29 K8s 1.24+ 推荐,轻量高效
网络插件 Calico v3.29.2 支持 eBPF 数据面,低延迟
数据面 iptables(生产建议 eBPF ≥4GB) 2GB 节点用 iptables 更稳
镜像加速 Tencent 企业镜像 + USTC 多级 fallback
部署方式 模块化 Shell 脚本 幂等、可复用、注释详尽

Git 仓库https://gitee.com/caobin/boat (k8s-deploy/ 目录)


本文完整的部署 Prompt:

「你是一个精通 Kubernetes、Linux 内核网络优化以及高级 Shell 脚本编写的资深 Devops 架构师。请为我编写一套在 3 台全新安装的 Ubuntu 24.04 虚拟机上部署 K8s 1.34 集群的完整、可直接复制运行的 Shell 脚本和操作指南。该集群需要同时完美兼顾"传统微服务部署"与"高性能 Knative Serverless 服务"的运行要求…」

附录:完整原始部署 Prompt

# Role
你是一个精通 Kubernetes、Linux 内核网络优化以及高级 Shell 脚本编写的资深 Devops 架构师。

# Task
请为我编写一套在 3 台全新安装的 Ubuntu 24.04 虚拟机上部署 K8s 1.34 集群的完整、可直接复制运行的 Shell 脚本和操作指南。该集群需要同时完美兼顾"传统微服务部署"与"高性能 Knative Serverless 服务"的运行要求。

# Infrastructure Info
- 节点规划与主机名修改逻辑:
  - 主机 1:修改主机名为 `k8s-master` ip 10.211.55.6
  - 主机 2:修改主机名为 `k8s-worker1` ip 10.211.55.7
  - 主机 3:修改主机名为 `k8s-worker2` ip 10.211.55.8
- 所有主机的用户名 parallels/sudo 密码均为:`P@ssw0rdA`
- 所有主机名称修改完成后,使用ssh-copy-id 在~/.ssh/config配置免密登录
- 核心版本:Kubernetes v1.34 / Containerd 1.7+ / nerdctl 最新稳定版
- 网络配置:Pod 网络网段 (CIDR) 为 `192.168.0.0/16`
- 网络插件:Calico v3.27+,必须在初始化后自动或通过脚本引导切换为高性能 eBPF 路由模式。

# Script Design Requirements (模块化 Skill 技能包架构)
请将全套部署流程严格拆分为以下 3 个独立的脚本文件形成skill,确保具备基本的幂等性(重复执行不破坏系统),且注释清晰、可读性强:

## 【Skill 1:通用基础环境配置skill脚本】
- 文件名建议:`01-all-nodes-env.sh`
- 执行说明:在 3 台机器上均需运行,支持传入参数作为主机名(如 `bash 01-all-nodes-env.sh k8s-master`)。
- 核心功能:
  1. 自动化修改主机名,并在 `/etc/hosts` 中预留集群 IP 映射模板。
  2. 永久关闭 Swap 分区。
  3. 加载 `overlay` 和 `br_netfilter` 内核模块。
  4. 优化 `sysctl` 系统参数,针对 Serverless 高并发弹缩场景,大幅调高系统文件句柄、inotify 监视上限(`fs.inotify.max_user_watches = 524288`)以及连接跟踪表上限(`net.netfilter.nf_conntrack_max`)。
  5. 安装 `socat`、`conntrack`、`ipset`、`ipvsadm` 等核心依赖。
  6. 安装并配置 Containerd,必须显式开启 `SystemdCgroup = true`,并配置国内镜像加速器(如阿里云镜像源)。
  7. 安装 `nerdctl` 并设置 shell 别名 `alias docker='nerdctl -n k8s.io'`,确保习惯使用 docker 命令的开发者拥有无缝的底层排查体验。
  8. 引入国内 K8s APT 源,安装并锁定 `kubeadm`、`kubelet`、`kubectl` 的 1.34 版本。

## 【Skill 2:控制平面初始化与 Calico eBPF 优化skill脚本】
- 文件名建议:`02-master-init.sh`
- 执行说明:仅在 `k8s-master` 节点上执行一次。
- 核心功能:
  1. 自动获取或允许定义 Master IP,执行 `kubeadm init` 初始化集群(指定 1.34 版本、正确的 Pod CIDR 和阿里云镜像仓库)。
  2. 自动配置当前用户及 root 用户的 `.kube/config` 凭证。
  3. 部署 Calico 官方 Operator 及自定义资源清单(确保网络网段严格匹配)。
  4. **关键网络优化:** 包含完整的自动化指令或配置补丁,将 Calico 的数据面切换为 **eBPF 模式**(移除 Kube-Proxy 的 iptables 拦截,改为 BPF 驱动,开启 DSR 模式),以支撑 Serverless 服务极低的网络延迟和毫秒级冷启动就绪需求。
  5. 在脚本末尾清晰打印用于 Worker 节点加入的 `kubeadm join` 完整令牌命令。

## 【Skill 3:工作节点加入skill脚本】
- 文件名建议:`03-worker-join.sh`
- 执行说明:在 `k8s-worker1` 和 `k8s-worker2` 上执行。
- 核心功能:
  1. 提供干净、可交互的命令块,引导用户粘贴来自 Master 节点的 `kubeadm join` 认证命令并自动执行。

# Output Format
1. 请提供 3 个相互独立、结构清晰、带有对应文件名注释的skill代码块。
2. 脚本内部关键步骤必须包含中文详尽注释,阐明其设计原理(如为何开启 eBPF、为何锁定 K8s 版本等)。

Clash TUN 代理配置(解决 VM 网络受限)

VM 通过 Parallels NAT 共享 Mac 网络,但无法直连 Docker Hub。在 Mac 上开启 Clash (mihomo-party) 的 TUN 模式,VM 流量经 Mac 代理出口。

mihomo.yaml 关键配置

tun:
  enable: true
  stack: mixed
  auto-route: true
  dns-hijack:
    - any:53
  route-exclude-address:
    - 10.211.55.0/24      # 排除 Parallels 虚拟机网段,避免 Parallels 误判盗版
    - 10.37.129.0/24      # 排除 Host-Only 网段

⚠️ 必须添加 route-exclude-address 排除 Parallels 网段,否则 Parallels Desktop 会因网络被代理而提示"非正版"。

containerd HTTP 代理(备选方案)

TUN 对大镜像(Dashboard 等走 CloudFront CDN 的)存在 EOF 断连问题。备选方案是配置 containerd 使用 Clash HTTP 代理:

# /etc/systemd/system/containerd.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://10.211.55.1:7890"
Environment="HTTPS_PROXY=http://10.211.55.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,192.168.0.0/16"
sudo -S -p '' systemctl daemon-reload && sudo -S -p '' systemctl restart containerd

实测 alpinebusybox 等小镜像可正常拉取;大镜像受限于 Clash 代理的 HTTPS CONNECT 转发稳定性。


Kubernetes Dashboard 部署

镜像导入

Dashboard 镜像在国内同样无法直拉,通过腾讯云 CVM 中转:

# 腾讯云拉取
ssh tencent-crm "docker pull --platform linux/arm64 kubernetesui/dashboard:v2.7.0"
ssh tencent-crm "docker pull --platform linux/arm64 kubernetesui/metrics-scraper:v1.0.9"
ssh tencent-crm "docker save kubernetesui/dashboard:v2.7.0 kubernetesui/metrics-scraper:v1.0.9 -o /tmp/dashboard-arm64.tar"

# 传输并导入到 3 节点
scp tencent-crm:/tmp/dashboard-arm64.tar /tmp/
for node in k8s-master k8s-worker1 k8s-worker2; do
  scp /tmp/dashboard-arm64.tar $node:/tmp/
  ssh $node "sudo ctr -n k8s.io images import /tmp/dashboard-arm64.tar && rm /tmp/dashboard-arm64.tar"
done

RBAC 与 Deployment

# ServiceAccount + ClusterRoleBinding
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
---
# Deployment(关键:mount /tmp emptyDir 避免非 root 容器日志写入失败)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    spec:
      serviceAccountName: admin-user
      containers:
      - name: kubernetes-dashboard
        image: docker.io/kubernetesui/dashboard:v2.7.0
        ports:
        - containerPort: 8443
        args:
        - --auto-generate-certificates
        - --namespace=kubernetes-dashboard
        volumeMounts:
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: tmp
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  type: NodePort
  ports:
  - port: 443
    targetPort: 8443
    nodePort: 30443
  selector:
    k8s-app: kubernetes-dashboard

🐛 踩坑:Dashboard v2.7.0 非 root 容器 /tmp 不可写

现象:Pod 启动后立即 CrashLoopBackOff,日志显示 cannot create log: open /tmp/... no such file or directory

原因:Dashboard v2.7.0 默认以非 root 运行,/tmp 目录不可写导致日志初始化失败。

修复:mount emptyDir/tmp(见上面 Deployment YAML 的 volumes 配置)。

🐛 踩坑:Token 末尾的 % 符号

现象kubectl create token 输出的 Token 粘贴到 Dashboard 登录框提示 “Invalid credentials”

原因:zsh 在命令输出末尾自动添加 % 作为行尾标记(partial-line marker),复制时容易带进去。

修复:获取 Token 时过滤:

kubectl -n kubernetes-dashboard create token admin-user --duration=24h | tr -d '\n%'

访问方式

由于 Dashboard 使用自签名证书,通过 SSH 隧道 + kubectl proxy 绕过 HTTPS 证书问题:

# Mac 终端执行 SSH 隧道
ssh -L 8001:127.0.0.1:8001 k8s-master "kubectl proxy --port=8001"

浏览器打开:http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/,粘贴 Token 登录。


最终集群全景

组件 版本/配置 状态
Kubernetes v1.34.8 ✅ 3 节点 Ready
containerd 1.7.29 SystemdCgroup
Calico v3.29.2 iptables ✅ VXLAN CrossSubnet
eBPF 已启用 (4GB Master) ✅ DSR + kube-proxy bypass
boat-web NodePort 30080 http://10.211.55.7:30080/
Dashboard v2.7.0 (kubectl proxy) ✅ localhost:8001
镜像方案 腾讯云 tar 导入 + Clash TUN

生产高可用:MetalLB + NodePort 实现故障无感切换

问题

NodePort 模式下,用户必须知道具体节点 IP。如果访问的节点宕机,服务中断:

❌ http://10.211.55.7:30080/  ← worker1 关机 → 不可用
✅ http://10.211.55.8:30080/  ← worker2 正常 → 可用

生产环境必须通过域名 → 虚拟 IP → 健康检查,实现任意节点宕机后自动切换。

方案选型

方案 适用场景 复杂度
MetalLB L2 模式 裸金属 / 本地 VM 集群 ⭐⭐ 推荐
云 LB(SLB/ELB/CLB) 云上 K8s
外部反向代理(Nginx/HAProxy) 已有独立 LB 基础设施 ⭐⭐⭐
Ingress Controller + 外部 LB 需要 7 层路由/SSL 终止 ⭐⭐⭐

MetalLB Layer 2 架构

用户浏览器
    │
    ▼
boat.example.com  ──DNS──▶  192.168.0.100 (VIP, 由 MetalLB 分配)
                                  │
                    ┌─────────────┼─────────────┐
                    ▼             ▼             ▼
              k8s-master     k8s-worker1   k8s-worker2
              (NodePort)     (NodePort)    (NodePort)
                    │             │             │
                    └─────────────┼─────────────┘
                                  ▼
                            boat-web Pod
                           (任意存活节点)

工作流程

  1. MetalLB 从地址池分配一个虚拟 IP(如 192.168.0.100
  2. 集群中某个存活节点通过 ARP 应答声称拥有此 IP
  3. 所有流量到达该节点 → kube-proxy/BPF → 分发到健康 Pod
  4. 当该节点宕机,另一个节点在几秒内接管 VIP
  5. DNS 始终指向同一 VIP,用户完全无感

部署步骤

# 1. 安装 MetalLB(K8s 1.34 兼容 v0.14+)
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml

# 2. 等待 MetalLB 就绪
kubectl wait --namespace metallb-system \
  --for=condition=ready pod \
  --selector=app=metallb \
  --timeout=120s

# 3. 配置 IP 地址池(与 Node 同网段)
kubectl apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: boat-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.211.55.100-10.211.55.110    # 与 VM 同网段,选未占用的 IP
EOF

# 4. 创建 L2 广播通告
kubectl apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: boat-l2
  namespace: metallb-system
spec:
  ipAddressPools:
  - boat-pool
EOF

# 5. 将 Service 改为 LoadBalancer 类型
kubectl patch svc boat-web -p '{"spec":{"type":"LoadBalancer"}}'

# 6. 查看分配的 VIP
kubectl get svc boat-web
# EXTERNAL-IP: 10.211.55.100  ← 这就是虚拟 IP

故障切换测试

# 拿到 VIP 后,域名指向它
echo "10.211.55.100 boat.bmap.xyz" | sudo -S -p '' tee -a /etc/hosts

# 持续访问(不中断)
while true; do curl -s -o /dev/null -w "%{http_code}\n" http://boat.bmap.xyz:30080/; sleep 1; done

# 另开终端,关掉当前持有 VIP 的节点
# 观察 HTTP 状态码是否从 200 → 短暂中断(几秒) → 恢复 200

典型切换时间:2-5 秒(ARP 广播 + 新节点接管)。

与云 LB 对比

特性 MetalLB L2 云 LB
流量路径 所有流量经单节点 云平台分发到所有节点
带宽瓶颈 受单节点网卡限制 无单点瓶颈
切换速度 2-5 秒(ARP) 通常 <1 秒
成本 免费 按流量/实例计费
适用场景 裸金属、VM、边缘 云上生产环境

结论:小规模集群(3-10 节点)MetalLB L2 完全够用。流量大到单节点网卡瓶颈时,升级到 Ingress + 云 LB 或 MetalLB BGP 模式。