深夜に襲った単一障害点(SPOF)の悪夢
2023年のブラックフライデー当日、23時ちょうどのことでした。弊社のNginxリバースプロキシサーバーの電源ユニットが突如故障しました。背後の4ノードのバックエンドとデータベースクラスタはCPU負荷30%未満で正常に稼働していたにもかかわらず、エンドユーザーには即座にConnection timed outエラーが返される事態に陥りました。理由は単純で、ドメインがそのプロキシの単一のパブリックIPを直接指していたためです。ゲートウェイがダウンしたことで、システム全体が完全に麻痺してしまいました。
慌ててCloudflareのダッシュボードを開き、Aレコードを予備サーバーのIPアドレスへと切り替えました。Cloudflare側でのレコード反映はわずか数秒で完了したものの、クライアント側の各ISP網では10分近くキャッシュが残り続けました。その結果、約350件の注文が消失し、120万ドン以上の売上機会を失いました。これこそが、単一障害点(SPOF)を残したアーキテクチャが招いた手痛い代償です。
なぜDNSフェイルオーバーでは迅速な復旧ができないのか?
「難しく考えず、DNSラウンドロビンを使うか、CloudflareやRoute 53のAPIを叩くcronジョブでレコードを自動更新すれば十分では?」と軽く考える方も少なくありません。しかし、実際の運用現場では以下の3つの致命的な落とし穴に直面します。
- クライアントおよびISPによる強固なDNSキャッシュ:プロバイダ(ISP)やクライアントのブラウザは、短いTTL設定(60秒など)を無視することが往々にしてあります。その結果、多数のセッションがダウンした古いIPに15〜30分間も取り残されます。
- サービス状態(Service Health)の検知不能:DNSは単にIPの名前解決を行うだけであり、サーバー上のNginxが正常に稼働しているか、あるいはメモリ枯渇(OOM Killer)でクラッシュしたかまでは把握できません。
- 検知および切り替えの遅延:ヘルスチェックスクリプトが異常を検知して切り替えを発動するまでに、通常3〜5回のping/curl失敗を待つ必要があります。APIの呼び出し時間も加わると、実際のダウンタイムは容易に3〜5分を超えてしまいます。
本質的な課題はネットワークレイヤーにあります。アプリケーション外部のDNSレイヤーに高可用性を委ねるべきではありません。代わりに内部ネットワーク内でVirtual IP(VIP)を活用すべきです。このレイヤーであれば、サーバー同士が自律的に調停を行い、わずか数ミリ秒で制御権をフェイルオーバーできます。
ゲートウェイ高可用化(HA)における3つの選択肢
Linux環境において、システムエンジニアは主に次の3つのアプローチを検討します。
- クラウド型ロードバランサー(AWS ALB、GCP LBなど):運用の手間がなく、プロバイダにより99.99%のSLAが保証されます。一方、スループットが数百Mbps以上に跳ね上がるとコストが急増する点がデメリットです。また、オンプレミス環境や専用サーバー、自前で構築したProxmox/KVM仮想化クラスタには適用できません。
- Pacemaker + Corosync:スプリットブレイン対策のフェンシング機能や複雑なリソース管理に優れた、エンタープライズ標準のクラスタリングソリューションです。その反面、設計・設定が極めて複雑であり、2ノード構成ではqdeviceを適切に設定しないとクォーラム喪失エラーに陥りやすい難点があります。
- Keepalived(VRRPプロトコル):非常に軽量でメモリ消費量は20MB未満、単一の設定ファイルだけで完結します。Keepalivedは内部ネットワーク越しにハートビートを送信し続け、プライマリ機に障害が発生すると、わずか1〜2秒でバックアップ機のNICへVirtual IPを引き継ぎます。
Ubuntu ServerでのKeepalived + VIP標準導入手順
NginxやHAProxyを採用する多くの中〜大規模Webシステムにおいて、Keepalivedは安定性と運用コストのバランスが最も優れた選択肢です。以下に、即座に実運用へ適用できる標準的なMaster – Backup構成の手順を示します。
想定ネットワーク構成
- Node 01(Master):IP
192.168.1.11、NICeth0 - Node 02(Backup):IP
192.168.1.12、NICeth0 - Virtual IP(共有VIP):IP
192.168.1.100(クライアント向け公開IPまたはDNSの指定先)
ステップ1:Keepalivedのインストール
両ノードでaptリポジトリから必要なパッケージをインストールします:
sudo apt update
sudo apt install -y keepalived psmisc
補足:psmiscパッケージには、後続ステップのNginx監視スクリプトで利用するkillallコマンドが含まれています。
ステップ2:Non-local IP bindingの有効化
Linuxのデフォルト設定では、自身のNICに割り当てられていないIPアドレスへのバインド(bind)が禁止されています。このままではNode 02がVIPを保持していない間、Node 02上のNginxが起動できません。ip_nonlocal_bindフラグを有効化してこの問題を解決します:
echo "net.ipv4.ip_nonlocal_bind=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
ステップ3:Masterノード(192.168.1.11)の設定
/etc/keepalived/keepalived.confにメイン設定ファイルを新規作成します:
sudo nano /etc/keepalived/keepalived.conf
設定内容:
global_defs {
router_id lb01
enable_script_security
script_user root
}
vrrp_script check_nginx {
script "/usr/bin/killall -0 nginx"
interval 2
weight -20
fall 2
rise 2
}
vrrp_instance VI_WEB {
state MASTER
interface eth0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass M@tKhauB@oMat123
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:vip
}
track_script {
check_nginx
}
}
重要ポイントの解説:
virtual_router_id 51:VRRPグループを識別するIDです。MasterとBackupで必ず同一の値を指定します(範囲:1〜255)。priority 101:優先度です。最も高いpriorityを持つノードがVIPを保持します。vrrp_script check_nginx:2秒ごとにNginxの生存確認を行います。Nginxが停止した場合、priorityが20減算されて81になります。これによりBackup側の100を下回り、直ちにVIPのフェイルオーバーがトリガーされます。
ステップ4:Backupノード(192.168.1.12)の設定
Node 02側でも同様に設定ファイルを作成します:
sudo nano /etc/keepalived/keepalived.conf
設定ファイルの内容:
global_defs {
router_id lb02
enable_script_security
script_user root
}
vrrp_script check_nginx {
script "/usr/bin/killall -0 nginx"
interval 2
weight -20
fall 2
rise 2
}
vrrp_instance VI_WEB {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass M@tKhauB@oMat123
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:vip
}
track_script {
check_nginx
}
}
主な違いは、stateがBACKUPになり、priorityが100に設定されている点のみです。
ステップ5:サービスの起動と動作確認
両ノードでKeepalivedサービスを起動および自動起動有効化します:
sudo systemctl enable --now keepalived
Node 01(Master)のネットワークインターフェースを確認します:
ip addr show eth0
セカンダリIPとして192.168.1.100/24が表示され、ラベルeth0:vipが付与されていることが確認できます。Backupノードで同じコマンドを実行すると、本来の静的IPのみが表示されます。
障害発生時のフェイルオーバー試験(Failover test)
クライアント端末でターミナルを開き、VIPに対して継続的にpingを実行します:
ping 192.168.1.100
Node 01側で意図的にNginxを停止させ、障害状態をシミュレートします:
sudo systemctl stop nginx
pingを実行しているターミナルを確認してください。パケットの欠落はわずか1回(約1秒程度のタイムアウト)にとどまるはずです。続いてNode 02側でip addr show eth0を実行すると、VIP 192.168.1.100が副系ノードへ即座に移動していることが確認できます。クライアントからのHTTPリクエストも正常に200 OKを返却し続けます。
Node 01側でNginxを再起動(sudo systemctl start nginx)すると、Masterのpriorityが101へと復旧します。preempt(優先引き戻し)機構により、VIPは直ちにNode 01へとフェイルバックされます。これで、ゲートウェイのハードウェア障害にも自動復旧できる堅牢な高可用性環境が完成しました。

