NFSはなぜローカルディスクのように振る舞わないのか
一言でいうと
NFSはファイルシステムのように見えますが、ネットワーク上のRPC呼び出しです。そのため、ローカルディスクにはない障害モード(停止、部分障害、キャッシュの不整合)が生じます。
なぜ必要なのか
ホームラボでも会社でも、複数のサーバーが同じデータを見る必要がある瞬間が来ます。Kubernetesで、複数のノードのPodが同じPVCを使うにはReadWriteManyが必要で、最も手軽な実装がNFSです。ところが、NFSを取り付けたあとに、おかしなことが起きます。lsが30秒ずつ止まり、プロセスがD状態で死なず、あるノードではファイルが見えるのに、別のノードでは見えません。
どう動くのか
サーバー側: exports
# /etc/exports
/export/share 10.0.0.0/24(rw,sync,no_subtree_check,root_squash)
/export/ro 10.0.0.0/24(ro,sync,no_subtree_check)
| オプション | 意味 |
|---|---|
rw / ro |
書き込みを許可 / 読み取り専用 |
sync |
書き込みをディスクに反映したあとに応答。遅いが安全 |
async |
メモリにだけ書いてすぐに応答。サーバーが死ぬとデータを失う |
root_squash |
クライアントのrootをnobodyにマッピング(既定値) |
no_root_squash |
rootをそのまま認める。事実上、サーバーを明け渡すのと同じ |
no_subtree_check |
サブツリー検査を省略。現在の推奨既定 |
root_squashを理解できないために生じる混乱が多いです。クライアントでrootとしてファイルを作ったのに、所有者がnobodyと見えます。これはバグではなく、セキュリティ機能です。NFSはUIDをそのまま信頼するので、squashがないと、クライアントでsudoを1回するだけで、サーバーの任意のファイルに触れます。
そしてUID/GIDは、名前ではなく数字で伝えられます。サーバーのappuserが1001なのに、クライアントの1001がwebuserなら、ファイルの所有権がおかしく見えます。そのためNFSを使う環境は、UIDを一元的に統一するか(LDAP)、NFSv4のidmapdを設定します。
クライアント側: マウントオプション
nfs01:/export/share /mnt/share nfs4 _netdev,rw,soft,timeo=600,retrans=2,noatime 0 0
最も重要な選択は、hardかsoftかです。
| オプション | サーバーが応答しないとき |
|---|---|
hard(既定) |
永遠に再試行します。プロセスがD状態で止まり、killも効きません |
soft |
timeo×retransのあとにI/Oエラーを返します |
intr |
(旧バージョン)hardでもシグナルで中断できます。最新のカーネルでは、既定の動作に吸収されています |
hardはデータの整合性の面で安全です。書き込みが失敗として処理されて、アプリケーションが誤った状態で先に進むことがないからです。その代わり、サーバーが死ぬとクライアントが丸ごと止まります。softは逆です。応答がなければEIOを返すので、システムは生きていますが、アプリケーションがそのエラーをきちんと処理できないと、データが壊れます。
実務の判断は、たいてい次のとおりです。データベースや書き込みが重要なワークロードはhard、読み取り中心だったり、なくてもよいキャッシュ的なデータはsoftです。
キャッシュと一貫性
NFSクライアントは、性能のために属性(attribute)をキャッシュします。既定のacregmin/acregmaxは3–60秒です。そのため、サーバーで変わったファイルが、クライアントにすぐには見えないことがあります。複数のノードが同じファイルを同時に書く設計は、NFSでは危険です。noacでキャッシュを切ると、一貫性は良くなりますが、性能が大きく落ちます。
ファイルロック(flock、fcntl)はNFSv4でサポートされますが、サーバーの再起動やネットワークの断絶時に、ロックの復旧が完全ではありません。NFSの上でロックに依存する設計は避けるほうがよいです。
現場での姿
dfが止まります。NFSサーバー1台が死んだときにhardでマウントされていると、dfですら応答しません。そのときは、df -l(ローカルのみ)やtimeout 5 dfで迂回します。モニタリングスクリプトがdfを使っていて、この状況で一緒に止まる事故もよくあります。
起動が止まります。fstabに_netdevを入れ忘れると、ネットワークの準備ができる前にマウントを試みます。hardと重なると、起動が事実上、無限の待機に入ります。
KubernetesでPodがTerminatingから抜け出せません。NFSボリュームが応答しないと、kubeletがアンマウントできず、Podが永遠に終了しません。このときは、NFSサーバーを復旧させるか、ノードで強制アンマウント(umount -f -l)をする必要があります。
次にすること
このモジュールは概念だけを扱います。ただし、前のfstabのラボで作成したNFSのエントリが、なぜそのようなオプションの組み合わせだったのか、もう説明できるはずです。