TT Lab
はじめる
学ぶ 学習パス コース

ストレージとマウント

NFSはなぜローカルディスクのように振る舞わないのか

TT Labで続きを見る

一言でいうと

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のエントリが、なぜそのようなオプションの組み合わせだったのか、もう説明できるはずです。