大家好,我是米开朗基杨。
熟悉我的小伙伴都知道我是一名与时俱进的 WireGuard 舔狗,我早就把所有的跨云组网都换成了 WireGuard。
WireGuard 利用内核空间处理来提升性能(更高吞吐和更低延迟),同时避免了不必要的内核和用户空间频繁上下文切换开销。在 Linux 5.6 将 WireGuard 合并入上游之后, OpenVPN
无论做什么,也无法逆转大部队向 WireGuard 迁移之大趋势,所谓历史之潮流。
不要再跟我提 OpenVPN 了,你们农村人才用 OpenVPN,我们城里人早就换上了 WireGuard!(此处只是开个玩笑,别当真哈😂)
言归正传,我在👉上篇文章中介绍了 Netmaker 的工作原理和功能解读,本篇文章将会介绍如何使用 Netmaker 来配置 WireGuard 全互联模式。
此前我单独用了整篇文章来给大家介绍 Netmaker 是个什么东西,它的架构和工作原理是什么,以及如何部署 Netmaker。所有的这些内容都是为了今天的文章做铺垫,本文要讲的内容才是真正的杀手锏。假定你已经通读了我的👉上一篇文章,并且按照文中所述步骤部署好了 Netmaker。如果你还没有做好这些准备工作,建议先去准备一下,再来阅读本篇文章。
好,我们已经部署好了 Netmaker,但它只负责存储和管理各个节点的 WireGuard 配置和状态信息,真正的主角还是通过 WireGuard 私有网络进行通信的节点。节点通常是运行 Linux 的服务器,它需要安装 netclient
和 WireGuard
。这个节点会通过 WireGuard 私有网络和其他所有节点相连。一但节点被添加到私有网络中,Netmaker 管理员就可以操控该节点的配置。
光说不练假把式,为了让大家更容易带入,咱们还是来模拟一下实际场景。假设我有 4 个不同的节点,这 4 个节点的操作系统分别是 Ubuntu
、macOS
、OpenWrt
和 Android
,且分别处于不同的局域网中,即每个节点的公网出口都不同。先来看下架构图:
创建网络
加入节点之前,需要先在 Netmaker 中创建一个网络。一般我们会将这个新创建的网络命名为 default
,但我的环境中已经存在了该网络,所以我将重新创建一个网络为大家演示。
先创建一个网络,命名为 demo。
创建完成后,还可以继续修改该网络的相关元数据,比如允许节点在不使用秘钥的情况下加入 VPN 网络。
加入节点
如果部署 Netmaker 时开启了环境变量 CLIENT_MODE: "on"
,Netmaker 就会将自身所在的主机也作为一个网络节点,名字默认为 netmaker
。
其他节点的加入流程也很简单,但不同的操作系统又不尽相同。
Ubuntu
常规的 Linux 发行版最简单,直接下载二进制文件,赋予可执行权限。
$ wget https://github.com/gravitl/netmaker/releases/download/latest/netclient
$ chmod +x netclient
然后执行下面的命令将节点加入网络。
$ ./netclient join --dnson no --name <HOSTNAME> --network demo --apiserver <Netmaker_IP>:8081 --grpcserver <Netmaker_IP>:50051
- 将
<HOSTNAME>
替换成你的节点名称,你也可以设置成别的名字。 - 将
<Netmaker_IP>
替换为 Netmaker Server 的公网 IP。
到 Netmaker UI 中批准加入节点的请求。
批准之后就可以看到两个节点之间已经握手成功了。
如果没有握手成功,你需要检查一下 Netmaker 的防火墙是否放行了 UDP 端口(本文是 51821
端口)。
对于 WireGuard 而言,一般情况下通信双方只需一个节点开放固定的公网端口即可,另一个节点的防火墙可以不放行 UDP 端口。所以这里只需开启 Netmaker Server 所在主机的 UDP 端口即可。
同时还会设置一个计划任务,来定期(每 15 秒执行一次)启动守护进程执行签到命令,签到的作用是将本地的配置与 Netmaker Server 托管的配置进行比较,根据比较结果进行适当修改,再拉取所有的 Peer 列表,最后重新配置 WireGuard。
$ cat /etc/systemd/system/netclient.timer [Unit] Description=Calls the Netmaker Mesh Client Service Requires=netclient.service
[Timer]
Unit=netclient.serviceOnCalendar=::0/15
[Install]
WantedBy=timers.target
$ systemctl status netclient.timer
● netclient.timer - Calls the Netmaker Mesh Client Service
Loaded: loaded (/etc/systemd/system/netclient.timer; enabled; vendor preset: enabled)
Active: active (running) since Sat 2021-10-09 01:34:27 CST; 4 weeks 1 days ago
Trigger: n/a
Triggers: ● netclient.serviceOct 09 01:34:27 blog-k3s04 systemd[1]: Started Calls the Netmaker Mesh Client Service.
$ cat /etc/systemd/system/netclient.service
[Unit]
Description=Network Check
Wants=netclient.timer[Service]
Type=simple
ExecStart=/etc/netclient/netclient checkin -n all[Install]
WantedBy=multi-user.target
$ systemctl status netclient.service
● netclient.service - Network Check
Loaded: loaded (/etc/systemd/system/netclient.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2021-11-07 15:00:54 CST; 11ms ago
TriggeredBy: ● netclient.timer
Main PID: 3390236 (netclient)
Tasks: 5 (limit: 19176)
Memory: 832.0K
CGroup: /system.slice/netclient.service
└─3390236 /etc/netclient/netclient checkin -n all
Nov 07 15:00:54 blog-k3s04 systemd[1]: Started Network Check.
Nov 07 15:00:54 blog-k3s04 netclient[3390236]: 2021/11/07 15:00:54 [netclient] running checkin for all networks
macOS
如果是 Intel CPU,可以直接到 Releases 页面[1]下载可执行文件。如果是 M1 系列芯片(包含 M1 Pro 和 M1 Max),需要自己从源码编译:
$ git clone https://github.com/gravitl/netmaker
$ cd netmaker/netclient
$ go build -a -ldflags="-s -w" .
安装 WireGuard 命令行工具:
$ brew install wireguard-tools
下面的步骤就和 Ubuntu 一样了,执行以下命令将节点加入网络。
$ sudo ./netclient join --dnson no --name <HOSTNAME> --network demo --apiserver <Netmaker_IP>:8081 --grpcserver <Netmaker_IP>:50051
再到 Netmaker UI 中批准加入节点的请求,批准之后就可以看到各个节点之间已经握手成功了。
$ sudo wg
interface: utun5
public key: 2sGnrXTY1xb+cWMR+ZXfBLZqmpDtYCNtKdQ3Cm6gBAs=
private key: (hidden)
listening port: 61259peer: X2LTMBX8fyXyCrCVFcJMDKVBtPcfJHT24lwkQQRSykg=
endpoint: 121.36.134.95:51821
allowed ips: 10.8.0.1/32
latest handshake: 37 seconds ago
transfer: 216 B received, 732 B sent
persistent keepalive: every 20 seconds
peer: Z6oCQdV5k4/AVXsUhhGNW69D2hnqcgJe7i3w8qzGJBY=
endpoint: 103.61.37.238:55730
allowed ips: 10.8.0.2/32
latest handshake: 1 minute, 47 seconds ago
transfer: 1.30 KiB received, 2.99 KiB sent
persistent keepalive: every 20 seconds
除了 Netmaker Server 节点之外,Ubuntu 节点和 macOS 节点的 UDP 监听端口都是随机的,而且他们的防火墙都没有放行相应的 UDP 端口,竟然也握手成功了!那是因为他们都开启了 UDP 打洞,这就是 UDP 打洞的神奇之处。
我们可以来验证下 macOS 和 Ubuntu 之间的连通性:
$ ping 10.8.0.2 -c 2
PING 10.8.0.2 [局域网 IP] (10.8.0.2 [局域网 IP]): 56 data bytes
64 bytes from 10.8.0.2 [局域网 IP]: icmp_seq=0 ttl=64 time=44.368 ms
64 bytes from 10.8.0.2 [局域网 IP]: icmp_seq=1 ttl=64 time=44.065 ms
--- 10.8.0.2 [局域网 IP] ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 44.065/44.216/44.368/0.152 ms
完美,即使 macOS 位于 NAT 后面,防火墙没有配置 UDP 端口转发,对等节点也没有放行相应 UDP 端口,双方仍然能够握手成功。
macOS 的守护进程是通过 launchctl 来配置的,netclient 在 macOS 中也会创建一个守护进程来定时同步配置。
$ sudo launchctl list com.gravitl.netclient
{
"StandardOutPath" = "/etc/netclient/com.gravitl.netclient.log";
"LimitLoadToSessionType" = "System";
"StandardErrorPath" = "/etc/netclient/com.gravitl.netclient.log";
"Label" = "com.gravitl.netclient";
"OnDemand" = true;
"LastExitStatus" = 0;
"Program" = "/etc/netclient/netclient";
"ProgramArguments" = (
"/etc/netclient/netclient";
"checkin";
"-n";
"all";
);
};
守护进程的配置文件在 /Library/LaunchDaemons/com.gravitl.netclient.plist
目录下:
$ sudo cat /Library/LaunchDaemons/com.gravitl.netclient.plist
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version='1.0'>
<dict>
<key>Label</key><string>com.gravitl.netclient</string>
<key>ProgramArguments</key>
<array>
<string>/etc/netclient/netclient</string>
<string>checkin</string>
<string>-n</string>
<string>all</string>
</array>
<key>StandardOutPath</key><string>/etc/netclient/com.gravitl.netclient.log</string>
<key>StandardErrorPath</key><string>/etc/netclient/com.gravitl.netclient.log</string>
<key>AbandonProcessGroup</key><true/>
<key>StartInterval</key>
<integer>15</integer>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
其中有一段配置内容如下:
<key>StartInterval</key>
<integer>15</integer>
表示每过 15 秒执行签到命令来同步配置。
OpenWrt
虽然 OpenWrt 也是 Linux 发行版,但目前 netclient 的可执行文件还不能在 OpenWrt 中运行,这和 C 语言的动态链接库有关,OpenWrt 中缺失了很多 C 语言动态链接库。为了解决这个问题,我们可以关闭对 C 语言外部依赖的调用,手动编译出纯静态的可执行文件。
你可以找一台常规的 Linux 发行版或者 macOS 来编译:
$ git clone https://github.com/gravitl/netmaker
$ cd netmaker/netclient
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags="-s -w" .
如果你的 OpenWrt 跑在其他 CPU 架构上,需要将
GOARCH
的值替换为相应的 CPU 架构。
编译成功后,可以检查一下可执行文件的类型和 CPU 架构:
$ file netclient
netclient: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=QWXj97OoEpN-Sm97lim2/ZtJJHaG77M3fYSMqtFGK/YPVj2xx-KdNyYT8YEZ8W/i9CliPF-AqUNcTy2ZKpA, stripped
如果确认无误,就可以将其拷贝到 OpenWrt 主机上了,例如:
$ scp netclient root@<Openwrt_IP>:/root/
接下来就可以登录到 OpenWrt 将节点加入网络了:
$ ./netclient join --dnson no --name <HOSTNAME> --daemon off --network demo --apiserver <Netmaker_IP>:8081 --grpcserver <Netmaker_IP>:50051
这里相比于之前的节点多了一个参数 --daemon off
,禁用了守护进程,因为 OpenWrt 不支持 Systemd。如果你坚持开启守护进程,那么加入网络时就会报错,所以必须要加这个参数。
和之前的步骤一样,到 Netmaker UI 中批准加入节点的请求,批准之后就可以看到各个节点之间已经握手成功了。
$ wg
interface: nm-demo
public key: sfrfimG++xk7X0AU5PrZs9p6PYith392ulhmL2OhPR8=
private key: (hidden)
listening port: 42655peer: Z6oCQdV5k4/AVXsUhhGNW69D2hnqcgJe7i3w8qzGJBY=
endpoint: 103.61.37.238:55730
allowed ips: 10.8.0.2/32
latest handshake: 5 seconds ago
transfer: 488 B received, 1.39 KiB sent
persistent keepalive: every 20 secondspeer: X2LTMBX8fyXyCrCVFcJMDKVBtPcfJHT24lwkQQRSykg=
endpoint: 121.36.134.95:51821
allowed ips: 10.8.0.1/32
latest handshake: 7 seconds ago
transfer: 568 B received, 488 B sent
persistent keepalive: every 20 seconds
peer: 2sGnrXTY1xb+cWMR+ZXfBLZqmpDtYCNtKdQ3Cm6gBAs=
endpoint: 192.168.100.90:57183
allowed ips: 10.8.0.3/32
latest handshake: 1 minute, 35 seconds ago
transfer: 1.38 KiB received, 3.46 KiB sent
persistent keepalive: every 20 seconds
由于我的 macOS 和 OpenWrt 在同一个局域网中,所以他们之间的 endpoint 都自动设置成了内网地址,太神奇啦!
到这里还没完,要想让 OpenWrt 动态更新配置,还需要手动实现一个计划任务来定期签到。我们选择使用 Crontab 来实现这个目的,直接添加两个计划任务:
$ cat <<EOF >> /etc/crontabs/root
-
-
-
-
- /etc/netclient/netclient checkin --network all &> /dev/null
-
-
-
- sleep 15; /etc/netclient/netclient checkin --network all &> /dev/null
EOF
- sleep 15; /etc/netclient/netclient checkin --network all &> /dev/null
这两个计划任务变相实现了 “每隔 15 秒执行一次签到” 的目的。
Android
Netclient 目前只支持 Linux、macOS 和 Windows,如果 Android 和 iOS 端想要加入 VPN 私有网络,只能通过 WireGuard 原生客户端来进行连接。要想做到这一点,需要管理员事先创建一个 External Client,它会生成一个 WireGuard 配置文件,WireGuard 客户端可以下载该配置文件或者扫描二维码进行连接。
当然,在创建 External Client 之前,需要先设置其中一个节点为 Ingress Gateway。
需要说明的是,目前移动设备通过 External Client 接入只是权宜之计,随着 Netclient 对更多操作系统的支持,最终所有的客户端都应该使用 netclient 来连接。
最终所有的节点之间实现了全互联模式,每个节点都和其他节点直连,不需要第三方节点进行中转。当然,目前移动设备还是要通过 Ingress Gateway 进行中转。
打通内网
到目前为止我们只是打造了一个点对点的 Mesh 网络,各个节点之间都可以通过 WireGuard 的私有网络 IP 进行直连。但我们可以更大胆一点,让每个节点都能访问其他节点的局域网 IP。以 OpenWrt 为例,假设 OpenWrt 跑在家中,家中的局域网 IP 为
192.168.100.0/24
,如何让其他所有节点都能访问这个局域网呢?
其实也很简单,可以将某个节点设置为 Egress Gateway(出口网关),允许将内部网络的流量转发到外部指定的 IP 范围。这里的内部指的是 WireGuard 私有网络,本文中就是
10.8.0.0/16
;外部网络指的是其他网段,比如局域网 IP。
操作步骤很傻瓜化,先点击 OpenWrt 节点左边的 “MAKE openwrt AN EGRESS GATEWAY MODE?”:
填写局域网的网段和出口网卡,如果你有多个网段需要打通(比如 OpenWrt 上的容器网段),可以用 "," 隔开。
配置完成后,就会在 OpenWrt 节点配置的 Postup 和 Postdown 中添加相关的 iptables 规则。
具体的规则为:
代码语言:javascript
复制
# Postup
iptables -A FORWARD -i nm-demo -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Postdown
iptables -D FORWARD -i nm-demo -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
很简单,想必就不用我再解释了。
除了添加 Postup 和 Postdown 之外,还会在其他节点 WireGuard 配置的
AllowedIps
中添加 OpenWrt 的局域网网段:
如果再自动添加相关的路由表,所有的节点就都可以访问 OpenWrt 的局域网了。可惜的是,Netmaker 目前并没有自动为我们添加相关路由表,不知道是出于什么原因,不管如何,我们可以自己手动添加路由表,将其添加到 Postup 和 Postdown 中。
具体的操作是,除了 OpenWrt 节点之外,在其他所有节点的配置中添加以下的路由表条目:
最终所有的节点都可以访问 OpenWrt 的局域网 IP 了。
大家可以根据我的例子举一反三,比如你用几台云主机搭建了 K8s 集群,如何在本地客户端和家中访问云上 K8s 集群的 Pod IP 和 Service IP 呢?不用我再解释了吧,相信你悟了。
总结
本文详细介绍了如何使用 Netmaker 来配置 WireGuard 全互联模式,并打通指定节点的局域网,你也可以根据此方法来访问远程 K8s 集群中的 Pod。下一篇文章将会介绍如何使用 Cilium + Netmaker 来打造跨公有云的 K8s 集群。
引用链接
[1]
Releases 页面: https://github.com/gravitl/netmaker/releases