背景とconntrackd × Keepalived連携が必要な理由
Linux環境でKeepalivedを使用して冗長化ゲートウェイを構築することは、ネットワーク管理者にとっておなじみの設計パターンです。メインルーター(Master)に障害が発生しても、Keepalivedは1秒未満で仮想IP(VIP)をスタンバイ機(Backup)へとフェイルオーバーさせ、ネットワーク通信は瞬時に復旧します。しかし、本当に頭を悩ませるのはその直後に起こる問題です。VoIPの通話が途中で切断され、HTTPS経由の20GBファイルダウンロードが中断し、インフラエンジニアのSSHセッションが一斉に切断されてしまうのです。
その原因は、Linux Netfilter(iptables/nftables)のステートフルファイアウォール(Stateful Firewall)の仕組みにあります。Linuxは接続追跡テーブル(conntrack)を利用して、各TCP/UDPセッションの状態をメモリ上に記録しています。VIPがBackupノードへ移行した際、Backup側にはそれまで確立されていたセッション情報が一切存在しません。そのため、事前の3ウェイハンドシェイク(SYN、SYN-ACK)を経ていないACKフラグ付きパケットが届くと、カーネルはそれを不正なパケット(INVALID)と判断し、容赦なくドロップしてしまいます。
50〜100人規模のオフィスや社内サーバークラスターのゲートウェイを運用している場合、このようなセッション切断は業務全体を停滞させる致命的なトラブルになります。この問題を根本から解決するのが、conntrackd(connection tracking daemon)の導入です。
このデーモンはユーザースペースで常駐し、同期専用ネットワーク経由で2台のノード間の接続追跡テーブルをリアルタイムに複製します。KeepalivedによってBackupノードがMasterへと昇格すると、conntrackdはキャッシュ内のセッション情報を即座にカーネルへとコミットします。その結果、完全に透過的なゲートウェイの切り替えが実現し、ユーザーは障害が発生したことにすら気付きません。
必要なパッケージのインストール
本構成では、2台のLinuxノード(Ubuntu 22.04 / Debian 12)を使用します:
- Node 1 (FW-01): LAN IP
192.168.1.2/24(eth0), 同期用IP10.0.0.1/30(eth1) - Node 2 (FW-02): LAN IP
192.168.1.3/24(eth0), 同期用IP10.0.0.2/30(eth1) - ゲートウェイVIP:
192.168.1.1(eth0に割り当て)
ハードウェア設計の注意点:セッション同期トラフィック用には、必ず専用の物理NIC(クロスケーブルやDACケーブルで直結したeth1など)を用意してください。通常のLAN帯域との共用は絶対に避けるべきです。同時接続数が約20,000件に達すると、conntrackイベントの送信量は毎秒数千パケット(PPS)に達することがあり、社内LANのブロードキャストドメインを圧迫して輻輳を引き起こす原因になります。
両方のノードにKeepalived、conntrack、conntrackdをインストールします:
sudo apt update
sudo apt install -y keepalived conntrack conntrackd
パケット転送(IPフォワーディング)を有効化し、TCPシーケンスチェックを緩和します(両ノードで実行):
cat <<EOF | sudo tee /etc/sysctl.d/99-firewall-sync.conf
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_tcp_be_liberal = 1
EOF
sudo sysctl -p /etc/sysctl.d/99-firewall-sync.conf
ここで設定したnf_conntrack_tcp_be_liberal = 1パラメータは極めて重要です。フェイルオーバー発生直後、実際のパケット到達に対して同期パケットが数ミリ秒遅れる場合があります。この設定を有効にしておくことで、想定TCPウィンドウ外のパケットであってもカーネルが誤って破棄せず、正常に受け付けるようになります。
Keepalivedとconntrackdの詳細設定
1. conntrackdの設定
メイン設定ファイルは/etc/conntrackd/conntrackd.confです。ここではUDPユニキャストを使用したFTFW(Fault-Tolerant Framework)モードで動作させます。
Node 1 (FW-01)の設定内容:
sudo tee /etc/conntrackd/conntrackd.conf <<'EOF'
Sync {
Mode FTFW {
ResendQueueSize 10240
CommitTimeout 180
PurgeTimeout 60
}
UDP {
IPv4_address 10.0.0.1
IPv4_Destination_Address 10.0.0.2
Port 3780
Interface eth1
SndSocketBuffer 12492800
RcvSocketBuffer 12492800
Checksum on
}
}
General {
Systemd on
Nice -20
Scheduler {
Type FIFO
Priority 99
}
HashSize 32768
HashLimit 131072
LogFile on
Syslog off
LockFile /var/lock/conntrackd.lock
UNIX {
Path /var/run/conntrackd.ctl
Backlog 20
}
NetlinkBufferSize 2097152
NetlinkBufferSizeMaxGrowth 2
Filter From Userspace {
Protocol Accept {
TCP
UDP
ICMP
}
Address Ignore {
IPv4_address 127.0.0.1
IPv4_address 10.0.0.0/30
}
}
}
EOF
SndSocketBufferおよびRcvSocketBufferの値は12MBまで引き上げています。このバッファサイズにより、トラフィックが急増した際でもNetlinkパケットのドロップを防止できます。また、Address Ignoreディレクティブで10.0.0.0/30を除外指定することで、デーモン自身が送信するUDP同期パケットまで同期対象にしてしまうループを防いでいます。
Node 2 (FW-02)では、同様の設定をコピーした上で、UDPブロック内の2つのIPアドレスを入れ替えます:
IPv4_address 10.0.0.2
IPv4_Destination_Address 10.0.0.1
2. conntrackd状態連動スクリプトの作成
Keepalivedのロールが切り替わる際、conntrackdに対して適切な命令を発行する必要があります。Master昇格時にはキャッシュしたセッションをカーネルにコミットし、Backup降格時には外部キャッシュをクリアします。両ノードでスクリプトファイル/etc/conntrackd/primary-backup.shを作成します:
sudo tee /etc/conntrackd/primary-backup.sh <<'EOF'
#!/bin/bash
case "$1" in
primary)
# 外部キャッシュのデータをカーネルのconntrackテーブルへコミット
conntrackd -c
# 現在のセッションの再同期シグナルを送信
conntrackd -f
;;
backup)
# スタンバイ状態への移行時に外部キャッシュを解放
conntrackd -t
;;
fault)
conntrackd -t
;;
esac
exit 0
EOF
sudo chmod +x /etc/conntrackd/primary-backup.sh
3. Keepalivedの設定
重要なポイントとして、nopreemptオプションを意図通りに動作させるには、両方のノードで初期状態をstate BACKUPとして定義する必要があります。プライマリノードはpriorityの数値を高く設定することで識別されます。もしプライマリノードにstate MASTERとnopreemptを同時に指定してしまうと、再起動時にこのフラグが無視されて強制的にVIPを奪還し、2重のネットワーク瞬断を引き起こしてしまいます。
Node 1 (FW-01)の/etc/keepalived/keepalived.conf設定:
vrrp_sync_group VG1 {
group {
VI_LAN
}
notify_master "/etc/conntrackd/primary-backup.sh primary"
notify_backup "/etc/conntrackd/primary-backup.sh backup"
notify_fault "/etc/conntrackd/primary-backup.sh fault"
}
vrrp_instance VI_LAN {
state BACKUP
interface eth0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass Secr3tPwD
}
virtual_ipaddress {
192.168.1.1/24 dev eth0
}
nopreempt
}
Node 2 (FW-02)の設定もほぼ同様ですが、priorityのみ100に下げます:
vrrp_sync_group VG1 {
group {
VI_LAN
}
notify_master "/etc/conntrackd/primary-backup.sh primary"
notify_backup "/etc/conntrackd/primary-backup.sh backup"
notify_fault "/etc/conntrackd/primary-backup.sh fault"
}
vrrp_instance VI_LAN {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass Secr3tPwD
}
virtual_ipaddress {
192.168.1.1/24 dev eth0
}
nopreempt
}
両ノードで各サービスを再起動し、自動起動を有効化します:
sudo systemctl restart conntrackd
sudo systemctl enable conntrackd
sudo systemctl restart keepalived
sudo systemctl enable keepalived
動作確認とモニタリング
以下の手順に沿って、セッションデータが同期回線経由で正常にやり取りされているかを確認します。
1. 同期統計情報(Stats)の確認
Node 2(FW-02)で確認コマンドを実行します:
sudo conntrackd -s
出力結果を確認します。cache (external)セクションにはNode 1から受信したセッション数が表示されます。クライアントのアクセスに伴ってcurrent active connectionsの数値が増加していれば、同期ラインは正常に機能しています:
cache (external):
current active connections: 142
connections created: 520 failed: 0
connections updated: 310 failed: 0
connections destroyed: 378 failed: 0
2. 実環境でのフェイルオーバー試験
切り替え時の安定性を検証します:
- クライアントPC(IP:
192.168.1.50)のデフォルトゲートウェイをVIP(192.168.1.1)に設定します。 - インターネット宛てのSSH接続を確立するか、大容量ファイルのダウンロードコマンドを実行します:
curl -O https://speed.hetzner.de/100MB.bin - FW-01上でKeepalivedを停止し、突発的な障害をシミュレートします:
sudo systemctl stop keepalived
クライアント端末を確認します。ファイルダウンロードは約0.5秒ほど一瞬停止するものの、そのまま継続して100%まで完了します。SSHセッションもBroken pipeなどのエラーを出さずに維持されます。
FW-02のログを確認してコミット状況を検証します:
sudo tail -f /var/log/conntrackd.log
外部キャッシュ全体がカーネルテーブルに正常にコミットされたログが記録されます。この構成を採用すれば、追加のハードウェア投資をほぼゼロに抑えながら、商用アプライアンス製品に匹敵する高信頼なLinuxゲートウェイを実現できます。

