LinuxとBIRDによるBGP Anycastの構築:耐障害性に優れたDNSおよびWebロードバランシング

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

アプローチの比較:DNSラウンドロビン vs VRRP vs BGP Anycast

午前2時14分、ラックAのプライマリロードバランサーが完全にフリーズしました。トラフィックはブラックホールへと吸い込まれ、パブリックDNSリゾルバーがレコードを300秒間キャッシュしていたため、セカンダリノードへ切り替わるまでに4万件以上のリクエストが消失しました。本番環境において、フェイルオーバーの仕組みをどう設計するかは、ちょっとした障害の最中でも眠り続けられるか、それとも朝一番でインシデント報告書の作成に追われるかの分かれ道となります。

権威DNS(UDP)やエッジリバースプロキシ(TCP)などのクリティカルなサービスをスケールさせる際、インフラチームは通常、以下の3つのモデルから選択します。

  • DNSラウンドロビン / GeoDNS:設定はシンプルで、1つのホスト名に複数のA/AAAAレコードを紐付けます。しかし、中継DNSリゾルバーが強力にキャッシュを保持し、設定した60秒のTTLを平然と無視することも珍しくありません。サーバーが停止した場合、クライアントは数分から数時間にわたって停止したIPへリクエストを送り続けることになります。
  • VRRP(Keepalived):ローカル環境の高可用性(HA)には信頼性があります。各ノードは毎秒のハートビートマルチキャストを用いてVirtual IP(VIP)を共有します。VRRPは1〜3秒以内にIPをフェイルオーバーできますが、単一のレイヤー2ブロードキャストドメイン内に制限されます。そのため、ToR(Top-of-Rack)スイッチの電源が落ちるとVIPも道連れになります。
  • BGP Anycast:複数の独立したサーバーが、BGPを通じてまったく同じIPアドレス(IPv4の/32またはIPv6の/128プレフィックス)を上流ルーターに広報します。ルーターは標準のIPルーティングメトリックを使用して、最も近い正常なノードへパケットを転送します。ノードで障害が発生した場合は経路が取り消され(Withdraw)、ネットワークファブリックがミリ秒単位で着信パケットを再ルーティングします。
メトリック DNSラウンドロビン VRRP(Keepalived) BGP Anycast
フェイルオーバー速度 数分〜数時間(TTLに依存) 1〜3秒 サブセカンド(BFD使用時)〜6秒
障害の影響範囲(Blast Radius) キャッシュを持つクライアントで広域障害 単一ラックまたはL2サブネット ローカルなネットワーク経路内に限定
トラフィック分散 粗いクライアント側のランダム分散 Active/Passive構成 全ノード間でのワイヤーレートECMP
DDoS攻撃の吸収 すべてのターゲットに均等に着弾 単一のアクティブ回線を圧迫・飽和 エッジで地理的に分散・細分化

BGP Anycastのメリットとデメリット

BGP Anycastはトラフィック分散を大幅にシンプル化しますが、レイヤー4のバランシングをレイヤー3へ引き下げることで、特有の運用上の制約が生じます。

メリット

  • プロキシ不要の高スループット:上流スイッチがECMP(Equal-Cost Multi-Path)を用いてトラフィックを分散します。ソフトウェアプロキシを介することなく、スイッチのASICからサーバーのネットワークカードへ直接40 Gbpsや100 Gbpsのワイヤーレートでパケットが転送されます。
  • 影響範囲の自動隔離:大容量のDDoS攻撃は、感染ボットネット端末からトポロジ的に最も近いノードへ誘導されます。フランクフルト発の50 Gbpsのフラッド攻撃は欧州内にとどまり、アシュバーンのクラスターを巻き込んでダウンさせることはありません。
  • ステートレスな回復性:BGPセッションが切断されるか経路取り消し(Route Withdrawal)パケットを受信すると、ルーターは即座に障害ノードをルーティングテーブルから除外します。

デメリットとトレードオフ

  • 経路変更によるTCPセッションのリセット:スイッチは5タプルハッシュ(送信元IP、宛先IP、送信元ポート、宛先ポート、プロトコル)を用いてECMP経路を計算します。ECMPグループ内の1台のサーバーがダウンすると、スイッチは残りのノード全体に対してハッシュを再計算します。そのため、400MBのファイルアップロードやWebSocketストリームといった長時間のTCP接続は別のサーバーへルーティングされ、TCP RSTによって即座に切断されます。
  • ルートフラッピングのペナルティ:不具合のあるヘルスチェックが数秒おきにBGP広報と取り消しを繰り返すと、トランジットプロバイダーによってBGPルートダンピングが適用され、プレフィックスが最大30分間抑制(抑止)されるおそれがあります。
  • BGPピアリングの要件:データセンターやホスティング事業者が、ToRスイッチとの間でeBGPまたはiBGPによるプライベートピアリングセッションを許可している必要があります。

LinuxにおけるBIRDの推奨アーキテクチャ

ハードウェアアプライアンスのペアで処理できる限界をトラフィックが超えた場合、Linuxホスト上で直接BGPを実行するのが業界標準のアプローチです。最もクリーンな構成は、ベアメタルまたは仮想マシンインスタンス上でBIRD 2(Border Internet Routing Daemon)を稼働させ、2台のToRスイッチと冗長ピアリングすることです。

トポロジーの概要

  • Anycast VIP:全ノードの専用dummy0インターフェースにバインドされた192.0.2.53/32。
  • 物理サブネット:ノード管理およびBGPピアリング用のeth0上の10.10.10.0/24。
  • Node 1:IP 10.10.10.11(AS 65001)、ToR Switch 1(10.10.10.1、AS 65000)とピアリング。
  • Node 2:IP 10.10.10.12(AS 65001)、ToR Switch 2(10.10.10.2、AS 65000)とピアリング。
  • 障害検知ウォッチドッグ:ローカルエージェントがプロセスの応答性(ポート53へのDNSクエリやポート80でのHTTPステータス200)を検証します。デーモンがクラッシュした場合、スクリプトはdummy0アドレスを削除します。BIRDはLinuxのnetlink経由でこれを即座に検知し、BGPのWITHDRAWを発行します。

TCPトラフィック(NginxやEnvoyなど)を扱う場合は、カーネルのsysctlパラメーターnet.ipv4.ip_nonlocal_bind = 1を設定します。これにより、初期起動時にdummyインターフェースが無効化されていても、Webサーバーが192.0.2.53:443にバインドできるようになります。

実装ガイド:Linux上でのBIRDステップバイステップ構築

ステップ1:Dummy Anycastインターフェースの設定

Anycast IPを物理インターフェースに直接割り当ててはいけません。dummyインターフェースを使用することで、サービスVIPを物理リンクの状態変化から切り離すことができます。

# dummyカーネルモジュールをロード
sudo modprobe dummy

# 永続化用のdummyインターフェースを作成
sudo ip link add dev dummy0 type dummy
sudo ip link set dev dummy0 up

# Anycast IPを/32ホストアドレスとして割り当て
sudo ip addr add 192.0.2.53/32 dev dummy0

システムの再起動後もこの設定を維持するため、/etc/systemd/network/10-dummy0.netdevを作成します:

[NetDev]
Name=dummy0
Kind=dummy

次に、/etc/systemd/network/10-dummy0.networkにネットワーク設定を記述します:

[Match]
Name=dummy0

[Network]
Address=192.0.2.53/32

systemd-networkdデーモンを有効化して起動し、インターフェースを有効にします:

sudo systemctl enable --now systemd-networkd

ステップ2:カーネルのパケット転送とARPフィルタリングの有効化

データセンターの同一サブネット内で複数のサーバーが同じIPを共有していると、そのIP宛てのARPリクエストに各サーバーが応答しようとします。カーネルのsysctlフラグを適切に設定し、重複ARPによる競合を防止します:

sudo tee /etc/sysctl.d/99-anycast.conf << 'EOF'
# パケット転送を有効化
net.ipv4.ip_forward = 1

# インターフェースに未割り当てのIPへのバインドを許可
net.ipv4.ip_nonlocal_bind = 1

# 物理インターフェースeth0においてdummy0宛てのARP要求への応答を防止
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
EOF

sudo sysctl --system

ステップ3:BIRD 2のインストールと設定

BIRD 2パッケージをインストールします:

sudo apt-get update && sudo apt-get install -y bird2

/etc/bird/bird.confを以下の本番向け設定で置き換えます。BIRDはdummy0の状態を監視し、192.0.2.53/32プレフィックスをToRスイッチへ広報します:

# /etc/bird/bird.conf
log syslog all;
router id 10.10.10.11;

define ANYCAST_IP = 192.0.2.53/32;

protocol device {
    scan time 5;
}

# netlink経由でdummy0に設定されたルートとIPを読み込む
protocol direct direct_anycast {
    interface "dummy0";
}

# フィルター:Anycastの/32プレフィックスのみを広報
filter anycast_export {
    if net = ANYCAST_IP then accept;
    reject;
}

# Top-of-Rackスイッチ1とのピアリングセッション
protocol bgp tor_switch1 {
    local 10.10.10.11 as 65001;
    neighbor 10.10.10.1 as 65000;
    
    ipv4 {
        import none;
        export filter anycast_export;
    };
    
    # 6秒での高速な障害検知のため、hold timeを6秒、keepaliveを2秒に設定
    hold time 6;
    keepalive time 2;
}

設定ファイルの構文を検証し、サービスを再起動します:

sudo bird -p
sudo systemctl restart bird
sudo systemctl enable bird

ステップ4:ヘルスチェックによる経路取り消しの自動化

停止しているサービスに対してBGP経路を広報し続けると、致命的なブラックホールが発生します。チェックが失敗した際にはローカルのヘルスチェックによってdummy0からIPアドレスを削除し、BIRDによるプレフィックス取り消しをトリガーする必要があります。

/usr/local/bin/anycast-healthcheck.shに実行スクリプトを作成します:

#!/usr/bin/env bash
set -euo pipefail

ANYCAST_IP="192.0.2.53/32"
INTERFACE="dummy0"
CHECK_HOST="127.0.0.1"

# 1. ローカルでDNS応答が500ms以内に返ることを確認
check_dns() {
    dig +time=1 +tries=1 @"$CHECK_HOST" health.check.local A > /dev/null 2>&1
}

# 2. Nginx / Envoyのhealthzエンドポイントが1秒以内にHTTP 200を返すことを確認
check_http() {
    curl -s -f -o /dev/null --connect-timeout 1 "http://$CHECK_HOST/healthz"
}

if check_dns && check_http; then
    # サービス正常:IPがdummy0にバインドされていることを確認
    if ! ip addr show dev "$INTERFACE" | grep -q "$ANYCAST_IP"; then
        ip addr add "$ANYCAST_IP" dev "$INTERFACE"
        logger -t anycast-healthcheck "サービス正常。$INTERFACE に $ANYCAST_IP を追加しました。"
    fi
else
    # サービス異常:BGP経路の取り消しをトリガーするためIPを削除
    if ip addr show dev "$INTERFACE" | grep -q "$ANYCAST_IP"; then
        ip addr del "$ANYCAST_IP" dev "$INTERFACE"
        logger -t anycast-healthcheck "チェック失敗!BGP経路を取り消すため $ANYCAST_IP を削除しました。"
    fi
fi

スクリプトに実行権限を付与します:

sudo chmod +x /usr/local/bin/anycast-healthcheck.sh

このチェックをsystemdタイマーまたはオーケストレーションループを用いて2秒ごとに実行します。本番環境のフリートでは、ヘルス監視機能が組み込まれたanycast-healthcheckerやexabgpなどの専用ツールの採用も検討してください。

ステップ5:ピアリングの確認とノード障害シミュレーション

BIRDの対話型クライアント(birdc)を使用してセッション状態を確認します:

# BGPセッションの状態を確認
sudo birdc show protocols

# 広報中のルート(エクスポートルート)を確認
sudo birdc show route export tor_switch1

コンソール出力から、セッションがEstablishedであり、192.0.2.53/32が正常に広報されていることが確認できます:

Name       Proto      Table      State  Since         Info
device1    Device     master4    up     02:30:12
direct_anycast Direct master4    up     02:30:12
tor_switch1 BGP       master4    up     02:30:15      Established

192.0.2.53/32        unicast [direct_anycast 02:30:12] * (240)

次に障害時の挙動をテストします。ローカルのDNSサービスを停止します:

sudo systemctl stop named
sudo /usr/local/bin/anycast-healthcheck.sh

スクリプトがdummy0から192.0.2.53/32を削除した瞬間、Linuxカーネルはnetlinkのアドレス削除イベントを発行します。BIRDはこのイベントを即座にキャッチし、ToRスイッチに対してBGP WITHDRAWフレームを送信します。50ミリ秒以内にスイッチはNode 1へのパケット送信を停止し、Node 2へトラフィックを再配分します。DNS TTLによる遅延も、手動での介入も一切不要です。

Share: