Linux環境におけるKeepalivedによる高可用性構成:仮想IPとフェイルオーバーの自動化

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

高可用性戦略の比較:Keepalived vs. Pacemaker vs. DNSフェイルオーバー

火曜日の深夜2時15分、PagerDutyのアラートが鳴り響きました。カーネルパニックによってプライマリNginxプロキシがフリーズし、1,200のアクティブ接続が切断され、受信トラフィックが虚空へと消え去ったのです。このような緊急事態において、自動フェイルオーバーの有無が決定的な差を生み出します。障害をわずか5秒で収束させられるか、あるいはその後2時間も顧客からの問い合わせ対応に追われることになるかの分かれ道となります。本番環境の運用チームでは、通常以下の3つのアプローチから選択します。

  • Keepalived(VRRP – Virtual Router Redundancy Protocol):複数のノードが単一のフローティング仮想IP(VIP)を共有します。1台のノードがMASTERとして動作してARPリクエストに応答し、BACKUPノードは1,000ミリ秒ごとにVRRPマルチキャストハートビートを監視します。このハートビートが途絶えるかカスタムヘルスチェックが失敗すると、バックアップ機が即座にVIPを引き継ぎます。
  • Pacemaker + Corosync:複雑なリソーストポロジ、共有ブロックストレージ(DRBD)、STONITHノードフェンシングなどを想定して設計された重量級のクラスタリングスイートです。多数のマシンにまたがるステートフルなマルチサービスフェイルオーバーを協調制御します。
  • DNSベースのフェイルオーバー:外部の外形監視プローブがエンドポイントの稼働状態を監視し、オリジンサーバーがオフラインになった際にAPI経由でDNSのA/AAAAレコードを書き換えます。

メリット・デメリット:最適なフェイルオーバー層の選定

ミリ秒単位の可用性が求められる現場では、アーキテクチャの選択に明確なトレードオフが存在します。

  • Keepalived
    • メリット:極めて軽量です。消費メモリは15MB未満で、CPUリソースもほとんど消費しません。クライアント側の再設定を一切必要とせず、レイヤー3/4で1〜2秒以内にフェイルオーバーを完了できます。また、設定ファイルもシンプルで可読性に優れています。
    • デメリット:明示的なユニキャストピアを設定しない限り、両ノードが同一のレイヤー2ブロードキャストドメイン(VLANまたはサブネット)を共有している必要があります。また、多層構造のサービス依存関係に対するネイティブなオーケストレーション機能はありません。
  • Pacemaker + Corosync
    • メリット:アクティブ/アクティブクラスタ、厳格な起動順序制御、共有ストレージのロック機構、スプリットブレインを確実に阻止するハードウェアフェンシングをサポートします。
    • デメリット:学習コストが高く、メモリオーバーヘッドも無視できません。フェンシング設定を1箇所でも誤ると、サーバー群が無限再起動ループに陥るリスクがあります。
  • DNSフェイルオーバー
    • メリット:サブネットの制約を受けることなく、異なるクラウド、リージョン、ルーティングネットワークを跨いでグローバルに機能します。
    • デメリット:クライアント側のリゾルバキャッシュの影響を強く受けます。TTLを30秒に短縮していても、上位ISPのリゾルバが有効期限を無視することが多く、停止したIP宛てに10〜15分間トラフィックが流れ続ける恐れがあります。

推奨アーキテクチャ:フローティングVIPによるアクティブ/パッシブ構成

HAProxyやNginxなどのリバースプロキシおよびロードバランサーには、Keepalivedを用いたアクティブ/パッシブペアが最適な構成となります。2 vCPU、4GB RAMを搭載したUbuntu 22.04の仮想マシン2台による検証クラスタでは、この構成でわずか1.4秒のフェイルオーバーを実現しました。重厚なクラスタリングスタックのような分散状態管理の煩わしさを抱えることなく、この速度を達成できる点が大きな強みです。

本番環境における標準トポロジは以下の通りです。

  • 仮想IP(フローティングVIP):192.168.10.100
  • ノード1(プライマリ – MASTER):eth0インターフェース上の 192.168.10.11
  • ノード2(セカンダリ – BACKUP):eth0インターフェース上の 192.168.10.12
  • ワークロード:両ノードでポート80および443をリッスンするNginx

実装ガイド:高可用性クラスタの構築手順

ステップ1:非ローカルIPへのバインドを許可

デフォルトのLinuxカーネルでは、アクティブなインターフェースにまだ割り当てられていないIPアドレスのソケットにプロセスがバインドすることを禁止しています。フローティングVIPは常にいずれか1台のサーバーにしか存在しないため、この設定を変更しないとセカンダリノードはシステム起動時にNginxを開始できません。

ノード1とノード2の両方で次のコマンドを実行します。

echo "net.ipv4.ip_nonlocal_bind = 1" | sudo tee -a /etc/sysctl.d/99-keepalived.conf
sudo sysctl --system

ステップ2:Keepalivedのインストール

ご利用のディストリビューション標準のパッケージマネージャーを使用してKeepalivedをインストールします。

# Ubuntu / Debian の場合
sudo apt update && sudo apt install -y keepalived

# RHEL / AlmaLinux / Rocky Linux 9 の場合
sudo dnf install -y keepalived

ステップ3:アプリケーションヘルスチェックスクリプトの作成

ネットワークレベルのハートビートだけに依存するのは危険です。背後のWebサービスが回復不能なワーカーデッドロックに陥っていても、ホスト自体がVRRPパケットを送信し続ける場合があるためです。Nginxがローカルリクエストに対して2秒以内に有効なステータスコードを返しているかを検証するヘルスチェックを用意します。

両方のノードに /usr/local/bin/check_service.sh を配置します。

sudo tee /usr/local/bin/check_service.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

# タイムアウトを厳格に2秒に設定してローカルHTTPエンドポイントに問い合わせ
if curl -sf --max-time 2 http://127.0.0.1:80/ > /dev/null; then
    exit 0
else
    exit 1
fi
EOF

sudo chmod 755 /usr/local/bin/check_service.sh

ステップ4:プライマリノード(MASTER)の設定

ノード1(192.168.10.11)でメインデーモンの設定ファイルを作成します。初期優先度(priority)を 101 に設定し、ヘルススクリプトに -30 のマイナスウェイトを付与します。2回連続でチェックが失敗すると、ノード1の実効優先度は101から71へ低下し、ノード2のベース優先度である90を下回る仕組みです。

sudo tee /etc/keepalived/keepalived.conf << 'EOF'
global_defs {
    router_id lb-node-01
    script_user root
    enable_script_security
}

vrrp_script chk_web {
    script "/usr/local/bin/check_service.sh"
    interval 2
    weight -30
    fall 2
    rise 2
}

vrrp_instance VI_PROD {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass VrrpS3cur3Key
    }

    virtual_ipaddress {
        192.168.10.100/24 dev eth0 label eth0:vip
    }

    track_script {
        chk_web
    }
}
EOF

ステップ5:セカンダリノード(BACKUP)の設定

ノード2(192.168.10.12)の設定を行います。state BACKUP を指定し、ベース優先度を 90 に設定している点に注目してください。

sudo tee /etc/keepalived/keepalived.conf << 'EOF'
global_defs {
    router_id lb-node-02
    script_user root
    enable_script_security
}

vrrp_script chk_web {
    script "/usr/local/bin/check_service.sh"
    interval 2
    weight -30
    fall 2
    rise 2
}

vrrp_instance VI_PROD {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 90
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass VrrpS3cur3Key
    }

    virtual_ipaddress {
        192.168.10.100/24 dev eth0 label eth0:vip
    }

    track_script {
        chk_web
    }
}
EOF

ステップ6:ファイアウォールの設定とサービスの起動

Keepalivedは標準的なTCP/UDPポートではなく、VRRP(IPプロトコル番号112)を介して通信します。ローカルファイアウォールでこのプロトコルを遮断してしまうと、両ノードがお互いを検知できなくなります。その結果、双方が自身をマスターと判断して同一のVIPをバインドし、深刻なスプリットブレインおよびパケット衝突を引き起こします。

両ノードでプロトコルを解放し、サービスを有効化します。

# UFW(Ubuntu / Debian)
sudo ufw allow in on eth0 proto 112

# Firewalld(RHEL / AlmaLinux)
sudo firewall-cmd --add-protocol=vrrp --permanent
sudo firewall-cmd --reload

# systemdをリロードしてデーモンを起動
sudo systemctl daemon-reload
sudo systemctl enable --now keepalived

ステップ7:VIPの割り当て確認とフェイルオーバーテスト

ノード1のネットワーク構成を確認します。

ip addr show dev eth0

プライマリIPと並んで 192.168.10.100/24 が表示されているはずです。一方、ノード2を確認した場合は 192.168.10.12 のみが表示されている必要があります。

次に、ノード1のWebサーバーを停止してフェイルオーバーの動作をテストします。

# ノード1で実行
sudo systemctl stop nginx

4秒以内(2秒間隔のヘルスチェックが2回失敗した後)に、ノード2のログを確認します。

sudo journalctl -u keepalived -n 25 --no-pager

ノード2が BACKUP から MASTER へ遷移し、上位スイッチのテーブルを更新するためにGratuitous ARPフレームが送信されたログが確認できるはずです。192.168.10.100 に対する継続的なHTTPリクエストでも、パケットのドロップはわずか1〜2回程度にとどまり、即座に正常通信が再開されます。

Share: