共有ストレージにおける単一障害点(SPOF)
共有ストレージは、高トラフィックなアーキテクチャにおいて最も脆弱なボトルネックになりがちです。NginxのWebワーカー群、Kubernetesクラスタ、メディアトランスコーディングノードなど、どのような環境であっても、多くのチームはまず標準的なNetwork File System(NFS)共有から始めます。導入自体は非常に手軽です。
しかし、NFSは運用上の地雷を抱えています。単一のNFSサーバーでドライブ障害やカーネルパニックが発生したり、定期的なカーネルパッチ適用で再起動したりすると、マウントしているすべてのクライアントがハングアップします。わずか数秒でI/Oブロックが蓄積し、PHP-FPMやNodeのワーカープールが飽和、ロードバランサーのヘルスチェックに失敗し、最終的にはアプリケーション全体がダウンしてしまいます。
エンタープライズ向けのSANハードウェアを導入すればこの単一障害点は解消できますが、独自のファイバーチャネルスイッチは平気で数百万円規模のコストがかかります。また、AWS EBSやGCP Persistent Diskなどのクラウドブロックストレージはディスクの信頼性を高めてくれますが、マルチアタッチオプションは高価で、リージョン制限があり、設定も一筋縄ではいきません。そこで求められるのが、一般的な汎用Linuxサーバー上で直接動作し、ローカルのNVMeやSATA SSDをプールして、脆弱な中央メタデータサーバーに依存することなく同期的に書き込みを複製できるソフトウェア定義ストレージ(SDS)です。
GlusterFSの基本的な仕組み
GlusterFSは、ネットワーク上の複数ノードにあるローカルファイルシステムを束ね、単一の柔軟なネットワークボリュームとして集約します。Cephを扱ったことがある方なら、専用のモニターノード、OSDデーモン、メタデータマネージャー(MDS)を管理する運用負担をご存知でしょう。GlusterFSは、それらすべてを必要としません。
メタデータサーバー不要:エラスティックハッシュアルゴリズム(DHT)
GlusterFSには中央の参照テーブルが存在しません。アプリケーションが/uploads/avatar_9482.jpgを書き込む際、Glusterクライアントはファイルパスの32ビットDavies-Meyerハッシュを計算します。このハッシュ値が、クラスタのボリュームマップ内にある特定のブリックに直接マッピングされます。クライアントはネットワーク経由でメタデータを問い合わせるのではなく、純粋な数学的計算によってファイルの場所を特定するため、単一のインデックスサーバーがディレクトリ検索のボトルネックになることがありません。これにより、メタデータのレイテンシスパイクに悩まされることなく、ストレージ層を数百テラバイト規模まで水平スケールできます。
主要コンポーネント
- Brick(ブリック): ストレージの最小構成要素。マウントされたPOSIXファイルシステム(必ずXFSでフォーマット)上のエクスポートディレクトリを指します。
- Trusted Storage Pool(信頼ストレージプール): 相互に認証されピアリング接続された、ストレージボリュームを管理するLinuxサーバーのクラスタ。
- Replicated Volume(レプリケートボリューム): 2つ以上のブリック間でファイル操作を同期的に複製するボリューム。ノードAに障害が発生しても、ノードBがクライアントのマウントを切断することなく読み書きリクエストを継続処理します。
- Arbiter Node(アービターノード): 実データペイロードの代わりにファイルメタデータと拡張属性(xattr)のみを保持する特別なクォーラムブリック。RAWディスク容量のわずか0.1%程度のリソースでスプリットブレインを防止します。
スプリットブレインとクォーラムが必要な理由
クォーラムを設定しない2ノードレプリケーションには高いリスクが伴います。クライアントからは両ノードに接続可能な状態で、ストレージノード間のプライベートネットワーク接続だけが切断された場合、双方のサーバーが相手をダウンしたと判断します。その結果、双方が同じファイルに対して異なる書き込みを受け入れてしまい、自動化ツールでは安全に修復できないデータの不整合(スプリットブレイン)が発生します。
面倒なデータ手動修復を避けるには、必ずクライアントサイドクォーラムを有効にするか、軽量な第3のアービターノードを追加してください。ノードの厳格な過半数から応答が得られた場合にのみ書き込みが成功するように設計します。コンセンサスが得られない場合は、ボリュームの破損を防ぐために書き込みがクリーンに失敗します。
実践手順:レプリケートボリュームの構築
ここでは、2ノードのレプリケートストレージクラスタと独立したアプリケーションクライアントを構築します。検証環境はプライベートサブネット10.0.10.0/24上のUbuntu 22.04 LTSで構成されています:
gluster01.lab.local: 10.0.10.11(ストレージノード1)gluster02.lab.local: 10.0.10.12(ストレージノード2)appclient01.lab.local: 10.0.10.20(アプリケーションクライアント)
ステップ1:ネットワークの準備と名前解決
GlusterFSのノードはホスト名でお互いを識別します。DNS名前解決が不安定だとクラスタのハートビート途絶につながるため、3台すべてのサーバーの/etc/hostsに内部IPを直接定義しておきます:
cat << 'EOF' | sudo tee -a /etc/hosts
10.0.10.11 gluster01.lab.local gluster01
10.0.10.12 gluster02.lab.local gluster02
10.0.10.20 appclient01.lab.local appclient01
EOF
必要なファイアウォールポートを開放します。Glusterは管理用にTCP 24007、RDMA用に24008を使用し、アクティブなブリックごとに49152以降の動的ポートを割り当てます:
sudo ufw allow 24007:24008/tcp
sudo ufw allow 49152:49156/tcp
sudo ufw reload
ステップ2:専用ストレージディスクのパーティショニングとフォーマット
本番環境のブリックをルートパーティション内に配置することは絶対に避けてください。ルートディスクの空き容量が枯渇するとOSがロックアップするだけでなく、ルートのinodeテーブルは並行書き込み処理に弱いためです。物理ドライブまたはクラウドディスクの専用ボリューム(ここでは500GB SSDの/dev/sdb)を用意します。
gluster01とgluster02の両方で以下のコマンドを実行します:
# Glusterの拡張属性を格納するために512バイトのinodeでフォーマット
sudo mkfs.xfs -f -i size=512 /dev/sdb
# マウントディレクトリを作成し永続的なfstabを設定
sudo mkdir -p /data/glusterfs/brick1
echo "/dev/sdb /data/glusterfs/brick1 xfs defaults,noatime,nodiratime,inode64 0 2" | sudo tee -a /etc/fstab
sudo mount -a
# 新しくマウントしたパーティション内にブリックディレクトリを作成
sudo mkdir -p /data/glusterfs/brick1/gv0
ステップ3:GlusterFSのインストールとプールの形成
両方のストレージノードに、メンテナンスされているGlusterFS 10 LTSパッケージをインストールします:
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:gluster/glusterfs-10
sudo apt update
sudo apt install -y glusterfs-server
sudo systemctl enable --now glusterd
systemctl status glusterdでサービスが実行中であることを確認します。次にピアリングを確立します。このコマンドはgluster01でのみ実行してください:
sudo gluster peer probe gluster02.lab.local
いずれかのノードから接続状態を確認します:
sudo gluster peer status
出力結果にState: Peer in Cluster (Connected)と表示されていることを確認してください。もし切断状態(Disconnected)と表示される場合は、DNS名前解決を確認し、nc -zv gluster02.lab.local 24007を実行してポート24007へのTCP接続をテストしてください。
ステップ4:レプリケートボリュームの作成と起動
続いて、vol_sharedという名前のレプリケートボリュームを作成します。gluster01から以下を実行します:
sudo gluster volume create vol_shared replica 2 \
gluster01.lab.local:/data/glusterfs/brick1/gv0 \
gluster02.lab.local:/data/glusterfs/brick1/gv0
sudo gluster volume start vol_shared
起動したボリュームの状態を確認します:
sudo gluster volume info vol_shared
Status: Startedと表示され、レプリカセットの下に両方のブリックがリストされているはずです。
ステップ5:本番運用のためのパフォーマンスチューニング
GlusterFSのデフォルト設定は速度よりも最大限の安全性を重視しています。ユーザーメディアの配信やマイクロサービスのログ収集のような高並行環境では、そのままでは書き込みパフォーマンスが低く感じられることがあります。gluster01で以下のチューニングオプションを適用してください:
# 小さな書き込みをRAMにバッファリングして非同期にフラッシュ
sudo gluster volume set vol_shared performance.write-behind on
sudo gluster volume set vol_shared performance.write-behind-window-size 4MB
# 連続ブロックをプリフェッチしてシーケンシャルリードを高速化
sudo gluster volume set vol_shared performance.read-ahead on
sudo gluster volume set vol_shared performance.read-ahead-page-count 4
# アクセス頻度の高いディレクトリ検索用キャッシュを拡張
sudo gluster volume set vol_shared performance.cache-size 512MB
# フェイルオーバーを迅速に発生させるためネットワークタイムアウトを42秒から10秒に短縮
sudo gluster volume set vol_shared network.ping-timeout 10
# スプリットブレインを防ぐためクォーラムを強制
sudo gluster volume set vol_shared cluster.quorum-type auto
100の並行ワーカーが25KBのアセットファイルを書き込むテスト環境では、これらのチューニングパラメータによりiowaitが38%から6%へと低下し、書き込みレイテンシは45msから8ms未満へと劇的に短縮されました。
ステップ6:クライアントノードでの共有マウント
appclient01に切り替えます。Glusterの共有は通常のNFS経由でもマウントできますが、ネイティブのGlusterFS FUSEクライアントの使用を常に推奨します。ネイティブクライアントはクラスタトポロジーを直接参照し、すべてのレプリカブリックに同時書き込みを行うとともに、Keepalivedのような外部ロードバランサーや仮想IP(VIP)を必要とせずに内部でフェイルオーバーを処理します。
sudo apt update
sudo apt install -y glusterfs-client
sudo mkdir -p /mnt/distributed-storage
# 最初の接続ポイントとしてgluster01を使用して共有をマウント
sudo mount -t glusterfs gluster01.lab.local:/vol_shared /mnt/distributed-storage
クライアントの再起動後もマウントが維持されるよう永続化します。/etc/fstabを編集し、backup-volfile-serversディレクティブを含めます。これにより、クライアント起動時にgluster01がオフラインであっても、ドライバが自動的にgluster02へ接続します:
echo "gluster01.lab.local:/vol_shared /mnt/distributed-storage glusterfs defaults,_netdev,backup-volfile-servers=gluster02.lab.local 0 0" | sudo tee -a /etc/fstab
ステップ7:フェイルオーバーと自己修復(セルフヒーリング)の検証
クラスタの動作検証を行います。appclient01からテストファイルを50個書き込みます:
cd /mnt/distributed-storage
for i in {1..50}; do echo "ペイロード チェックサム $i" > "file_$i.txt"; done
ls -1 | wc -l
ここでノードの完全クラッシュをシミュレートします。gluster01でネットワークリンクを切断するか、制御デーモンとブリックプロセスの両方を停止します:
# gluster01上で実行
sudo systemctl stop glusterd
sudo pkill -9 -f glusterfsd
すぐにappclient01に戻り、間を置かずにデータの読み書きを試行します:
cat /mnt/distributed-storage/file_1.txt
echo "ノード1停止中の新規書き込み" >> /mnt/distributed-storage/file_failover_test.txt
コマンドは正常に完了します。クライアントは2秒未満でトラフィックをgluster02にリダイレクトします。I/Oエラーは発生せず、マウントポイントがロックされることもありません。
次に、gluster01を復旧させます:
# gluster01上で実行
sudo systemctl start glusterd
Glusterの自己修復デーモン(glustershd)がバックグラウンドで同期を処理します。gluster01から修復キューを確認します:
sudo gluster volume heal vol_shared info
保留中の修復エントリが数秒以内にゼロに減少するのが確認できます。クラスタは再び正常な状態に戻りました。
現場で培われた本番運用のベストプラクティス
- レプリケーショントラフィックを分離する: クライアントがネットワーク経由で各ブリックにデータを複製するため、GlusterFSでは書き込みトラフィックが倍増します。バックアップ同期がパブリックAPIの帯域幅を圧迫しないよう、ストレージトラフィックは独立したVLANまたは専用の10GbE NICに配置してください。
- 2ノード+1アービター構成を採用する: 純粋な2ノードクラスタでは、クォーラム設定で難しいトレードオフを強いられます。月額数百円程度の1vCPU・ディスク10GB程度の小型VPSを用意してアービターノード(
replica 3 arbiter 1)として機能させましょう。アービターはクォーラムの投票に参加しメタデータのみを保持するため、高価な3台目のフルストレージマシンを購入することなく、万全のスプリットブレイン対策が実現できます。 - バックエンドのブリックを直接操作しない: サーバーホスト上の
/data/glusterfs/brick1/gv0内でファイルを直接追加・編集・削除しないでください。Glusterはファイルのリビジョン追跡を拡張属性(trusted.gfidやtrusted.afr.*)に依存しています。ファイルを直接変更すると変換層がバイパスされ、即座に同期が破損してスプリットブレインの原因となります。 - inode枯渇に注意する: 画像のサムネイルなど大量の小さなファイルを保存する場合、ディスクのRAW容量が枯渇するよりはるか前にファイルシステムのinodeが枯渇してしまいます。スモールファイル主体のワークロードでは、フォーマット時に
-Nオプションなどでinode数を多めに確保してください。
まとめ
GlusterFSは、エンタープライズのライセンス料や複雑なクラスタアーキテクチャを必要とせず、高い信頼性を誇るソフトウェア定義ストレージを提供します。専用のXFSパーティションに適切なキャッシュパラメータとクォーラムルールを組み合わせることで、一般的なLinuxサーバー上で自動ノードフェイルオーバーと自己修復による高い復旧性を実現できます。専用のネットワークインターフェースでクラスタを構築し、セルフヒーリングの状況を監視することで、ストレージの単一障害点に悩まされる日々から解放されるでしょう。

