Anycast DNS с BIRD2 и BGP: настройка на нескольких локациях
Развёртывание anycast DNS на нескольких VPS с BIRD2 и BIND9. Анонс маршрутов BGP, синхронизация зон через TSIG, автоматическое переключение по health-check и защита nftables.
Anycast DNS назначает один IP-адрес DNS-серверам в нескольких локациях. Каждый сервер анонсирует этот IP через BGP. Маршрутизаторы направляют запросы к ближайшему серверу на основе топологии сети. Если сервер выходит из строя и отзывает свой BGP-маршрут, запросы автоматически достигают следующего ближайшего сервера. Это обеспечивает DNS-резолвинг с низкой задержкой и автоматический failover без изменений на стороне клиента.
Это руководство разворачивает продакшн-конфигурацию anycast DNS на двух VPS-нодах Virtua с использованием BIRD2 в качестве демона маршрутизации и BIND9 в качестве авторитативного DNS-сервера. Безопасность встроена в каждый шаг, а не добавлена в конце.
Что вы построите:
- Две VPS-ноды в разных локациях, каждая анонсирует один и тот же префикс /24 через BGP
- Авторитативный DNS на BIND9, слушающий на anycast IP
- Синхронизация зон между нодами через TSIG-аутентифицированные трансферы
- Health-check скрипт, отзывающий BGP-маршруты при отказе DNS
- Правила nftables для защиты каждой ноды
Какие требования для anycast DNS?
Перед началом эти ресурсы должны быть уже подготовлены. Если ты ещё не настраивал BGP на VPS, сначала прочти Настройка BGP в BIRD2 на Linux VPS. Для создания ROA-записей смотри RPKI ROA для BGP: создание ROA, валидация маршрутов в BIRD2 и FRR.
| Требование | Детали |
|---|---|
| ASN | Собственный ASN (например, AS212345), зарегистрированный в RIR |
| IPv4 префикс | Минимум /24 (например, 198.51.100.0/24). Префиксы длиннее /24 фильтруются большинством транзитных провайдеров. |
| IPv6 префикс | Минимум /48 (например, 2001:db8:abcd::/48) |
| ROA записи | Созданы в портале RIR для обоих префиксов, авторизующие твой ASN |
| VPS ноды | 2+ VPS Virtua в разных локациях, каждая с BGP-сессией к upstream-маршрутизатору |
| Upstream 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 upstream сосед | 169.254.169.1 AS 64496 |
| Нода B (Амстердам) основной IP | 203.0.113.20 |
| Нода B upstream сосед | 169.254.169.1 AS 64496 |
| DNS зона | example.com |
Как выглядит топология сети?
┌─────────────┐
│ Internet │
└──────┬───────┘
│
┌────────────┴────────────┐
│ │
┌────────┴────────┐ ┌────────┴────────┐
│ Virtua Router │ │ Virtua Router │
│ Frankfurt │ │ Amsterdam │
│ AS 64496 │ │ AS 64496 │
└────────┬────────┘ └────────┬────────┘
│ eBGP │ eBGP
┌────────┴────────┐ ┌────────┴────────┐
│ Node A (VPS) │ │ Node 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 (via │
│ primary IPs) │
└─────────────────────────┘
Обе ноды анонсируют 198.51.100.0/24 и 2001:db8:abcd::/48 через BGP. Клиенты попадают на ту ноду, которая топологически ближе. Трансферы зон происходят через основные (unicast) IP, а не через anycast-адрес.
Как создать anycast loopback-интерфейс?
Каждой ноде нужен anycast IP, назначенный на локальный интерфейс. Dummy-интерфейс подходит лучше всего, потому что он всегда поднят и не зависит от физического подключения. Настрой его через systemd-networkd, чтобы он сохранялся после перезагрузки.
Создай файл netdev:
cat > /etc/systemd/network/10-anycast.netdev << 'EOF'
[NetDev]
Name=anycast0
Kind=dummy
EOF
Создай файл network для назначения anycast IP. Используй полную длину префикса (/24 для IPv4, /48 для IPv6), чтобы протокол direct в BIRD2 видел connected-маршруты, совпадающие с префиксами, которые ты хочешь анонсировать:
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-ноде. Конфигурация идентична.
Какая конфигурация BIRD2 нужна для анонса anycast-маршрутов?
BIRD2 анонсирует anycast-префикс upstream-маршрутизатору через eBGP. Ключевой принцип: BIRD2 экспортирует только маршруты, полученные с интерфейса anycast0 через протокол direct. Когда интерфейс падает (или health-check скрипт его гасит), BIRD2 автоматически отзывает маршрут.
Установи BIRD2:
apt update && apt install -y bird2
Debian 12 поставляется с BIRD 2.0.12. Для более новых функций (улучшения BFD, расширения фильтров) добавь upstream-репозиторий 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включает аутентификацию TCP MD5 (RFC 2385) на BGP-сессии. Получи общий секрет у upstream-провайдера.import noneозначает, что эта нода не принимает маршруты от upstream. Anycast-ноды только анонсируют; для исходящего трафика используется маршрут по умолчанию VPS.graceful restart onуменьшает route flap при перезапусках BIRD2.
Конфигурация ноды 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-ключ с помощью tsig-keygen:
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 (primary)
Отредактируй /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 (secondary)
На ноде 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 аутентифицировать все взаимодействия с нодой A через TSIG-ключ. Трансферы зон происходят автоматически после этой настройки.
Запуск BIND9
Включи и запусти на обеих нодах:
systemctl enable --now named
Проверь статус:
systemctl status named
Протестируй DNS-резолвинг через anycast IP:
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
Серийный номер должен совпадать на обеих нодах. Если secondary показывает другой серийный номер, трансфер не прошёл. Проверь совпадение TSIG-ключей и правила файрвола.
Какие правила файрвола нужны для BGP anycast DNS ноды?
Заблокируй каждую ноду через nftables. Разрешай только необходимое: SSH для управления, BGP от upstream-маршрутизатора и 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;
}
}
Адаптируй строки ip saddr и ip6 saddr на ноде B (поменяй IP пиров для трансфера зон и используй IPv6-соседа upstream ноды B). В продакшне ограничь 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 поднят.
Как сделать health-check DNS и отозвать BGP-маршруты при сбое?
Health-check скрипт мониторит BIND9 и сигнализирует BIRD2 отозвать anycast-маршрут, когда DNS не работает. Метод: опускаем интерфейс anycast0, после чего BIRD2 автоматически отзывает префикс, поскольку протокол direct больше не видит маршрут.
Почему нельзя просто полагаться на BGP таймеры?
С дефолтными BGP таймерами (90 секунд hold, 30 секунд keepalive) переключение занимает до 90 секунд. Health-check обнаруживает отказ DNS за секунды и немедленно инициирует отзыв. Таблица ниже показывает разницу:
| Метод | Время обнаружения | Конвергенция |
|---|---|---|
| Истечение BGP hold таймера | 90 секунд | 90-180 секунд |
| BFD (если доступен) | <1 секунды | 1-3 секунды |
| Health-check скрипт | 5-15 секунд | 35-45 секунд (+ BGP propagation) |
BFD обнаруживает только отказ канала/сессии, но не приложения. Health-check скрипт ловит падения BIND9, ошибки конфигурации и заполнение диска, которые BFD не видит.
Создание выделенного пользователя
Health-check работает под выделенным пользователем с минимальными привилегиями:
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
Написание health-check скрипта
Создай /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 остановит health-check при остановке named. Это противоречит цели: health-check должен продолжать работать для обнаружения отказа 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
Как протестировать failover anycast DNS?
Запускай эти тесты с внешней машины, не с одной из нод.
Шаг 1: Убедись, что обе ноды анонсируют
С локальной машины:
dig @198.51.100.1 example.com A +short
Убедись, что получаешь ответ. Запусти traceroute, чтобы увидеть, на какую ноду попадаешь:
traceroute -n 198.51.100.1
Путь показывает, какая локация ближе к тебе.
Шаг 2: Симулируй отказ DNS на ноде A
На ноде A останови BIND9:
systemctl stop named
Наблюдай за логами health-check:
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 секунд (3 проверки с интервалом 5 секунд) health-check опускает anycast0. BIRD2 обнаруживает изменение интерфейса и отзывает маршрут.
Проверь, что маршрут исчез:
birdc show route export upstream4
Таблица должна быть пустой. Upstream-маршрутизатор удаляет 198.51.100.0/24 с ноды A и перенаправляет весь трафик на ноду B.
Шаг 3: Проверь переключение клиентов
С внешней машины запроси снова:
dig @198.51.100.1 example.com A +short +time=5
Первый запрос после отзыва может отвалиться по таймауту (старый маршрут ещё закеширован на некоторых маршрутизаторах). Последующие запросы достигают ноды B. BGP-конвергенция обычно занимает 30-90 секунд в зависимости от конфигурации upstream.
Шаг 4: Восстанови ноду A
systemctl start named
Health-check обнаруживает, что DNS вернулся после 2 успешных проверок подряд (10 секунд) и поднимает 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 на ноде, на которую попадаешь. Посчитай секунды между последним успешным ответом и первым успешным ответом с другой ноды. На инфраструктуре Virtua с дефолтными BGP таймерами ожидай 30-60 секунд частичной недоступности.
Как BIRD2 сравнивается с другими демонами маршрутизации для anycast?
| Функция | BIRD2 | FRRouting | ExaBGP |
|---|---|---|---|
| Язык конфигурации | Декларативные фильтры | CLI в стиле Cisco | JSON/API |
| Интеграция с anycast | Протокол direct на dummy-интерфейсе | Статический маршрут + redistribute | Внешний скрипт анонсирует/отзывает |
| Метод health-check | Смена состояния интерфейса вызывает изменение маршрута | Команды vtysh из скрипта | Встроенный менеджер процессов |
| Поддержка IPv6 | Единая конфигурация (каналы ipv4/ipv6) | Отдельные address families | Ручное управление для каждого семейства |
| Сложность фильтров | Полный язык фильтров с функциями | Route maps, prefix lists | Только внешняя логика |
| Потребление памяти | Низкое (~10 МБ для малой таблицы) | Среднее (~30 МБ) | Очень низкое (~5 МБ) |
| Зрелость в продакшне | IXP, крупномасштабный DNS (Cloudflare, RIPE) | ISP, дата-центры | Малые развёртывания, мониторинг |
Преимущество BIRD2 для anycast: protocol direct на dummy-интерфейсе обеспечивает автоматический отзыв маршрута при падении интерфейса. Внешний скриптинг для BGP-части не нужен. Health-check только переключает интерфейс.
Как синхронизировать DNS-зоны между anycast-нодами?
Синхронизация зон использует встроенную primary/secondary репликацию BIND9 через AXFR с аутентификацией TSIG. Конфигурация была выполнена в секции BIND9 выше. Далее операционные детали.
Обновления зон идут в одном направлении: редактируй файл зоны на ноде A (primary), увеличь серийный номер и перезагрузи:
# 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 получает NOTIFY от ноды A и загружает обновлённую зону через AXFR. На ноде B проверь серийный номер:
dig @127.0.0.1 example.com SOA +short
Серийный номер должен совпадать. Если нет, проверь:
- Файрвол разрешает TCP 53 от ноды A к ноде B
- TSIG-ключ идентичен на обеих нодах
journalctl -u namedна ноде B на предмет ошибок трансфера
Добавление третьей ноды: настрой её как ещё один secondary. Добавь её IP в список also-notify и правила nftables на ноде A. Изменения на существующих secondary не нужны.
А как насчёт IPv6 BGP anycast?
Конфигурация BIRD2 выше уже включает IPv6. Блок protocol direct anycast имеет каналы ipv4 и ipv6. BGP-сессия upstream6 экспортирует префикс /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
Тест DNS через IPv6:
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.
Health-check не отзывает маршруты:
journalctl -u anycast-healthcheck -f
Проверь права sudo: sudo -u anycast-healthcheck sudo -l. Пользователь должен иметь возможность запускать /usr/sbin/ip link set anycast0 up и down без пароля.
Запросы отваливаются по таймауту после failover:
DNS-клиенты кешируют ответы. TTL в файле зоны (300 секунд / 5 минут) определяет, как долго клиенты используют устаревшие данные. BGP-конвергенция добавляет 30-90 секунд. Полное время failover с точки зрения клиента: до TTL плюс время конвергенции. Уменьши минимальный TTL в SOA, если нужен более быстрый failover, но не опускайся ниже 60 секунд для авторитативных зон.
Чеклист для продакшна
Перед запуском:
- ROA-записи созданы и проверены (
rpki-clientили портал RIR) - BGP-сессии установлены на всех нодах (
birdc show protocols) - Anycast-префикс виден в глобальных таблицах маршрутизации (используй looking glass)
- BIND9 отвечает на anycast IP из внешних сетей
- Серийный номер зоны совпадает на всех нодах
- Права TSIG-ключа установлены в 640, владелец root:bind
- Правила nftables активны на всех нодах
- Сервис health-check включён и работает
- Failover протестирован: остановил BIND9, подтвердил отзыв маршрута, подтвердил восстановление
- Мониторинг настроен: алерты на падение BGP-сессии, процесс BIND9, сбои health-check
-
journalctl -u birdиjournalctl -u namedне показывают ошибок
Для основного руководства по BGP и использованию собственного IP-пространства смотри BGP и Bring Your Own IP на VPS: полное руководство. Для фильтрации BGP-маршрутов и усиления безопасности смотри BGP Route Filtering: Prefix Lists, AS-Path фильтры, отклонение богонов и GTSM.
Авторское право 2026 Virtua.Cloud. Все права защищены. Данный контент является оригинальным произведением команды Virtua.Cloud. Воспроизведение, повторная публикация или распространение без письменного разрешения запрещены.
Готовы попробовать?
Разверните свой сервер за секунды. Linux, Windows или FreeBSD.
Смотреть тарифы VPS