跨云缝合怪:记一次多云 K3s 集群组网的血泪史(Flannel NAT 穿透之坑)
跨云缝合怪记一次多云 K3s 集群组网的血泪史Flannel NAT 穿透之坑在云原生玩家的日常折腾中“白嫖”各大云厂商的免费/吃灰资源组建一个大一统的 Kubernetes 集群绝对是一件极具诱惑力的事情。最近我打算把手头散落在阿里云、AWS 和 Oracle Cloud (OCI) 的机器整合起来用 K3s 搭一个轻量级的跨云混合集群并在上面跑一套完整的 ArgoCD。本以为 K3s 主打一个“开箱即用”结果却在跨云网络Flannel上踩进了一个巨大的“天坑”。这篇文章记录了我们整个踩坑、排错以及最终利用 Tailscale 完美破局的全过程。1. 我们的“万国牌”机器阵容这套集群的物理拓扑横跨了三大云厂商机器配置也参差不齐Master 节点 (Control Plane)阿里云 (广州)2C 2G作为集群的总指挥部负责承载 API Server 等核心组件。Worker 节点 (Data Plane)AWS EC2 (新加坡)1C 1Gaws-moon-proxy主力 Worker承载 ArgoCD 的 Repo Server 和 Redis。Oracle Cloud (OCI - AMD VM 1)1C 1Gfree-amd-vm跨云打工人 1 号。Oracle Cloud (OCI - AMD VM 2)1C 1Gfree-amd-vm2跨云打工人 2 号。我们的目标是让这 4 台跨越半个地球的机器组成一个内网互通的 K3s 集群。2. 梦魇的开始看似美好的公网直连最开始的思路很简单粗暴既然大家都有公网 IP那我直接通过公网 IP 互联不就行了于是在 Master 节点上我们指定了--tls-san 阿里云公网IP。在各大海外 Worker 节点上我们通过--node-external-ip 公网IP强制注册并连接到 Master 的公网 IP。敲下命令后一切看起来无比顺利$ kubectl get nodes NAME STATUS ROLES AGE VERSION aliyun-master Ready control-plane 10m v1.35.5k3s1 aws-moon-proxy Readynone2m v1.35.5k3s1 free-amd-vm Readynone2m v1.35.5k3s1...但是当我们满心欢喜地部署完 ArgoCD 后灾难降临了Pod 疯狂卡死ArgoCD 的各种 Controller 持续卡在CreateContainerConfigError或是Terminating状态。跨节点通信全挂在阿里云的 Pod 试图访问 AWS 上的 Pod 时全部报502 Bad Gateway或者Timeout。运维指令失效当我们在 Master 节点执行kubectl exec或是kubectl logs试图进入海外 Worker 节点上的容器时指令直接卡死超时。资源雪崩大量的底层 TCP 握手重试和挂起的网络连接甚至直接把原本只有 1G 内存的海外节点给 OOM内存溢出干崩了。3. 深入底层为什么 Flannel 处理不了云厂商的 NAT经过痛苦的抓包和排查我们发现了罪魁祸首K3s 默认使用的 Flannel 网络插件VXLAN 模式与云厂商的网络架构存在不可调和的矛盾。在 AWS 和 OCI 的机器里如果你敲下ip a你会发现网卡绑定的根本不是公网 IP而是172.x.x.x或10.x.x.x这样的内网 IP。云厂商的公网 IP 实际上是挂在外层的网关上通过1:1 NAT网络地址转换映射给你的机器的。这就是著名的“NAT 穿透陷阱”封包阶段当阿里云上的 Pod A 要发数据给 AWS 上的 Pod B 时阿里云的 Flannel 根据注册信息将数据包用 UDP 8472 端口进行 VXLAN 封装并将目标 IP 写为 AWS 的公网 IP。NAT 转换这个封包跨越汪洋大海来到 AWS 的外层网关。AWS 网关一看“这包是给我的公网 IP 的”于是将其 NAT 转换为内网 IP172.31.x.x丢给对应的 EC2 实例。拆包丢弃致命一击AWS 机器上的 Flannel 收到数据包后解开外层一看——“等一下这个 VXLAN 包里面记录的 Target IP 怎么是公网 IP我的网卡明明是 172.31.x.x 啊”。Linux 内核的网络栈瞬间产生“精神分裂”认为这个包不是发给自己的直接将其无情丢弃Drop。这就是为什么kubectl get nodes能通因为 kubelet 是主动向 API Server 发起连接的单向请求但只要涉及 Master 主动向 Node 发起连接如 exec/logs或者 Pod 跨节点通信双向路由就会彻底陷入网络黑洞。4. 破局之道用 Tailscale 构建 Overlay 虚拟大内网既然公网直连会被 NAT 网关和 Flannel 的底层逻辑“卡脖子”那我们就换个思路在机器之间拉一根虚拟的“物理网线”。我们引入了基于 WireGuard 的大杀器Tailscale。我们在 4 台机器上全部安装了 Tailscale组建了一个专属的虚拟局域网网段为100.x.x.x。Tailscale 会在每台机器的系统里强行插入一张名为tailscale0的真实虚拟网卡。接着我们对 K3s 进行了重构放弃使用公网 IP 互联强制绑定 Tailscale 网卡在 Aliyun Master 节点上重新启动 K3scurl-sfLhttps://get.k3s.io|sh-s- server\--node-ip100.114.103.101\--flannel-ifacetailscale0在 AWS 和 OCI 的 Worker 节点上重新加入集群curl-sfLhttps://get.k3s.io|K3S_URLhttps://100.114.103.101:6443K3S_TOKENxxxsh-s- agent\--node-ip当前机器的Tailscale IP\--flannel-ifacetailscale0奇迹发生了。K3s 的 Flannel 现在只认tailscale0这张网卡。当数据包需要跨云时Flannel 看到源 IP 是 100.x目标 IP 也是 100.x。数据包被交给 Tailscale。Tailscale 在底层使用 WireGuard 将其加密并利用其极其强悍的 NAT 打洞P2P Hole Punching能力直接在广州机房和新加坡机房的公网之间建立了点对点的 UDP 直连通过tailscale status我们可以清晰地看到流量没有绕路中转服务器而是直接走了公网直连direct100.104.30.75 aws-moon-proxy active; direct 13.212.67.185:41641 100.67.168.10 free-amd-vm active; direct 161.118.250.97:416415. 最终成果得益于 Tailscale 的加持我们获得了一个极为纯净且安全的跨云 K8s 运行环境零丢包广州与新加坡/日本之间的跨云 PING 延迟稳定在70ms ~ 100ms0% 丢包。绝对安全所有的跨云通信都被 WireGuard 进行了高强度加密并完全隐匿了原本暴露在公网的 Kubernetes 端口6443/10250 等无视各种恶意扫描。完美运行之前疯狂报错卡死的 ArgoCD在更换为 Tailscale 内网后几十个 Pod 瞬间拉起并变为绿色的Running状态丝滑无比。结语在多云/混合云场景下折腾 K3s 时千万不要试图去和各大云厂商复杂的公网 NAT、安全组以及 Flannel 的底层路由死磕。引入 Tailscale 这类 Overlay 网络将复杂的公网拓扑降维打击成一个纯净的“本地局域网”才是最优雅、最省头发的终极解决方案。