glusterfs on zfsで高可用性サーバの構築
glusterfsとzfs
glusterfsは分散ファイルシステムの一つで、複数のコンピュータにあるディスクを全体として一つのディスクとして利用できる。各コンピュータにはglusterfs専用の領域(brickという)を一般的なファイルシステム上に用意し、それをデーモンであるglusterdを通して複数台運用する。クライアントからはnfsと似た感じで使用できるが、たとえばレプリケーション(複製)モードでglusterfsを構築していた場合、サーバのどれかがダウンしていてもクライアントからは常に問題なく利用できる。ストライプモードにすれば、非常に効率的な負荷分散となるだろう。glusterfsに用いるbrick領域は一般的なファイルシステム上にある。つまりext4やxfsファイルシステムをまず作り、その上にbrickディレクトリを作り、それをデーモンに供与する。従って、このバックエンドファイルシステムはなんでもよいが、信頼性のあるものがよい。そこでzfsである。
zfsは分散ファイルシステムではなく、OS自体のファイルシステムの一つであり、複数ディスクをRAIDにする機能やスナップショット機能など、いろいろできる素晴らしいファイルシステム。とくにRAIDZ2モードなどを用いれば多数のディスクのうち2つが故障しても依然何事もなかったかのように稼働する。非常に高い信頼性と利便性を持つファイルシステムを構築できる。
以前はglusterfsはバックエンドとしてzfsには対応していなかったが、今はしており、且つ、zfsのスナップショット機能を使える。さらにUbuntu 24.04 では、すべての必要なソフトが標準レポジトリにあるので、インストールも非常に簡単。だから各サーバではzfsでファイルシステムを構築しておき、glusterfsクラスタを構築する事で高可用性ファイルサーバが簡単に構築できる。
前提知識
この記事はglusterfsをzfs上に構築してスナップショットとったりする方法の解説で、サーバのssh経由での管理やzfsの管理についてはすでに習得していることを前提にしている。
今回構築するシステム
今回は、所属機関でssh gateway server(組織内に外からsshアクセスするための唯一の入り口サーバー)を構築する事になった。その際の要求項目は以下の通り。
- 複数台入り口サーバを作り、DNSラウンドロビンで負荷分散
- 入り口サーバの一つが落ちてても他でサービスを継続可能
- 入り口サーバ毎にhomeがあるとssh public keyの管理などが面倒なのでhomeは共通にしたい
- だからといって他にNFSサーバのようなものを用意してそれに依存したくない。
- 極端な話、ルータやスイッチ以外の機関内の他のシステムは全部止まっていてもサーバのIPさえわかればなんとかたどり着けるようにしたい
これらを全部解決するのがglusterfs on zfsなのだが、なぜzfsなのかと言われるとあまり強い理由はなく、別にzfsじゃなくても良い。zfsだとスナップショットがとれたり、まぁかっこいい。というか他にもっとすごい高可用性サーバを構築するときの参考にもなるかと。
今回は以下三の台のマシンでglusterfsクラスタを組む。IPアドレスの上位24ビットはX.Y.Zで隠してある。
- watt1: X.Y.Z.205 (実マシン, N100) ssh gateway server
- watt2: X.Y.Z.206 (実マシン, N100) ssh gateway server
- watt-storage: X.Y.Z.207 (仮想マシン) split-brain を避けるためのブリック供与のみに使う
実際にsshサービスを外に公開するのはwatt1, watt2のみであり、glusterfsクライアントとなるのも、watt1, watt2である。これらのホームディレクトリをglusterfsで運用する。
ではwatt-storageという3台目が何かというと、もしwatt1, watt2の2台だけでレプリケーションしていただけだと、split-brain問題[3]が起きやすくなるた。このあtめ3台目としてwatt-storageを設ければ、問題が起きる確率は抑えられる(ゼロにはならない)。
だからwatt-storageはログインには使用されずbrickを供与するだけとなり、watt1, watt2ともにN100の非力なマシンなので、組織奥底のサーバの仮想マシンで充分という判断。
設定
以下はすべて Ubuntu Server 24.04.2 で実施した場合の例。
すべての操作はroot権限での実行が必要なので、適宜sudoを前につける。
パッケージインストール
必要なパッケージは以下のみ。すべてのマシンで以下を実行する。
# apt install glusterfs-server glusterfs-client zfsutils-linux
zfs
ネットにたくさん記事があるので、zfsの設定の詳解はここでは割愛する。zfsの設定としては、まずプールまで作成しておく。今回は3台ともだいたい以下のような状態にもっていく。
# df -h
Filesystem Size Used Avail Use% Mounted on
zpool1 384G 128K 384G 1% /zpool1
glusterfs
流れとしては、gluster volumeのbrick(ファイル断片置き場)ディレクトリをzfsファイルシステムのルートに配置する。
[1] を見ると、ディレクトリツリーは例えば
/data/glusterfs/<volume>/<brick>/brick
という構造になっているのがお勧めで、<brick>はマウントポイントで。その後に続くbrickがファイル断片を入れるディレクトリ。
他のネット上の記述(たとえば[2]は良い記事なのだが)ではbrickディレクトリ内にgluster volume用のディレクトリを作るような構造で説明しているものもあるが、どうやらそれはあまり正しくないようである。特にzfsをバックエンドに使うと、そのファイルシステムのルート、つまりこの場合<brick>/.zfs/snapshotにスナップショットが作られるようになるが、同じ<brick>で示されるファイルシステムに複数のボリュームがあると、あるボリュームのスナップショットをとると他のすべてのボリュームもとられてしまう。
ところで、上記お勧め構造において、<volume> 部分はどのボリューム用のブリックファイルシステムかを明示するためにそうしておけばいい、という事のようなので、これはなんでもよい。今回は、
/gluster/<volume_brick>/brick
という構造にし、<volume_brick>をマウントポイントにして、階層が一つ減らしてシンプルにする。<brick>の代わりに<volume_brick>がマウントポイントである。依然としてマウントポイントの中にbrickディレクトリがあるので、これで問題ない。
実行
以下、3台すべてのマシンで実行せねばならないものを<all nodes>、どれか1台で1回だけ実行すべきものを<one node>として記述する。特に指定がないものはどのノードで何回しても良い。
まずデーモンを開始。
# systemctl enable glusterd
# systemctl start glusterd
いったんリブートし、statusを確認のこと。
次にノード間を関連付ける。例えばX.Y.Z.205 (watt1)から以下のように実行する。
# gluster peer probe X.Y.Z.206
# gluster peer probe X.Y.Z.207
こうすることで、3台のマシンが協調するように設定される。
状況確認をする。
# gluster peer status
Number of Peers: 2
Hostname: X.Y.Z.206
Uuid: a03f2828-db46-4c4b-a35b-02026915b521
State: Peer in Cluster (Connected)
Hostname: X.Y.Z.207
Uuid: cdb11a44-4502-489b-9cc6-6bd472d9f273
State: Peer in Cluster (Connected)
どのマシンからやってもほぼ同様に見えるはず。これで3つのマシンがクラスタ化された(まだファイルシステムはない)。
Brick用ファイルシステムを作成してマウントする。gv1が今回作成するボリューム名。ファイルシステム名は前述の<volume_brick>に相当するものであるが、brickという単語は明らかなので含めない。
# mkdir -p /gluster/gv1
# zfs create zpool1/gv1
# zfs set mountpoint=/gluster/gv1 zpool1/gv1
ところでzfsのルートであるzpool1はもうマウントポイントいらないので無しにする。
# zfs set mountpoint=none zpool1
いざという時用に、一応ファイルシステムのサイズ上限をプールより小さめに設けておく(wattはただのssh gatewayなので、全然ディスクはいらない)
# zfs set quota=300G zpool1/gv1
gluster brickディレクトリを作成する。サーバとクライアントが同一の場合はパーミッションを閉じておいたほうが良い。ただし、brickを閉じると、マウントしたボリュームにもそれが反映されてしまうので、根元の/glusterを閉じておく。
# mkdir /gluster/gv1/brick
# chmod go-rwx /gluster
いよいよgluster volumeを作成する。
# gluster volume create gv1 replica 3 X.Y.Z.205:/gluster/gv1/brick X.Y.Z.206:/gluster/gv1/brick X.Y.Z.207:/gluster/gv1/brick
volume create: gv1: success: please start the volume to access data
上記の意味は、gv1というボリュームを、3台のレプリカモード(ミラーリング)で作り、各マシンの/gluster/gv1/brickをBrick置き場に使う、という意味。
次にボリューム開始を行う。これをしないと使えない。
# gluster volume start gv1
そうするとどのマシンから見てもgv1の中になにやら管理ファイルが作られる
# ls -a /gluster/gv1/brick
. .. .glusterfs
statusは以下のように確認
# gluster volume info
クライアントからマウントしてみる。
# mount -t glusterfs X.Y.Z.205:/gv1 /mnt
問題なければ各マシンの/etc/fstabに登録する。登録方法は例えば、
:
watt1,watt2,watt-storage:/gv1 /gv1 glusterfs defaults 0 0
のようにすれば、どれかを経由してつないでくれるが、実は
:
watt1:/gv1 /gv1 glusterfs defaults 0 0
と一つだけ書いても、きちんと他のノードも認識してくれ、マウント後はwatt1が一時的に落ちても問題なく利用を続ける事ができる。
ただし、この方法ではnamedが立ち上がってないとマウントに失敗するという問題がある。
さらに、watt類は全部対等でどれがプライマリとかそういうのは無いにも関わらずdfをとるとwatt1しか出てこないのはなんだかかっこ悪い。そのために、/etc/hostsに以下のような記述を追加するとよいだろう。
:
X.Y.Z.207 watt-collective
IP Addressとしては、マウント時にそのアドレスのサーバが立ち上がっていないと失敗するので、今回のようにサーバ・クライアントが同じマシンらの場合は、それぞれのマシンで自分自身のアドレスにしておけば無難ではないかと思われる。つまり上記はwatt-storageの例。
そして、/etc/fstabで
:
watt-collective:/gv1 /gv1 glusterfs defaults 0 0
としておき、マウントする。
# mkdir /gv1
# mount -a
# df
Filesystem 1K-blocks Used Available Use% Mounted on
:
watt-collective:/gv1 314572672 4024192 310548480 2% /gv1
かっこいい。
以上で基本設定は終了。リブートして問題がないか確認
スナップショット
スナップショットをとってみる。
# gluster snapshot create test gv1
# gluster snapshot list
test_GMT-2025.07.05-07.57.25
このようにtestという名前の後に日付が自動で付け加わる(付け加えなくすることもできる)。
snapshotはzfs上でつくられるので、zfsブリックファイルシステムに行き、スナップショットを見てみると、それらしいものがある。
# ls -l /gluster/gv1/.zfs/snapshot/
total 0
drwxrwxrwx 1 root root 0 Jul 5 07:57 41e4cbef022443bf9a188921ad6e6111_0
中を覗くと普通にbrickが見える。
スナップショットを普通のファイルシステムみたいに参照したいときは、アクティベートしてからマウントできる。名前の指定の仕方を間違えないように。
# gluster snapshot activate test_GMT-2025.07.05-07.57.25
Snapshot activate: test_GMT-2025.07.05-07.57.25: Snap activated successfully
# mount.glusterfs X.Y.Z.205:/snaps/test_GMT-2025.07.05-07.57.25/gv1 /mnt
勿論書き込みはできない。
スナップショットの消去は以下でできる。
# gluster snapshot delete test_GMT-2025.07.05-07.57.25
スナップショットの詳細設定
glusterfsのスナップショットは結構便利な機能を含んでいて、例えばスナップショットの自動消去などがある。
以下のようにしてスナップショットのより詳細なセットアップが見れる。
# gluster snapshot config
Snapshot System Configuration:
snap-max-hard-limit : 256
snap-max-soft-limit : 90%
auto-delete : disable
activate-on-create : disable
Snapshot Volume Configuration:
Volume : gv1
snap-max-hard-limit : 256
Effective snap-max-hard-limit : 256
Effective snap-max-soft-limit : 230 (90%)
root@watt2:/home# man gluster
snap-max-hard-limitは、これ以上スナップショットを作れなくなるリミット、snap-max-soft-limitは警告リミット。いずれもauto-deleteがdisalbeの場合であって、auto-deleteをenableにすると、snap-max-soft-limitがきた段階で古いスナップショットが消去される。
これは結構便利そうなので、auto-deleteをenableにし、snap-max-hard-limitを60としてみる。
gluster snapshot config gv1 snap-max-hard-limit 90
gluster snapshot config auto-delete enable
auto-deleteはグローバルなので、ボリューム名なしで指定する。これでおよそ90の90%=81個のスナップショットで古いのから消去される。
あとはどれか一つのノードのcronにスナップショットコマンドを仕込めば良い。以下はその例。
SHELL=/bin/sh
0 1 * * * gluster-snapshot.sh >> gluster-snapshot-`date +\%u`.log 2>&1
#! /usr/bin/bash
gluster snapshot create daily gv1
これで一日一回スナップショットがとられ、80日めころから過去のものは削除される。
セキュリティ設定
glusterfsはNFSのように他のマシンからボリュームをマウントできるが、NFSの/etc/exportsのようなアクセスを制限するような機能はない。このままではIPアドレスとボリューム名さえわかれば同じローカルネットのどのホストからもマウントできてしまう。
従って、今回はUbuntuなのでufwを用いて管理する。
まずイネーブルする。
# ufw status
Status: inactive
# ufw default deny
# ufw allow ssh
Rules updated
Rules updated (v6)
root@watt-storage:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
root@watt-storage:~# ufw status
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
この時点で、これをしたマシン以外のマシンからglusterのピアステータスを見ると Disconnectedとなる(ただしリブートしないと以前のコネクションが設立のままかもしれない。その場合はまだConnectedに見える)。
引き続きクラスタの他のマシンからの接続を許可。
ufw allow from X.Y.Z.205
ufw allow from X.Y.Z.206
ufw allow from X.Y.Z.207
しばらくすると他のマシンから見てもConnectedに戻る。
その他
1ファイルシステムに1ボリュームが良い
同じzfsマウントポイントに複数のボリュームのBrickを作成すると、スナップショットを特定のボリュームにとって実施しても、Brickが同じファイルシステムにあるので、他のボリュームもスナップショットされてしまう。まぁそういう仕様というか、zfsのスナップショットはそもそもファイルシステム全体を単位としてしかできないので、あたりまえ。
だからzfsファイルシステムは各ボリューム専用とし、その中にそのボリュームのbrickディレクトリが作るのはよい。幸いzfsは同一pool上にいくつでもファイルシステムが作れる。
私が推奨する一番シンプルなファイルシステム構造は以下のような感じ。
/gluster//gv1/brick
/gluster//gv2/brick
:
//はマウントポイントであることを示す。
brickディレクトリは必要か?
brickディレクトリをBrick用につくったファイルシステムの中にわざわざつくらないといけないのはなんで?と思うので、試しに、brickがzfsファイルシステムそのものになるようにするとどうなるかやってみると、
zfs create zpool1/testvol
mkdir /gluster/testvol
zfs set mountpoint=/gluster/testvol zpool1/testvol
# gluster volume create testvol replica 3 X.Y.Z.205:/gluster/testvol X.Y.Z.206:/gluster/testvol X.Y.Z.207:/gluster/testvol
volume create: testvol: failed: The brick X.Y.Z.206:/gluster/testvol is a mount point. Please create a sub-directory under the mount point and use that as the brick directory. Or use 'force' at the end of the command if you want to override this behavior.
このように、マウントポイントの中にbrickディレクトリを作らないとだめのようである。名前はなんでもよいだろうが、多分直下じゃないとだめなのではないかと思われる。気になる人は実験してください。多分できたとしてもzfsのスナップショットが動かないのでは。
参考資料
[1] https://docs.gluster.org/en/v3/Administrator Guide/Brick Naming Conventions/
[2] https://qiita.com/nagano_m/items/ebb0b1dead85866da176
[3] https://sites.google.com/site/glusterfstech/recovery/split-brain
Discussion