你添加了一台中继主机,把它的 Nebula 地址写进配置,但两个对端仍然无法通信。这是因为中继路径需要三处彼此独立的声明保持一致,而其中只有一处位于你很可能编辑过的那台机器上。我追查过两次:一次是转发主机从未被赋予中继角色,另一次是设置写在了连接的错误一侧。下面的顺序依次检查角色、通告和权限,每一步都有一条命令告诉你是否继续。

编辑配置之前先梳理三种角色

Nebula 给每台主机一个职责,而职责不会继承。灯塔回答“这个对端在哪里”的问题,让主机能够互相找到。中继在两台没有可用直连路径的对端之间转发流量。普通主机两者都不做。真正重要的三项设置位于三台不同的机器上,所以一份看起来完整的客户端配置仍可能让两个对端一直等待。

# on the discovery host
lighthouse:
  am_lighthouse: true
  hosts: []

# on every other host, point at the discovery host's Nebula address
lighthouse:
  hosts:
    - 10.42.0.1

一个症状可以区分两类故障。如果两个对端互相列出,而日志显示始终无法完成的握手尝试,说明发现机制正常,数据路径被 NAT 或防火墙阻断。如果某个对端根本学不到对方的地址,问题就在灯塔或其静态主机映射上,中继设置无法修复。

在编辑任何其他内容之前,先确认每台主机实际加载的是哪份配置。某个单元读取的路径与你编辑的文件不同,是“改了也没用”常见又尴尬的原因:

systemctl cat nebula | grep ExecStart
nebula -version
journalctl -u nebula -n 20 --no-pager

在混合机群里,版本行很重要。中继处理、打洞行为和可接受的配置键在不同版本之间并不相同,所以不要假设旧版本能理解它并未为之编写的段落。把主机对齐到同一个次版本系列,然后在每台主机上运行 nebula -test -config /etc/nebula/config.yml,并在调试代码尚未实现的传输方式之前比较输出。

在中继主机上启用转发角色

中继角色是转发主机上的一个标志,与作为灯塔是两回事。启用了发现但关闭了中继转发的主机仍然只是发现服务。在中继上:

relay:
  am_relay: true

这个标志声明意图,并不创造可达性。中继必须能被两个对端都访问到,实践中意味着公网地址、在主机防火墙和任何云安全组中开放 Nebula 端口的入站 UDP,以及前面没有双重 NAT。先检查监听套接字,再观察对端的数据包是否到达:

ss -lunp | grep nebula
tcpdump -ni any udp port 4242 and host 198.51.100.7

两个限制能在后面省下时间。中继不能通过另一个中继到达,所以配置里的一串中继名称并不能代替给转发主机一条可用的路径。而且中继只转发数据包,不授予权限,所以被中继的流量是否允许仍由目的端自己的入站规则决定。角色及其要求见中继配置文档。

从目的端通告中继,而不是从客户端

这就是最耗时的方向颠倒。中继列表属于你试图到达的那台主机,因为它告诉其他对端可以用哪些中继来到达该主机。把它放在发起连接的客户端上,对客户端需要的方向毫无作用:

# on the DESTINATION host: relays others may use to reach me
relay:
  relays:
    - 10.42.0.5

对端向灯塔登记时会连同自己的地址一起上报该列表,所以通告是经由发现机制传播,而不是通过直接交换。如果目的端无法到达任何灯塔,它的中继列表就永远不会传播,配置正确的中继也只能闲置。这种故障看起来和坏掉的中继一模一样,但其实不是。

在同一轮里给目的端一条引导路径。静态主机映射把 Nebula 地址绑定到公网端点,让第一次握手有地方可去;如果目的端要接受来自无法直连主机的被中继流量,就需要为那台中继添加一条记录:

static_host_map:
  "10.42.0.5": ["203.0.113.10:4242"]

允许发起连接的对端使用中继

发起方需要自己的权限。relay.use_relays 默认开启,所以值得排查的情况是显式的 false,它常常是从中继功能出现之前编写的模板里复制来的,或者来自早先强制走直连路径的尝试残留:

relay:
  use_relays: true

这一侧的权限并不是唯一的关口。目的端的入站规则仍然适用于被中继的数据包,而它们匹配的源是隧道内部的标识,不是中继自身的地址。当被中继的握手完成而应用仍然失败时,先读目的端日志中记录的源,再改写规则。

NAT 穿透设置与这一切相互作用。punchy.punch 帮助两个对端打开直连路径,成功时根本没有中继流量可观察;那是成功,不是中继缺失。时好时坏的一对通常意味着打洞有时成功,所以要多测几次这对主机,而不是凭一次尝试下结论。

用失败的那一对验证,然后重新加载

用它们需要的应用去测试真正失败的那两台主机。从你的工作站到中继自身服务的 ping 成功,完全不能证明另外两个对端之间的转发,而这是整个流程中最常见的假阳性。

journalctl -u nebula -f | grep -iE "relay|handshake"
ping -c 3 10.42.0.9

用重新加载代替重启,并确认正在运行的进程读取了你编辑的文件。重启会切断所有隧道,让下一次测试更难解读;而 SIGHUP 保留进程并让它重新读取配置:

sudo systemctl kill -s HUP nebula
# same signal when nebula runs in the foreground
kill -HUP $(pidof nebula)

systemctl reload 只有在本地单元定义了 ExecReload 时才有效,而项目附带的单元并没有定义,所以在标准安装上能成功的是 systemctl kill -s HUP。重新加载之后,查看日志中进程打开的配置路径,确认读到的是新文件,而不是你以为的那个。

中继设置和 NAT 打洞都作用于路径,两者都不作用于身份。如果在运行配置中确认了 am_relay、relays 和 use_relays 之后这一对仍然握手失败,就检查两台主机是否都出示了为其配置的名称签发的证书,以及两侧的 CA 是否相同。保持证书和防火墙配置不变,一次只改一项传输设置,因为证书修改和中继修改落在同一次测试里,就说不清是哪一个推动了握手。名称或 CA 不匹配会失败在从未找到中继时的同一个位置,所以这项反向核对也属于同一轮。

然后确认每条声明都在它所属的文件里:

  • 转发主机上的 am_relay: true,并且该主机从两侧都能通过 UDP 访问。
  • 在目的端指明该主机 Nebula 地址的 relays:。
  • 在发起连接的对端上的 use_relays: true。
  • 路径改变后,目的端的入站规则仍然允许真正的源。

相关文章