使用BIRD2和BGP部署Anycast DNS:多站点配置
使用BIRD2和BIND9在多个VPS节点部署Anycast DNS。涵盖BGP路由通告、TSIG区域同步、健康检查故障切换和nftables防火墙加固。
Anycast DNS将同一个IP地址分配给位于不同位置的多台DNS服务器。每台服务器通过BGP通告该IP。路由器根据网络拓扑将查询导向最近的服务器。如果某台服务器故障并撤回其BGP路由,查询会自动到达下一个最近的服务器。这样就能实现低延迟的DNS解析和自动故障切换,无需客户端做任何修改。
本指南在两个Virtua VPS节点上部署生产级Anycast DNS,使用BIRD2作为路由守护进程,BIND9作为权威DNS服务器。安全措施融入每个步骤,而非事后添加。
你将构建的内容:
- 两个位于不同位置的VPS节点,各自通过BGP通告相同的/24前缀
- BIND9权威DNS服务器监听anycast IP
- 节点间通过TSIG认证的区域传输进行同步
- 健康检查脚本在DNS故障时撤回BGP路由
- nftables防火墙规则锁定每个节点
Anycast DNS的先决条件是什么?
开始之前,你需要以下已配置的资源。如果你之前没有在VPS上配置过BGP,请先阅读Linux VPS上配置BIRD2 BGP路由。如果需要创建ROA记录,请参阅RPKI ROA配置BGP:创建ROA,在BIRD2和FRR中验证路由。
| 要求 | 详情 |
|---|---|
| ASN | 你自己的ASN(如AS212345),已在RIR注册 |
| IPv4前缀 | 至少一个/24(如198.51.100.0/24)。大多数Transit提供商会过滤长于/24的前缀。 |
| IPv6前缀 | 至少一个/48(如2001:db8:abcd::/48) |
| ROA记录 | 在RIR门户中为两个前缀创建,授权你的ASN |
| VPS节点 | 2个以上位于不同位置的Virtua VPS,每个都有到上游路由器的BGP会话 |
| 上游BGP信息 | 每个节点的邻居IP和ASN,由Virtua提供 |
| Debian 12 | 本指南使用Debian Bookworm。其他发行版请调整包名。 |
本指南中使用以下示例值,请替换为你自己的值:
| 变量 | 值 |
|---|---|
| 你的ASN | 212345 |
| Anycast IPv4 | 198.51.100.1 |
| Anycast前缀 | 198.51.100.0/24 |
| Anycast IPv6 | 2001:db8:abcd::1 |
| Anycast IPv6前缀 | 2001:db8:abcd::/48 |
| 节点A(法兰克福)主IP | 203.0.113.10 |
| 节点A上游邻居 | 169.254.169.1 AS 64496 |
| 节点B(阿姆斯特丹)主IP | 203.0.113.20 |
| 节点B上游邻居 | 169.254.169.1 AS 64496 |
| DNS区域 | example.com |
网络拓扑是什么样的?
┌─────────────┐
│ Internet │
└──────┬───────┘
│
┌────────────┴────────────┐
│ │
┌────────┴────────┐ ┌────────┴────────┐
│ Virtua路由器 │ │ Virtua路由器 │
│ 法兰克福 │ │ 阿姆斯特丹 │
│ AS 64496 │ │ AS 64496 │
└────────┬────────┘ └────────┬────────┘
│ eBGP │ eBGP
┌────────┴────────┐ ┌────────┴────────┐
│ 节点A (VPS) │ │ 节点B (VPS) │
│ AS 212345 │ │ AS 212345 │
│ BIRD2 + BIND9 │ │ BIRD2 + BIND9 │
│ anycast0: │ │ anycast0: │
│ 198.51.100.1/24 │ │ 198.51.100.1/24 │
│ 2001:db8:abcd::1 │ │ 2001:db8:abcd::1 │
└──────────────────┘ └──────────────────┘
│ │
│ AXFR + TSIG(通过 │
│ 主IP) │
└─────────────────────────┘
两个节点都通过BGP通告198.51.100.0/24和2001:db8:abcd::/48。客户端到达拓扑上最近的节点。区域传输通过主(单播)IP进行,而非anycast地址。
如何创建anycast回环接口?
每个节点需要将anycast IP分配到本地接口。Dummy接口最为合适,因为它始终处于活动状态且没有物理依赖。使用systemd-networkd配置以确保重启后持久化。
创建netdev文件:
cat > /etc/systemd/network/10-anycast.netdev << 'EOF'
[NetDev]
Name=anycast0
Kind=dummy
EOF
创建network文件分配anycast IP。使用完整的前缀长度(IPv4用/24,IPv6用/48),这样BIRD2中的direct协议能看到与你要通告的前缀匹配的已连接路由:
cat > /etc/systemd/network/10-anycast.network << 'EOF'
[Match]
Name=anycast0
[Network]
Address=198.51.100.1/24
Address=2001:db8:abcd::1/48
EOF
启用并启动systemd-networkd。如果你的VPS使用ifupdown管理主接口(检查/etc/network/interfaces),不要使用systemctl restart systemd-networkd,因为这可能会中断主网络。首次启动是安全的,因为systemd-networkd只管理在/etc/systemd/network/中有配置文件的接口:
systemctl enable --now systemd-networkd
ip addr show anycast0
预期输出:
3: anycast0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether a2:b4:c6:d8:e0:f2 brd ff:ff:ff:ff:ff:ff
inet 198.51.100.1/24 brd 198.51.100.255 scope global anycast0
valid_lft forever preferred_lft forever
inet6 2001:db8:abcd::1/48 scope global
valid_lft forever preferred_lft forever
接口状态显示UNKNOWN,这对dummy接口来说是正常的。它表示链路层始终处于活动状态。IPv4和IPv6地址都应该出现。
在每个anycast节点上重复此操作。配置完全相同。
Anycast路由通告需要什么BIRD2配置?
BIRD2通过eBGP向上游路由器通告anycast前缀。关键设计:BIRD2只导出通过direct协议从anycast0接口学到的路由。当接口关闭(或健康检查脚本将其关闭)时,BIRD2自动撤回路由。
安装BIRD2:
apt update && apt install -y bird2
Debian 12附带BIRD 2.0.12。如需更新功能(BFD改进、过滤器增强),请添加BIRD官方仓库。
节点A配置(/etc/bird/bird.conf)
# /etc/bird/bird.conf -- Node A (Frankfurt)
log syslog all;
router id 203.0.113.10;
# Scan interfaces every 10 seconds
protocol device {
scan time 10;
}
# Learn routes from the anycast dummy interface
protocol direct anycast {
ipv4;
ipv6;
interface "anycast0";
}
# Define the prefixes we are authorized to announce
define ANYCAST_V4 = [ 198.51.100.0/24 ];
define ANYCAST_V6 = [ 2001:db8:abcd::/48 ];
# Export filter: only announce our anycast prefixes
filter export_anycast_v4 {
if net ~ ANYCAST_V4 then accept;
reject;
}
filter export_anycast_v6 {
if net ~ ANYCAST_V6 then accept;
reject;
}
# IPv4 BGP session to upstream
protocol bgp upstream4 {
description "Virtua Frankfurt upstream IPv4";
local 203.0.113.10 as 212345;
neighbor 169.254.169.1 as 64496;
password "your-bgp-md5-secret";
hold time 90;
keepalive time 30;
graceful restart on;
ipv4 {
import none;
export filter export_anycast_v4;
};
}
# IPv6 BGP session to upstream
protocol bgp upstream6 {
description "Virtua Frankfurt upstream IPv6";
local 2001:db8:1::10 as 212345;
neighbor 2001:db8:1::1 as 64496;
password "your-bgp-md5-secret";
hold time 90;
keepalive time 30;
graceful restart on;
ipv6 {
import none;
export filter export_anycast_v6;
};
}
关键要点:
- **
protocol direct anycast**只从anycast0接口学习路由。其他接口不会泄漏到路由表中。 - 导出过滤器将通告严格限制为你的/24和/48前缀。这可以防止意外的路由泄漏。
- **
password**在BGP会话上启用TCP MD5认证(RFC 2385)。从上游提供商获取共享密钥。 - **
import none**表示此节点不接受上游的任何路由。Anycast节点只做通告;它们使用VPS的默认路由处理出站流量。 - **
graceful restart on**减少BIRD2重启期间的路由抖动(route flap)。
节点B配置
将相同文件复制到节点B。只需修改:
router id 203.0.113.20;
# In protocol bgp upstream4:
local 203.0.113.20 as 212345;
# neighbor IP and AS may differ per location, use the values Virtua provides
启动BIRD2
启用并启动BIRD2:
systemctl enable --now bird
enable确保重启后自动启动。--now立即启动服务。
检查状态:
systemctl status bird
检查BGP会话状态:
birdc show protocols
预期输出:
BIRD 2.0.12 ready.
Name Proto Table State Since Info
device1 Device --- up 2026-03-19
anycast Direct --- up 2026-03-19
upstream4 BGP --- up 2026-03-19 Established
upstream6 BGP --- up 2026-03-19 Established
两个BGP会话都应显示Established。如果看到Active或Connect,请检查邻居IP、ASN和密码。使用journalctl -u bird -f查看日志。
检查导出的路由:
birdc show route export upstream4
预期:
BIRD 2.0.12 ready.
Table master4:
198.51.100.0/24 unicast [anycast 2026-03-19] * (240)
dev anycast0
路由来自anycast direct协议并导出到upstream4。在节点B上重复。
如何配置BIND9监听anycast IP?
BIND9在每个节点上作为纯权威DNS服务器运行,监听anycast IP。客户端查询anycast IP;BGP确保它们到达最近的节点。
安装BIND9:
apt update && apt install -y bind9 bind9-utils
生成用于区域传输的TSIG密钥
节点间的区域传输必须经过认证。使用tsig-keygen生成TSIG密钥:
tsig-keygen -a hmac-sha256 anycast-transfer > /etc/bind/anycast-transfer.key
输出如下:
cat /etc/bind/anycast-transfer.key
key "anycast-transfer" {
algorithm hmac-sha256;
secret "base64-encoded-secret-here";
};
将此文件原样复制到所有节点。密钥名称和密钥值必须在所有节点上一致。
设置严格权限:
chown root:bind /etc/bind/anycast-transfer.key
chmod 640 /etc/bind/anycast-transfer.key
文件应显示root:bind所有权和640权限:
ls -la /etc/bind/anycast-transfer.key
-rw-r----- 1 root bind 113 Mar 19 12:00 /etc/bind/anycast-transfer.key
节点A配置(主服务器)
编辑/etc/bind/named.conf.options:
options {
directory "/var/cache/bind";
// Listen only on the anycast IP and localhost
listen-on { 198.51.100.1; 127.0.0.1; };
listen-on-v6 { 2001:db8:abcd::1; ::1; };
// Authoritative only, no recursion
recursion no;
allow-recursion { none; };
// Hide version to avoid targeted exploits
version "not disclosed";
// Disable zone transfers by default
allow-transfer { none; };
// Rate limiting to mitigate DNS amplification
rate-limit {
responses-per-second 10;
window 5;
};
dnssec-validation auto;
};
引入TSIG密钥。编辑/etc/bind/named.conf.local:
include "/etc/bind/anycast-transfer.key";
// Allow transfers only to Node B using TSIG
acl "secondaries" {
key "anycast-transfer";
};
zone "example.com" {
type primary;
file "/var/lib/bind/db.example.com";
allow-transfer { secondaries; };
also-notify { 203.0.113.20; };
notify yes;
};
创建区域文件/var/lib/bind/db.example.com:
$TTL 300
@ IN SOA ns1.example.com. admin.example.com. (
2026031901 ; Serial (YYYYMMDDNN)
3600 ; Refresh (1 hour)
900 ; Retry (15 minutes)
604800 ; Expire (1 week)
300 ; Negative cache TTL (5 minutes)
)
@ IN NS ns1.example.com.
@ IN NS ns2.example.com.
; NS records point to the anycast IP, same IP, different names
ns1 IN A 198.51.100.1
ns1 IN AAAA 2001:db8:abcd::1
ns2 IN A 198.51.100.1
ns2 IN AAAA 2001:db8:abcd::1
; Your records
@ IN A 198.51.100.10
@ IN AAAA 2001:db8:abcd::10
www IN CNAME example.com.
mail IN A 198.51.100.25
@ IN MX 10 mail.example.com.
设置所有权:
chown bind:bind /var/lib/bind/db.example.com
chmod 640 /var/lib/bind/db.example.com
节点B配置(从服务器)
节点B上的/etc/bind/named.conf.options与节点A完全相同。
/etc/bind/named.conf.local有所不同:
include "/etc/bind/anycast-transfer.key";
server 203.0.113.10 {
keys { "anycast-transfer"; };
};
zone "example.com" {
type secondary;
file "/var/lib/bind/db.example.com";
primaries { 203.0.113.10 key "anycast-transfer"; };
};
server语句告诉BIND9使用TSIG密钥认证与节点A的所有通信。配置完成后,区域传输将自动进行。
启动BIND9
在两个节点上启用并启动:
systemctl enable --now named
检查状态:
systemctl status named
测试针对anycast IP的DNS解析:
dig @198.51.100.1 example.com A +short
预期:
198.51.100.10
在节点B上检查区域是否已传输:
dig @198.51.100.1 example.com SOA +short
ns1.example.com. admin.example.com. 2026031901 3600 900 604800 300
检查BIND9日志中的传输记录:
journalctl -u named --no-pager | grep "transfer of"
预期:
transfer of 'example.com/IN' from 203.0.113.10#53: Transfer status: success
两个节点上的序列号必须一致。如果从服务器显示不同的序列号,说明传输失败了。检查TSIG密钥一致性和防火墙规则。
BGP anycast DNS节点需要什么防火墙规则?
使用nftables锁定每个节点。只允许必要的流量:SSH用于管理,BGP来自上游路由器,DNS来自任何地方。
创建/etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Connection tracking
ct state established,related accept
ct state invalid drop
# Loopback
iif lo accept
# ICMP and ICMPv6 (needed for path MTU discovery and diagnostics)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# SSH (restrict to your management IPs in production)
tcp dport 22 accept
# BGP from upstream router only (IPv4)
ip saddr 169.254.169.1 tcp dport 179 accept
ip saddr 169.254.169.1 tcp sport 179 accept
# BGP from upstream router only (IPv6)
ip6 saddr 2001:db8:1::1 tcp dport 179 accept
ip6 saddr 2001:db8:1::1 tcp sport 179 accept
# DNS on anycast IP
ip daddr 198.51.100.1 udp dport 53 accept
ip daddr 198.51.100.1 tcp dport 53 accept
ip6 daddr 2001:db8:abcd::1 udp dport 53 accept
ip6 daddr 2001:db8:abcd::1 tcp dport 53 accept
# Zone transfers from the other node (TSIG-authenticated at app layer,
# but we also restrict at network layer)
ip saddr 203.0.113.20 tcp dport 53 accept
ip saddr 203.0.113.10 tcp dport 53 accept
# Log and drop everything else
log prefix "nftables-drop: " limit rate 5/minute
drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
在节点B上调整ip saddr和ip6 saddr行(交换区域传输对端IP,使用节点B的上游IPv6邻居)。在生产环境中,将SSH限制到你的管理CIDR。
应用并启用:
systemctl enable --now nftables
列出活动规则以确认已加载:
nft list ruleset
然后从服务器外部测试DNS是否仍然工作:
# Run this from your local machine, NOT the server
dig @198.51.100.1 example.com A +short
如果应用防火墙规则后DNS停止工作,检查daddr是否与你的anycast IP完全匹配,以及anycast0接口是否已启动。
如何通过健康检查监控DNS并在故障时撤回BGP路由?
健康检查脚本监控BIND9,在DNS故障时通知BIRD2撤回anycast路由。方法:关闭anycast0接口,BIRD2会自动撤回前缀,因为direct协议不再看到该路由。
为什么不能只依赖BGP计时器?
使用默认BGP计时器(90秒hold,30秒keepalive),故障切换需要长达90秒。健康检查能在数秒内检测到DNS故障并立即触发撤回。下表展示了影响:
| 方法 | 检测时间 | 收敛时间 |
|---|---|---|
| BGP hold timer到期 | 90秒 | 90-180秒 |
| BFD(如果可用) | <1秒 | 1-3秒 |
| 健康检查脚本 | 5-15秒 | 35-45秒(+ BGP传播) |
BFD只能检测链路/会话故障,无法检测应用层故障。健康检查脚本能捕获BIND9崩溃、配置错误和磁盘空间不足等BFD无法发现的问题。
创建专用用户
健康检查以最小权限的专用用户运行:
useradd --system --no-create-home --shell /usr/sbin/nologin anycast-healthcheck
授予其控制anycast接口的权限。创建sudoers规则:
cat > /etc/sudoers.d/anycast-healthcheck << 'EOF'
anycast-healthcheck ALL=(root) NOPASSWD: /usr/sbin/ip link set anycast0 up, /usr/sbin/ip link set anycast0 down
EOF
chmod 440 /etc/sudoers.d/anycast-healthcheck
编写健康检查脚本
创建/usr/local/bin/anycast-healthcheck.sh:
#!/bin/bash
# Anycast DNS health check with hysteresis
# Withdraws BGP route by downing anycast0 when BIND9 is unhealthy
set -euo pipefail
ANYCAST_IF="anycast0"
CHECK_IP="127.0.0.1"
CHECK_DOMAIN="example.com"
CHECK_TYPE="SOA"
# Hysteresis: 3 failures to withdraw, 2 successes to re-announce
FAIL_THRESHOLD=3
RECOVER_THRESHOLD=2
CHECK_INTERVAL=5
fail_count=0
recover_count=0
is_withdrawn=false
log() {
logger -t anycast-healthcheck "$1"
}
check_dns() {
dig +time=2 +tries=1 @"${CHECK_IP}" "${CHECK_DOMAIN}" "${CHECK_TYPE}" > /dev/null 2>&1
}
withdraw_route() {
if [ "$is_withdrawn" = false ]; then
sudo /usr/sbin/ip link set "${ANYCAST_IF}" down
is_withdrawn=true
log "WITHDRAW: ${ANYCAST_IF} down after ${FAIL_THRESHOLD} consecutive failures"
fi
}
announce_route() {
if [ "$is_withdrawn" = true ]; then
sudo /usr/sbin/ip link set "${ANYCAST_IF}" up
is_withdrawn=true # will be set to false after verification
# Verify the interface came back
sleep 1
if ip link show "${ANYCAST_IF}" | grep -q "UP"; then
is_withdrawn=false
recover_count=0
log "ANNOUNCE: ${ANYCAST_IF} up after ${RECOVER_THRESHOLD} consecutive successes"
else
log "ERROR: failed to bring ${ANYCAST_IF} up"
fi
fi
}
log "Starting anycast health check for ${CHECK_DOMAIN} on ${CHECK_IP}"
while true; do
if check_dns; then
fail_count=0
recover_count=$((recover_count + 1))
if [ "$recover_count" -ge "$RECOVER_THRESHOLD" ]; then
announce_route
fi
else
recover_count=0
fail_count=$((fail_count + 1))
log "DNS check failed (${fail_count}/${FAIL_THRESHOLD})"
if [ "$fail_count" -ge "$FAIL_THRESHOLD" ]; then
withdraw_route
fi
fi
sleep "${CHECK_INTERVAL}"
done
设置权限:
chmod 755 /usr/local/bin/anycast-healthcheck.sh
创建systemd服务
创建/etc/systemd/system/anycast-healthcheck.service:
[Unit]
Description=Anycast DNS health check
After=bird.service named.service
Wants=bird.service named.service
[Service]
Type=simple
User=anycast-healthcheck
ExecStart=/usr/local/bin/anycast-healthcheck.sh
Restart=always
RestartSec=5
# Hardening
NoNewPrivileges=no
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadOnlyPaths=/
ReadWritePaths=/run
[Install]
WantedBy=multi-user.target
这里NoNewPrivileges=no是必需的,因为脚本使用sudo来切换接口。sudoers规则限制了用户可以执行的命令。
[Unit]部分使用Wants=而非Requires=。如果使用Requires=,systemd会在named停止时停止健康检查。这恰恰相反:健康检查必须继续运行以检测DNS故障并撤回路由。
启用并启动:
systemctl daemon-reload
systemctl enable --now anycast-healthcheck
检查是否正在运行:
systemctl status anycast-healthcheck
日志应显示启动消息:
journalctl -u anycast-healthcheck -f
Starting anycast health check for example.com on 127.0.0.1
如何测试Anycast DNS故障切换?
从外部机器执行这些测试,不要从任何节点上执行。
步骤1:确认两个节点都在通告
从你的本地机器:
dig @198.51.100.1 example.com A +short
确认收到响应。运行traceroute查看你到达了哪个节点:
traceroute -n 198.51.100.1
路径显示哪个位置离你最近。
步骤2:在节点A上模拟DNS故障
在节点A上停止BIND9:
systemctl stop named
观察健康检查日志:
journalctl -u anycast-healthcheck -f
预期序列:
DNS check failed (1/3)
DNS check failed (2/3)
DNS check failed (3/3)
WITHDRAW: anycast0 down after 3 consecutive failures
15秒后(每5秒检查一次,共3次),健康检查关闭anycast0。BIRD2检测到接口变化并撤回路由。
检查路由是否已消失:
birdc show route export upstream4
表应该为空。上游路由器移除节点A的198.51.100.0/24,将所有流量转发到节点B。
步骤3:检查客户端故障切换
从外部机器再次查询:
dig @198.51.100.1 example.com A +short +time=5
撤回后的第一个查询可能超时(旧路由仍缓存在某些路由器中)。后续查询到达节点B。BGP收敛通常需要30到90秒,取决于上游配置。
步骤4:恢复节点A
systemctl start named
健康检查在连续2次成功后(10秒)检测到DNS恢复,并启动anycast0。BIRD2重新通告路由。
journalctl -u anycast-healthcheck --no-pager | tail -5
预期:
ANNOUNCE: anycast0 up after 2 consecutive successes
检查BGP会话:
birdc show protocols
两个BGP会话应再次显示Established。
步骤5:测量收敛时间
要精确测量,从外部机器运行持续的dig循环:
while true; do
echo -n "$(date +%H:%M:%S) "
dig @198.51.100.1 example.com A +short +time=1 +tries=1 || echo "TIMEOUT"
sleep 1
done
在你当前到达的节点上停止BIND9。计算从最后一个成功响应到来自另一个节点的第一个成功响应之间的秒数。在使用默认BGP计时器的Virtua基础设施上,预计30-60秒的部分不可用。
BIRD2与其他路由守护进程在anycast方面的比较如何?
| 特性 | BIRD2 | FRRouting | ExaBGP |
|---|---|---|---|
| 配置语言 | 声明式过滤器 | Cisco风格CLI | JSON/API |
| Anycast集成 | Dummy接口上的direct协议 | 静态路由 + 重分发 | 外部脚本通告/撤回 |
| 健康检查方式 | 接口上下触发路由变更 | 脚本中的vtysh命令 | 内置进程管理器 |
| IPv6支持 | 统一配置(ipv4/ipv6通道) | 独立地址族 | 每族手动配置 |
| 过滤器复杂度 | 带函数的完整过滤语言 | Route map、prefix list | 仅外部逻辑 |
| 内存占用 | 低(小表约10 MB) | 中(约30 MB) | 极低(约5 MB) |
| 生产成熟度 | IXP、大规模DNS(Cloudflare、RIPE使用) | ISP、数据中心 | 小型部署、监控 |
BIRD2在anycast方面的优势:dummy接口上的protocol direct在接口关闭时提供自动路由撤回。BGP部分不需要外部脚本。健康检查只需切换接口。
如何在anycast节点间同步DNS区域?
区域同步使用BIND9内置的主/从复制(通过AXFR),以TSIG认证。配置已在上面的BIND9部分完成。以下是操作细节。
**区域更新是单向的:**在节点A(主服务器)上编辑区域文件,递增序列号,然后重载:
# On Node A after editing the zone file
named-checkzone example.com /var/lib/bind/db.example.com
zone example.com/IN: loaded serial 2026031902
OK
重载前务必运行named-checkzone。它能捕获语法错误。
rndc reload example.com
节点B从节点A收到NOTIFY并通过AXFR拉取更新后的区域。在节点B上检查序列号:
dig @127.0.0.1 example.com SOA +short
序列号应该一致。如果不一致,请检查:
- 防火墙允许从节点A到节点B的TCP 53
- TSIG密钥在两个节点上完全相同
- 节点B上
journalctl -u named中的传输错误
**添加第三个节点:**将其配置为另一个从服务器。将其IP添加到节点A的also-notify列表和nftables规则中。现有从服务器无需更改。
IPv6 BGP anycast怎么样?
上面的BIRD2配置已经包含IPv6。protocol direct anycast块同时具有ipv4和ipv6通道。upstream6 BGP会话导出/48前缀。
检查IPv6路由导出:
birdc show route export upstream6
BIRD 2.0.12 ready.
Table master6:
2001:db8:abcd::/48 unicast [anycast 2026-03-19] * (240)
dev anycast0
测试IPv6 DNS:
dig @2001:db8:abcd::1 example.com AAAA +short
故障排除
BGP会话卡在Active/Connect状态:
journalctl -u bird -f
常见原因:邻居IP错误、ASN错误、MD5密码不匹配、防火墙阻止TCP 179。运行birdc show protocols all upstream4获取详细错误消息。
BIND9未监听anycast IP:
ss -tlnp | grep ':53'
如果只看到127.0.0.1:53,检查listen-on是否包含anycast IP,以及anycast0接口是否在BIND9启动前已经激活。在接口启动后重启BIND9:systemctl restart named。
区域传输失败:
journalctl -u named | grep -i "tsig\|transfer\|refused"
检查密钥文件是否可被bind用户读取:ls -la /etc/bind/anycast-transfer.key。运行手动传输测试:dig @203.0.113.10 example.com AXFR -k /etc/bind/anycast-transfer.key。
健康检查未撤回路由:
journalctl -u anycast-healthcheck -f
检查sudo权限:sudo -u anycast-healthcheck sudo -l。用户应能无密码执行/usr/sbin/ip link set anycast0 up和down。
故障切换后查询超时:
DNS客户端会缓存响应。区域文件中的TTL(300秒/5分钟)决定客户端使用过期数据的时间。BGP收敛额外增加30-90秒。从客户端角度看的总故障切换时间:最多为TTL加上收敛时间。如果需要更快的故障切换,降低SOA最小TTL,但权威区域不要低于60秒。
生产环境检查清单
上线前:
- ROA记录已创建并验证(
rpki-client或你的RIR门户) - 所有节点上BGP会话已建立(
birdc show protocols) - Anycast前缀在全球路由表中可见(使用looking glass)
- BIND9从外部网络响应anycast IP
- 区域序列号在所有节点上一致
- TSIG密钥权限设为640,所有者为root:bind
- 所有节点上nftables规则已激活
- 健康检查服务已启用并运行
- 故障切换已测试:停止BIND9,确认路由撤回,确认恢复
- 监控已配置:BGP会话断开、BIND9进程、健康检查失败的告警
-
journalctl -u bird和journalctl -u named无错误
关于BGP基础知识和使用自有IP空间的父级指南,请参阅BGP与自带IP到VPS:完整指南。关于BGP路由过滤和安全加固,请参阅BGP路由过滤:前缀列表、AS路径过滤、Bogon拒绝与GTSM。
版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。