Headscale 部署和 DERP 服务器配置
如何连接公司、家庭服务器、公网服务器是一个经常被讨论的问题。一直通过 WireGuard 组网作为核心方式、ZeroTier 作为备用方式。WireGuard 的配置和使用比较复杂但是网络链路清晰,而 ZeroTier 可以保证 WireGuard 链路异常的时候作为备用方式连接网络,通过自定义 Moon 服务器可以提高速度和稳定性。
基于 WireGuard 的 Tailscale 很好的解决了 WireGuard 的配置问题,但是需要依赖 Tailscale 的控制和中继服务器。Headscale 是开源的 Tailscale 控制服务器,摆脱了 Tailscale 的控制,而且相比 ZeroTier 可以更简单的自定义控制服务器和中继服务器,所以使用 Headscale 配置另一个备用网络。
使用 Docker 部署 Headscale
因为控制节点和中继节点都在国内,不打算备案所以无法使用域名,所以无论控制节点还是中继节点都使用 IP 地址的方式。
参考文档 https://github.com/juanfont/headscale/blob/main/docs/running-headscale-container.md
cd /home/ubuntu
mkdir -p ./headscale/config
cd ./headscale
touch ./config/db.sqlite
wget -O ./config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml
# vim config/config.yaml 修改里面的 server_url 为服务器的 IP 地址和端口,需要在服务器上开放对应端口
# 另外需要注意 nameservers 默认为 1.1.1.1 在国内并不稳定,可以换成其他服务器,具体可以参考最后的详细配置文件
# 假设配置的 server_url 为 http://11.22.33.44:8030/ ,后面会继续用到
# 启动容器
docker run \
--name headscale \
--detach \
--restart always \
--volume /home/ubuntu/headscale/config:/etc/headscale/ \
--publish 0.0.0.0:8030:8030 \
--publish 127.0.0.1:9090:9090 \
headscale/headscale:0.21 \
headscale serve
# 查看统计信息
curl http://127.0.0.1:9090/metrics
# 创建用户
docker exec headscale \
headscale users create phyng
macOS 配置
Tailscale 的客户端配置比 WireGuard 简单,Headscale 稍微麻烦一点,总之就是找到自定义服务端地址的方法,然后生成 nodekey 到服务端注册即可,或者服务端预先生成 authkey,然后直接使用 authkey 登录。
# 安装
brew install --cask tailscale
# 客户端登录
tailscale up --login-server http://11.22.33.44:8030 --force-reauth
# 前往服务器确认登录
docker exec headscale \
headscale nodes register --user phyng --key nodekey:******
Linux/Ubuntu 配置
# https://tailscale.com/download/linux
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --login-server http://11.22.33.44:8030 --force-reauth
# 前往服务器确认登录
docker exec headscale \
headscale nodes register --user phyng --key nodekey:******
Windows 配置
先去 Tailscale 官网下载 Windows 客户端 安装包,正常安装但是不启动。然后参考文档 https://github.com/juanfont/headscale/blob/main/docs/windows-client.md 修改注册表配置,可以直接用下面的命令修改。
# 以管理员身份打开 PowerShell 执行注册表修改,或者通过注册表编辑器修改
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value http://11.22.33.44:8030
修改后启动 Tailscale,会显示登录页面,找到 nodekey:xxxxx,然后在服务器上执行
# 前往服务器确认登录
docker exec headscale \
headscale nodes register --user phyng --key nodekey:******
群晖 Synology 配置
群晖可以通过 authkey 登录,不需要 nodekey
# 群晖可以直接在应用中心搜索安装 Tailscale 或者参考 https://tailscale.com/kb/1131/synology/ 手动在 https://pkgs.tailscale.com/stable/#spks 下载文件安装
# 服务端生成 authkey
docker exec headscale \
headscale --user phyng preauthkeys create --reusable --expiration 24h
# 进入群晖终端,使用 authkey 登录
sudo tailscale up --reset --login-server http://11.22.33.44:8030 --authkey ****** --force-reauth
iPhone 配置
新版 Tailscale 已经支持自定义服务器地址了,用非国区账号下载 Tailscale,在系统设置找到 Tailscale,输入服务器地址,打开 App 会弹出登录页面显示 nodekey:xxxxx,然后在服务器上执行命令即可。
docker exec headscale \
headscale nodes register --user phyng --key nodekey:******
Android 配置
首先在 https://f-droid.org/en/packages/com.tailscale.ipn/ 下载 APK 安装。安装之后先切换登录服务器地址再登录,首先点击右上角三个点菜单,然后点击其他区域关闭菜单,重复几次就能看到修改服务器地址的菜单,修改之后保存,再点击使用其他方式登录即可自动打开自定义服务器的登录页面,找到里面的 nodekey:xxxxx 然后在服务器上执行命令即可,参考 iPhone 的命令。
DERP 中继服务器配置
因为备案的问题,这里使用纯 IP 实现,参考文档 https://icloudnative.io/posts/custom-derp-servers/#使用纯-ip
# 默认使用 443 端口,如果需要修改可以参考文档评论区
docker run --restart always --net host --name derper -d ghcr.io/yangchuansheng/ip_derper
创建一个 DERP 配置的 JSON 文件命名为 derp.json
,可以上传到任意可以公开访问的地方,然后在 config.yaml
中填写对应的 URL 即可。中继服务器可以和控制节点在同一台服务器上,也可以是不同的服务器。
{
"Regions": {
"901": {
"RegionID": 901,
"RegionCode": "sh2",
"RegionName": "Shanghai-2",
"Nodes": [
{
"Name": "901a",
"RegionID": 901,
"DERPPort": 443,
"HostName": "11.22.33.44",
"IPv4": "11.22.33.44",
"InsecureForTests": true
}
]
}
}
}
ACL 权限控制配置
创建一个 acls.hujson
文件和 config.yaml
放在同一目录下。acls.hujson
和正常的 JSON 格式类似但是允许使用 //
注释。下面是一个简单配置示例。如果不配置 ACL,默认是允许所有机器间的流量。
下面的配置实现了 100.64.0.1 允许访问全部机器,而 100.64.0.6 可以访问 100.64.0.7 和 100.64.0.8,不能访问其他机器。更多配置参考文档。
{
// groups are collections of users having a common scope. A user can be in multiple groups
// groups cannot be composed of groups
"groups": {
"group:admin": ["phyng"]
},
// hosts should be defined using its IP addresses and a subnet mask.
// to define a single host, use a /32 mask. You cannot use DNS entries here,
// as they're prone to be hijacked by replacing their IP addresses.
// see https://github.com/tailscale/tailscale/issues/3800 for more information.
"hosts": {
"m1": "100.64.0.1/32",
"m2": "100.64.0.6/32"
},
"acls": [
// m1 -> *
{ "action": "accept", "src": ["100.64.0.1"], "dst": ["100.64.0.1:*"] },
{ "action": "accept", "src": ["100.64.0.1"], "dst": ["100.64.0.6:*"] },
{ "action": "accept", "src": ["100.64.0.1"], "dst": ["100.64.0.7:*"] },
{ "action": "accept", "src": ["100.64.0.1"], "dst": ["100.64.0.8:*"] },
// m2 -> 7/8
{ "action": "accept", "src": ["100.64.0.6"], "dst": ["100.64.0.7:*"] },
{ "action": "accept", "src": ["100.64.0.6"], "dst": ["100.64.0.8:*"] }
]
}
完整配置示例
下面是一个完整的 config.yaml
,相比默认配置去掉了一些注释,增加了本文相关的注释。注意里面引用了前面提到的 derp.json
和 acls.hujson
文件。
---
# 填写自己的服务器公网 IP 地址和端口,这里自定义端口为 8030,可以换成别的
server_url: http://11.22.33.44:8030
listen_addr: 0.0.0.0:8030
metrics_listen_addr: 0.0.0.0:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false
private_key_path: ./private.key
noise:
private_key_path: ./noise_private.key
# IPV4 在前,这 tailscale status 默认才能输出 IPV4 地址
ip_prefixes:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48
derp:
server:
enabled: false
region_id: 999
region_code: 'headscale'
region_name: 'Headscale Embedded DERP'
stun_listen_addr: '0.0.0.0:3478'
urls:
# 这里将 derp.json 上传到了阿里云,可以替换成其他任意可用的地址
- https://my-bucket.aliyuncs.com/public/apps/headscale/derp.json
- https://controlplane.tailscale.com/derpmap/default
paths: []
auto_update_enabled: true
update_frequency: 24h
disable_check_updates: false
ephemeral_node_inactivity_timeout: 30m
node_update_check_interval: 10s
db_type: sqlite3
db_path: ./db.sqlite
acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: ''
tls_letsencrypt_hostname: ''
tls_letsencrypt_cache_dir: ./cache
tls_letsencrypt_challenge_type: HTTP-01
tls_letsencrypt_listen: ':http'
tls_cert_path: ''
tls_key_path: ''
log:
format: text
level: info
# 权限控制文件相对路径
acl_policy_path: './acls.hujson'
dns_config:
override_local_dns: true
nameservers:
- 8.8.8.8
- 114.114.114.114
- 223.5.5.5
domains: []
magic_dns: true
# 可以自定义为自己的域名
base_domain: example.com
unix_socket: ./headscale.sock
unix_socket_permission: '0770'
logtail:
enabled: false
randomize_client_port: false
IP 显示优化
Headscale 默认配置文件 ip_prefixes 默认是 IPV6 在前,这样导致添加机器后运行 tailscale status
默认显示 IPV6 地址,而不是 IPV4 地址,不是很方便。
➜ tailscale status
fd7a:115c:a1e0::1 x*** phyng macOS -
fd7a:115c:a1e0::4 xx*** phyng linux active; relay "***", tx 97124 rx 79692
100.64.0.5 xxx*** phyng iOS offline
100.64.0.2 xxxx*** phyng linux idle, tx 195700 rx 419468
这是因为前述 config.yaml
中的配置默认 IPV6 在前:
ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10
如果刚配置还没有添加机器。可以修改这个配置,把 IPV4 放在前面即可。
ip_prefixes:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48
但是如果已经添加过机器,修改配置之后只能保证新的机器默认显示 IPV4 地址,已经添加的机器还是会显示 IPV6 地址,这样 tailscale status
输出的结果无法对齐。这种情况可以直接修改 sqlite3 数据库文件。
# Ubuntu/Debian 安装 sqlite3
sudo apt install sqlite3
# 进入 sqlite3 数据库
sqlite3 config/db.sqlite
进入之后通过分析发现机器数据存储在 machines
表里面,ip_addresses 字段存储的是 IP 地址,以逗号分隔,根据分析结果可以直接修改。
-- 设置显示模式
.mode column
.headers on
-- 查看全部机器配置
select * from machines;
-- 查看 machines 表里面的 IP 配置
select id, ip_addresses from machines;
-- 调换 ip_addresses 的顺序让 IPV4 靠前
update machines set ip_addresses = '100.64.0.2,fd7a:115c:a1e0::2' where ip_addresses = 'fd7a:115c:a1e0::2,100.64.0.2';
update machines set ip_addresses = '100.64.0.3,fd7a:115c:a1e0::3' where ip_addresses = 'fd7a:115c:a1e0::3,100.64.0.3';
update machines set ip_addresses = '100.64.0.4,fd7a:115c:a1e0::4' where ip_addresses = 'fd7a:115c:a1e0::4,100.64.0.4';
update machines set ip_addresses = '100.64.0.1,fd7a:115c:a1e0::1' where ip_addresses = 'fd7a:115c:a1e0::1,100.64.0.1';
-- 退出数据库编辑
.quit
实际测试发现,Headscale 应该会从 sqlite3 刷新数据同步到机器,要快速生效的话可以直接重启 headscale 让它从数据库读取最新的数据,这样所有客户端运行 tailscale status 的时候默认看到的就是最新的 IPV4 了。
docker restart headscale
headscale 命令行工具
# 列出全部节点
docker exec headscale headscale node ls
# 删除某个节点
docker exec -ti headscale headscale node delete -i 8
headscale 自定义 DNS 配置
可以自由的配置 DNS,比如添加 A 记录等等。
dns_config:
extra_records:
- name: 'www.example.com'
type: 'A'
value: '100.64.0.7'
- name: 'admin.example.com'
type: 'A'
value: '100.64.0.7'