
当你的节点不在同一个局域网里组建k8s集群这件事就从“照着安装文档敲命令”变成了“先把网络拓扑想明白”。我最近刚把三台分布在不同地域、不同运营商网络里的服务器拉进同一个集群一台放在公网机房有独立公网IP另外两台分别在两个不同的内网环境里平时只能通过出口公网IP访问互联网。最终集群顺利跑起来工作节点分别加入跨节点Pod也能互通。整个过程踩了不少坑这里把完整思路、命令和排错过程都整理出来给同样要做跨局域网集群的人参考。如果你只是想把两台机器临时组个测试环境或者公司内部有多个分支机构的服务器要统一纳管这篇文章都适用。文章默认你已经有k8s基础概念知道Node、Pod、Service是什么但对跨网络组网比较陌生。我会按“场景拆解 → 拓扑设计 → 控制面部署 → 工作节点接入 → 问题排查”的顺序讲尽量把每个决定背后的原因也说清楚这样你遇到不同环境时也能灵活调整。1. 场景拆解跨局域网组集群难在哪1.1 三个绕不开的网络问题跨局域网组k8s集群核心难点不在于k8s本身而在于网络。我总结下来绕不开三个问题。第一个是控制面地址可达性。kubelet、kubectl、kube-proxy这些组件都要访问apiserver而apiserver的地址必须是所有节点都能访问到的。单机房场景里大家都有同一个内网网段里的IP直接写进配置就行跨局域网场景里节点可能来自完全不同的网段有的在防火墙后面有的只有私有IP你得找到一条所有节点都能访问的路径。第二个是证书的SAN覆盖。Kubernetes的证书默认签的是初始化时指定的IP和域名。如果某个节点是通过公网IP或域名访问apiserver但这个地址没写进SAN那么apiserver证书校验就会失败节点永远无法正常join。这个问题在跨网络环境下尤其常见因为你要在init之前把所有可能访问的地址列全。第三个是Pod网络的跨节点通信。集群组起来之后Pod与Pod之间要通信而不同节点可能分属不同局域网二层广播根本过不去。CNI插件必须走Overlay封装把Pod流量封装到节点之间的三层链路里默认的VXLAN/UDP端口必须放行MTU也要配套调整。除这三个核心问题之外还有些软性因素节点时间不一致会导致证书校验失败节点名重复会导致kubelet注册错乱内网链路质量差的话etcd选举会频繁超时。这些问题单看起来都是小事叠加在一起就很磨人。1.2 为什么不能直接照搬单机房教程网上大多数k8s搭建教程默认所有节点在同一个内网于是会直接写“把--apiserver-advertise-address设为内网IP”“join命令直接用内网IP”。你在内网环境里操作当然没问题一旦跨局域网这个内网IP在其他节点眼里就是一个不可达地址。很多人卡在这一步反复执行join却总提示连接超时其实不是k8s配置错了而是“路径本来就不通”。另一个常见误区是把NAT端口映射当成万能药。实际上一台工作节点主动访问apiserver只需要出方向流量根本不需要为它做入站端口映射反倒是控制节点必须把6443等端口暴露给所有工作节点而且暴露的地址必须跟证书SAN对得上。这两个认知如果不纠正后面的方向就全偏了。还需要注意一个隐蔽问题不同局域网的私有网段可能互相重叠。比如分支机构A用的是192.168.1.0/24分支机构B也用的是192.168.1.0/24这种地址冲突在同机房环境几乎不会遇到但在跨网络环境下很麻烦。设计阶段如果不先统一整理各节点的网段信息后面做路由和CNI网段规划时会非常痛苦。1.3 整体方案的轮廓我采用的拓扑很朴素挑一台有公网IP的云主机作为控制节点它跑完整的控制面组件和内置etcd另外两台内网主机作为工作节点kubeadm join时通过公网Endpoint访问apiserver。Pod网络用Overlay模式让跨节点Pod流量封装后经由公网链路传输。这个方案不依赖复杂的负载均衡器或额外的专线适合中小规模集群。这个设计的关键在于“单一入口”所有节点只要能看到控制节点的公网地址就够了。控制节点本身上行带宽要留足因为工作节点之间的Pod流量会经过它所在的路径如果所有流量都走Overlay的话但只要不是跑大数据传输业务这种拓扑完全够用。如果以后要扩展到更多地域建议把控制节点换成“公网负载均衡入口 多个控制节点”的模式但那是另一个复杂度级别。当前场景里单控制面足以把流程跑通。2. 拓扑设计与前置选型2.1 用一张表把节点规划清楚动手之前先把节点信息整理成表。我强烈建议不要直接在脑子里规划因为跨局域网场景里“内网IP”和“公网可达地址”是两套东西写下来才能避免混淆。节点角色所在网络内网/私有IP公网可达地址control-plane公网机房172.16.0.10203.0.113.10worker-1局域网A192.168.10.20203.0.113.20出口地址worker-2局域网B192.168.50.30203.0.113.30出口地址注意203.0.113.0/24是文档保留地址段这里仅作示例。真实环境里你只要把“公网可达地址”这一列填准后面所有操作都以这一列为准。工作节点所在局域网的出口IP也需要确认虽然工作节点不主动暴露端口但在排查链路问题时你会需要知道它的出口地址。另外我还会顺手记录每台节点的操作系统版本、内核版本、计划安装的k8s版本、节点角色、是否已有容器运行时等信息。这些信息在跨网络协作时特别有用因为你不可能像单机房那样随时插到交换机上排查。2.2 控制面要暴露哪些端口跨局域网场景下端口规划比单机房严格。控制节点需要放行的入方向端口如下6443kube-apiserver所有节点和kubectl客户端都要访问。2379/2380etcd客户端端口和集群端口。单控制节点时只有本机访问但如果后续要加控制节点2380必须对新增控制节点开放。10250kubelet调度器和监控组件要访问。10251/10252kube-scheduler、kube-controller-manager的健康检查端口。10256kube-proxy健康检查端口视版本而定。8285/8472flannel VXLAN或Calico VXLAN端口。工作节点的出方向一般不需要额外放行但如果你要在工作节点上用kubectl执行命令比如在本地调试工作节点到apiserver的6443端口必须能通。另外节点间有时需要互相访问Pod网段所以VXLAN端口在每个节点上都要放行。提示不要把防火墙直接关掉。单机实验时关防火墙最省事但跨局域网场景里节点可能分布在多个安全域贸然关防火墙会有后续隐患。建议按端口最小化放行并逐一验证。云服务器上的安全组规则也要同步检查。我自己就遇到过节点内iptables全放开了但云平台安全组在上层把UDP端口丢弃导致Calico VXLAN报文发不出去的诡异问题。这种双层防火墙的坑只有亲历过一次才知道多隐蔽。2.3 域名和证书SAN怎么规划证书SAN要覆盖“所有节点访问apiserver时可能使用的地址”。最简单的情况下就是控制节点的公网IP203.0.113.10。如果客户端走域名访问比如k8s.example.com还要把域名加进去。如果以后会扩容再添上负载均衡器的VIP地址。这里有一个关键点如果你给apiserver配置了--control-plane-endpoint那么这个endpoint地址也必须在SAN里。kubeadm init时--control-plane-endpoint和--apiserver-cert-extra-sans是两个不同的参数前者用来生成join命令和集群配置后者专门控制证书SAN。很多人漏了后者导致join时证书报错。以我的规划为例SAN至少包含203.0.113.10172.16.0.10控制节点的内网IP便于控制节点本地访问k8s.example.com如果有把SAN规划好了后面的kubeadm init才有意义。否则证书一生成少一个地址就得重新来跨网络环境下重新初始化的成本比单机房高得多。3. 控制面部署实操3.1 基础环境准备以下所有操作都建议用root用户执行。系统我用的是Ubuntu 22.04CentOS 7.9/8/9也基本兼容只是包管理器命令不同。先把系统基本组件装齐关闭swap开启ip_forward设置桥接流量经过iptables。具体命令如下swapoff -a sed -i /swap/s/^/#/ /etc/fstab cat EOF | tee /etc/sysctl.d/k8s.conf net.ipv4.ip_forward 1 net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sysctl --system这里解释一下为什么必须开ip_forward。Kubernetes的Service转发依赖kube-proxy的IPVS/iptables模式而跨节点的Pod流量经过Overlay封装后在宿主机的路由层面需要做三层转发。如果你不开ip_forward哪怕CNI插件装好了Pod之间的数据包也无法被节点转发表现就是跨节点访问超时。另外一个容易被忽略的是内核模块。Calico的IPIP/VXLAN模式需要加载ipip、vxlan这些模块可以用modprobe ipip和modprobe vxlan手动加载或者让系统开机自动加载。如果模块没加载CNI Pod可能会启动但节点间的Overlay接口就是起不来日志里全是报错。容器运行时我选的是containerd。装好之后重点检查一下cgroup driver是否配置为systemd否则kubelet和containerd的cgroup管理方式不一致节点会反复NotReady。cat /etc/containerd/config.toml如果没有systemd_cgroup配置就直接生成一份默认配置再改containerd config default | tee /etc/containerd/config.toml # 找到 [plugins.io.containerd.grpc.v1.cri] # 设置 SystemdCgroup true改完重启containerd。这个细节不处理好后面很难排查。3.2 安装kubeadm、kubelet、kubectl版本统一很重要。我这里以v1.28.x为例所有节点用同一个版本。装完后执行kubeadm init之前先拉取所需镜像kubeadm config images pull这一步如果镜像拉取慢会卡很久。建议提前配置好镜像加速或离线导入不要在集群初始化途中干等。跨网络环境里控制节点和工作节点可能各自连不同的网络镜像是否提前就绪直接决定init和join能不能快速完成。安装kubelet时还有一个小坑默认的kubelet会尝试通过cgroupfs管理资源如果你已经给containerd设置了systemd cgroup driver记得把kubelet的启动参数也对齐。具体做法是在/etc/default/kubelet里加上KUBELET_EXTRA_ARGS--cgroup-driversystemd否则节点ready之后过几分钟又变NotReady。3.3 kubeadm init初始化控制节点关键命令如下kubeadm init \ --control-plane-endpoint203.0.113.10:6443 \ --apiserver-cert-extra-sans203.0.113.10,172.16.0.10,k8s.example.com \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/16 \ --kubernetes-versionv1.28.2这里有几个细节要展开讲。第一--control-plane-endpoint不一定要和apiserver实际绑定地址一致但必须能被所有节点解析和访问。这个值会写进生成的kubeconfig里后续kubectl和kubelet都靠它连apiserver。第二--apiserver-cert-extra-sans一定要写全。我一开始只写了公网IP导致控制节点本机通过内网IP访问时报证书SAN错误又得重新做证书。后来把内网IP和域名一起加进去一次性通过。第三--pod-network-cidr要避开所有节点所在的真实网段。比如你的局域网A是192.168.10.0/24局域网B是192.168.50.0/24那Pod网段就不能再用192.168.x.x否则跨节点流量会被节点路由表当成“发往内网网段”来处理走错路。我选10.244.0.0/16就是为了避开冲突。初始化完成后按提示执行mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config然后用kubectl get nodes查看这时候控制节点应该处于NotReady状态因为还没有安装CNI。3.4 部署CNI必须用Overlay模式跨局域网场景CNI插件我推荐Calico并明确选择VXLAN/Overlay封装模式。原因很直接节点之间没有二层连通性IPIP和BGP模式都需要节点能直接路由到对端Pod网段在NAT和防火墙后面往往走不通。VXLAN模式把Pod的L2帧封装在UDP里只要节点之间的三层链路能通Pod就能通信。安装Calico的通用步骤大概是先获取manifest修改其中的网络模式为VXLAN并确认Pod网段和init时一致然后apply。执行成功后大概等一两分钟控制节点的状态会变成Ready。注意安装CNI之后千万不要为了省事把CNI的DaemonSet调成hostNetwork。跨网络环境下宿主机的网络栈无法直接路由到其他节点的Pod网段只有Overlay网络才能兜住。hostNetwork在这里不是性能优化是给自己挖坑。另外如果链路相关参数不理想还需要调整Calico的MTU设置。VXLAN一般会占用一定字节的封装开销底层链路如果只能承载较小的MTU封装后的报文容易在链路上被分片或丢弃。这种问题表现很诡异小包能通大包超时。遇到这种情况直接把Calico的MTU调小配合路由表逐一验证。部署结束后检查一下CNI Pod是否都在Running状态。如果某个节点上的calico-node一直CrashLoopBackOff多半是节点的内核模块没加载或者VXLAN端口被防火墙挡了。4. 边缘工作节点加入集群4.1 工作节点初始化检查工作节点的环境准备和控制节点基本一致装containerd、开ip_forward、关swap、装同版本的kubelet/kubeadm。唯一要注意的是工作节点上不要执行kubeadm init只做准备工作。节点名必须唯一并且需要在/etc/hosts里能正确解析。如果多台机器hostname重复注册到集群后kubelet日志会出现冲突表现就是一会儿Ready一会儿NotReady。跨网络环境里这种问题尤其容易出现在两台机器从同一个镜像模板克隆出来的情况。另外时间同步非常重要。证书校验机制依赖机器时间如果某个节点的时间跟控制节点差了几分钟join时会出现“x509 certificate has expired or is not yet valid”之类的错误。建议在所有节点上都配置NTP服务不差这点功夫。4.2 执行join命令控制节点初始化完成后输出里已经给出了join命令和证书hash。如果当时没记下来可以重新生成kubeadm token create --print-join-command把生成的命令拿到工作节点执行比如kubeadm join 203.0.113.10:6443 \ --token 1abc2.def345ghi678jklm \ --discovery-token-ca-cert-hash sha256:xxx这里要强调一点工作节点是通过出方向访问控制节点的它只需要能主动连到203.0.113.10:6443不需要在工作节点的防火墙上做任何入方向端口映射。很多人在这一步卡很久以为必须给工作节点做NAT映射才能让控制节点访问它其实完全不用。反过来如果join时报“connection refused”或超时优先检查控制节点的6443端口是否真的对所有来源开放。join完成后回到控制节点执行kubectl get nodes应该能看到工作节点出现在列表里状态从NotReady变成Ready。如果一直是NotReady大概率是CNI或者cgroup driver配置问题按第五节的排查表处理。这里我再补充一个经验join命令里的token有效期默认只有24小时。如果你把join步骤推迟到第二天token可能已经失效所以更稳妥的做法是每次执行join前都用kubeadm token create --print-join-command重新生成一次确保命令里的token和hash都是最新的。4.3 验证跨节点Pod通信节点全部Ready只是第一步我更关注Pod之间的真实连通性。简单验证方式是部署一个nginx副本并设置反亲和把Pod调度到不同节点上kubectl create deployment nginx --imagenginx:latest --replicas3 kubectl expose deployment nginx --port80然后进入任意一个Podcurl其他Pod的IP。如果不通第一反应不是改防火墙而是检查CNI的overlay状态kubectl get pods -n kube-system | grep calico同时到工作节点上看VXLAN网卡是否正常up以及8472/4789端口是否对外开放。这一步最容易翻车的地方是云平台的安全组没有放行VXLAN端口。很多人把节点的iptables放开了但云平台安全组在上层把UDP端口直接丢弃表现就是“小包偶尔通、大包必丢”非常隐蔽。还有一个验证思路是直接在节点上用ip route查看路由表。正常情况下Calico会在每个节点上写入到其他节点Pod网段的路由下一跳是对端节点的宿主机IP。如果这条路由缺失要么是CNI没装好要么是节点的网段信息对不上。5. 常见问题与排查速查表5.1 apiserver地址不通症状join命令执行后一直卡在等待或者kubelet日志里反复报连接超时。排查步骤在工作节点上用telnet或nc测试控制节点的6443端口是否通。确认控制节点的防火墙和安全组都已放行6443。确认join用的地址跟控制节点的--control-plane-endpoint一致。这个排查基本能把80%的问题定位清楚。如果端口通了但join仍然失败再看5.2的证书问题。5.2 证书SAN不匹配症状执行kubectl get nodes或join时报x509 cannot validate certificate for 203.0.113.10 because it doesnt contain any IP SANs。原因就是初始化时--apiserver-cert-extra-sans没有包含访问地址。临时缓解可以把对应IP加到kubeconfig的server字段但根治还是在init阶段写全SAN。如果节点已经初始化完才发现可以重新生成apiserver证书再重启kube-apiserver或者最干脆的方式是重新init一次在测试阶段重来成本最低。提示不要试图直接删除/etc/kubernetes/pki下的apiserver证书来触发重新生成这往往会导致更深的证书链不一致问题。一般用kubeadm certs renew或kubeadm init重置更可控。如果你已经加了SAN但还是报错看看是不是中间有证书缓存。有时候kubelet会把旧的apiserver CA证书缓存下来需要重启kubelet或清空/etc/kubernetes/pki再重新join。但这一步要非常谨慎最好先备份整个目录。5.3 跨节点Pod不通症状Pod都处于Running状态但一个节点上的Pod访问另一个节点上的Pod超时。原因通常有三个CNI没选Overlay模式VXLAN端口被上层安全组挡掉MTU不一致导致大包分片丢包。排查顺序先看CNI Pod是否Running再看节点上VXLAN网卡是否存在且已封装目标IP再抓包看UDP包是否到达对端。三步走完基本能定位。我遇到的真实案例是安全组放行了TCP端口但漏了UDP 8472结果小包能通、大包必丢最终靠tcpdump定位到是UDP端口被丢。5.4 token过期与证书续期token有效期默认24小时。如果join命令是昨天生成的今天执行大概率报token过期。重新执行kubeadm token create --print-join-command即可。集群运行一段时间后还要注意控制面证书到期问题。默认证书有效期一年到期前用kubeadm certs renew all续期并把新证书同步到所有控制节点。这条在工作节点上不需要操作但控制节点的kubeconfig文件可能需要手动更新。这里有个容易忽略的点kubeadm certs renew all执行之后apiserver并不会自动加载新证书通常需要重启kube-apiserver容器或重新生成static pod。如果你用了高可用控制面还要把新证书同步到备用控制节点。这个环节别偷懒否则续期后反而可能把集群搞挂。5.5 节点时间不一致症状join时证书报“not yet valid”或“expired”很可能是节点本地时间还没同步。解决配置NTP或systemd-timesyncd让所有节点时间一致。这件事看似无关紧要但跨局域网场景下节点分布在不同的网络环境时间漂移更容易发生。公网机房的节点可能走NTP没问题但内网节点访问公网NTP服务器可能被防火墙限制需要内部NTP源。6. 实操心得与后续扩展6.1 跨局域网集群的可靠运行技巧经过这次实操我最大的体会是跨局域网组k8s网络拓扑决定一切。你可以在控制面、证书、CNI这些环节做很多优化但如果一开始的“节点如何互相访问”没想清楚后面所有问题都会反复回到这里。为了让集群在公网链路下运行得更可靠我在实际项目里还做了一些调整。比如给kubelet加上--node-labels的region标识在调度时尽量把业务Pod落到同一网络的节点上减少跨公网的Pod流量。实测下来同网络节点间的Pod通信延迟在1毫秒以内跨网络一跳就得几十毫秒业务对延迟敏感的话一定要做这类亲和性调度。再比如对外暴露服务时我优先用NodePort或Ingress而不是直接暴露Pod网段。原因很简单Pod网段本来就是Overlay网络把它直接暴露到局域网之外会让整个网络边界更复杂安全审计也很难做。6.2 给新手朋友的几个建议第一别急着追求高可用。跨局域网场景下高可用的复杂度不只是在DaemonSet上控制面要处理etcd成员、负载均衡、证书同步一系列问题先把单控制面的路子走通再谈HA。我曾经见过有人一开始就搭三个控制节点结果证书同步不一致整批工作节点加入失败花了整天才定位到问题。第二在正式初始化之前先准备好一份完整的检查脚本把所有节点的内核参数、容器运行时、时间同步一次性确认完能省很多事。跨网络环境下没办法像单机房那样挨个节点登录排查能被脚本自动化检查的部分就不要靠人肉巡检。第三kubeadm的日志已经足够详细遇到问题先看journalctl -u kubelet别凭感觉改配置。很多跨网络问题其实是网络链路本身的细节造成的比如MTU、UDP端口、安全组方向规则这些靠猜很难猜中但日志和tcpdump会告诉你真相。最后说一个实操技巧因为不同局域网内的服务器可能相隔很远链路质量不可控建议优先选择与控制节点链路质量最好的那台机器作为主力工作节点把核心业务Pod都调度过去边缘节点只承担相对独立的业务。我这次就是先在同一网络的机器上把集群功能全部验证通过再逐步接入异地节点分层排查的思路帮我把问题范围缩小了很多——先解决可达性、再解决证书、再解决通信一层一层剥下来最终把跨局域网集群稳稳跑起来了。希望这份经验也能帮你少走一段弯路。