😀

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アクセスするための唯一の入り口サーバー)を構築する事になった。その際の要求項目は以下の通り。

  1. 複数台入り口サーバを作り、DNSラウンドロビンで負荷分散
  2. 入り口サーバの一つが落ちてても他でサービスを継続可能
  3. 入り口サーバ毎にhomeがあるとssh public keyの管理などが面倒なのでhomeは共通にしたい
  4. だからといって他にNFSサーバのようなものを用意してそれに依存したくない。
  5. 極端な話、ルータやスイッチ以外の機関内の他のシステムは全部止まっていてもサーバの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>として記述する。特に指定がないものはどのノードで何回しても良い。
まずデーモンを開始。

<all nodes>
# systemctl enable glusterd
# systemctl start glusterd

いったんリブートし、statusを確認のこと。
次にノード間を関連付ける。例えばX.Y.Z.205 (watt1)から以下のように実行する。

<one node>
# 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という単語は明らかなので含めない。

<all nodes>
# mkdir -p /gluster/gv1
# zfs create zpool1/gv1
# zfs set mountpoint=/gluster/gv1 zpool1/gv1

ところでzfsのルートであるzpool1はもうマウントポイントいらないので無しにする。

<all nodes>
# zfs set mountpoint=none zpool1

いざという時用に、一応ファイルシステムのサイズ上限をプールより小さめに設けておく(wattはただのssh gatewayなので、全然ディスクはいらない)

<all nodes>
# zfs set quota=300G zpool1/gv1

gluster brickディレクトリを作成する。サーバとクライアントが同一の場合はパーミッションを閉じておいたほうが良い。ただし、brickを閉じると、マウントしたボリュームにもそれが反映されてしまうので、根元の/glusterを閉じておく。

<all nodes>
# mkdir /gluster/gv1/brick
# chmod go-rwx /gluster

いよいよgluster volumeを作成する。

<one node>
# 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置き場に使う、という意味。
次にボリューム開始を行う。これをしないと使えない。

<one node>
# 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に登録する。登録方法は例えば、

/etc/fstab
:
watt1,watt2,watt-storage:/gv1  /gv1  glusterfs  defaults  0  0

のようにすれば、どれかを経由してつないでくれるが、実は

/etc/fstab
:
watt1:/gv1  /gv1  glusterfs  defaults  0  0

と一つだけ書いても、きちんと他のノードも認識してくれ、マウント後はwatt1が一時的に落ちても問題なく利用を続ける事ができる。
ただし、この方法ではnamedが立ち上がってないとマウントに失敗するという問題がある。
さらに、watt類は全部対等でどれがプライマリとかそういうのは無いにも関わらずdfをとるとwatt1しか出てこないのはなんだかかっこ悪い。そのために、/etc/hostsに以下のような記述を追加するとよいだろう。

/etc/hosts
:
X.Y.Z.207 watt-collective

IP Addressとしては、マウント時にそのアドレスのサーバが立ち上がっていないと失敗するので、今回のようにサーバ・クライアントが同じマシンらの場合は、それぞれのマシンで自分自身のアドレスにしておけば無難ではないかと思われる。つまり上記はwatt-storageの例。
そして、/etc/fstabで

/etc/fstab
:
watt-collective:/gv1 /gv1 glusterfs defaults 0 0

としておき、マウントする。

<all nodes>
# mkdir /gv1
# mount -a
# df
Filesystem           1K-blocks    Used Available Use% Mounted on
:
watt-collective:/gv1 314572672 4024192 310548480   2% /gv1

かっこいい。
以上で基本設定は終了。リブートして問題がないか確認

スナップショット

スナップショットをとってみる。

<one node>
# 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

勿論書き込みはできない。
スナップショットの消去は以下でできる。

<one node>
# 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としてみる。

<one node>
gluster snapshot config gv1 snap-max-hard-limit 90
gluster snapshot config auto-delete enable

auto-deleteはグローバルなので、ボリューム名なしで指定する。これでおよそ90の90%=81個のスナップショットで古いのから消去される。

あとはどれか一つのノードのcronにスナップショットコマンドを仕込めば良い。以下はその例。

crontab
SHELL=/bin/sh
0 1 * * * gluster-snapshot.sh >> gluster-snapshot-`date +\%u`.log 2>&1
gluster-snapshot.sh
#! /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ファイルシステムそのものになるようにするとどうなるかやってみると、

<all nodes>
zfs create zpool1/testvol
mkdir /gluster/testvol
zfs set mountpoint=/gluster/testvol zpool1/testvol
<one node>
# 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