クイックスタート(5分)
半年前、私たちの開発チームでは、毎日80GBものDockerイメージやビルド成果物を生成するCIパイプラインのために共有ストレージが必要になりました。CephやGlusterFSの複雑な設定に頭を悩ませる代わりに、私は専用のNFSノードを1台立ち上げました。現在その単一サーバーは、1時間あたり約65,000件のファイルトランザクションをトラブルなく処理しています。ここでは、サーバーとクライアントの環境をわずか5分でクリーンに構築する手順を紹介します。
ステップ1:NFSサーバーのインストールと設定
まず、サーバーデーモンをインストールします。UbuntuまたはDebianの場合:
sudo apt update
sudo apt install -y nfs-kernel-server
AlmaLinux、Rocky Linux、またはRHELの場合:
sudo dnf install -y nfs-utils
sudo systemctl enable --now nfs-server
エクスポート用のディレクトリを作成します。テスト目的として、権限を持たない一般ユーザーnobodyに所有権を付与しておきます:
sudo mkdir -p /srv/nfs/shared
sudo chown -R nobody:nogroup /srv/nfs/shared
sudo chmod 755 /srv/nfs/shared
exports設定ファイルを開きます:
sudo nano /etc/exports
末尾に次のエクスポート定義を追加します。クライアントのサブネット(例: 192.168.1.0/24)は実際の環境に合わせて置き換えてください:
/srv/nfs/shared 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
共有設定をエクスポートし、稼働中のデーモンを再起動して反映させます:
sudo exportfs -rav
sudo systemctl restart nfs-kernel-server
ステップ2:クライアントノードからの接続
ストレージを利用するクライアントマシンに必要なユーティリティをインストールします:
# Debian / Ubuntu
sudo apt install -y nfs-common
# RHEL / AlmaLinux
sudo dnf install -y nfs-utils
サーバー(ここでは192.168.1.50と仮定)に問い合わせて、エクスポートが正しく公開されているか確認します:
showmount -e 192.168.1.50
エクスポートされた共有ディレクトリをローカルディレクトリにマウントします:
sudo mkdir -p /mnt/shared
sudo mount 192.168.1.50:/srv/nfs/shared /mnt/shared
df -h /mnt/sharedを実行して接続を確認します。リモートボリュームがマウントされ、その総容量が表示されていれば成功です。
アーキテクチャとエクスポートディレクティブ
近年のLinuxディストリビューションでは、デフォルトでNFSv4(具体的にはv4.1またはv4.2)が使用されます。rpcbindに依存し、ランダムな高位ポートにトラフィックを分散させていたNFSv3とは異なり、NFSv4はTCPポート2049のみで通信を行います。これによりファイアウォールルールの設定が大幅に簡素化されます。さらに、ステートフルセッション、擬似ファイルシステム、オプションのKerberos認証などの機能も導入されています。
/etc/exportsの主要ディレクティブ
/etc/exportsに指定するフラグは、安全性と速度のトレードオフをどの水準に置くかを制御します:
rw/ro: 読み書き可能(read-write)にするか、読み取り専用(read-only)を強制するかを指定します。sync: データが物理ディスクに書き込まれた後にのみ書き込みリクエストに応答(ACK)を返します。予期せぬ停電時などのデータ損失を防ぐため、本番環境のストレージでは必ず有効にしておくべきです。async: サーバーのRAMバッファにデータが到達した時点で書き込み完了を返します。スループットは劇的に向上しますが、停電が発生すると未書き込みファイルが破損します。no_subtree_check: サブフォルダへのアクセス時に親ディレクトリのチェックを省略します。これを無効(サブツリーチェック有効)のままにしておくと、頻繁なファイル名変更時にタイムアウトが発生しやすくなります。常に無効化(no_subtree_checkを指定)しておくことが推奨されます。root_squash: クライアント側のUID 0(root)をnobodyアカウントにマッピングします。これにより、クライアント側のrootが侵害された場合でも、サーバーのルートファイルシステムが改ざん・削除されるのを防ぎます。no_root_squashは、完全なroot権限を必要とする信頼されたバックアップジョブなどを実行する場合にのみ使用してください。all_squash(anonuid/anongidとの併用): すべてのクライアント接続を指定した単一のユーザーIDに集約します。アプリケーションの共有アップロード領域などに最適な設定パターンです。
スループット向上のためのクライアントマウントオプション調整
軽微なテキスト編集程度であればデフォルトのマウント設定でも十分です。しかし、10GbEネットワーク経由でシーケンシャルバックアップなどを実行すると、デフォルト設定では回線速度を大きく下回るパフォーマンスしか出ません。転送バッファやキャッシングフラグを適切にチューニングすることで、この問題を解消できます。
高パフォーマンス向けマウントコマンド
以下は、実際に本番ノードで使用しているマウントコマンドの例です:
sudo mount -t nfs -o proto=tcp,nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noatime 192.168.1.50:/srv/nfs/shared /mnt/shared
各フラグが重要な理由
rsize=1048576,wsize=1048576: チャンクサイズを1MBに拡張します。古い環境のデフォルトでは32KBや64KBのバッファでネゴシエーションされることが少なくありません。私たちの検証環境では、バッファを64KBから1MBに引き上げたことで、1GbE接続における大容量ファイル転送速度が48MB/sから回線上限に近い112MB/sへと大幅に向上しました。noatime: プロセスがファイルを読み取るたびにアクセス日時(タイムスタンプ)を記録する動作を無効化します。何千ものソースファイルを繰り返し読み込むビルドパイプラインにおいて、ディスクメタデータのIOPSを約30%削減できます。hard/soft: データボリュームでは絶対にsoftを使用しないでください。ネットワークが切断された場合、softマウントはアプリケーションに直接I/Oエラーを返すため、SQLiteデータベースやバイナリファイルが破損する原因になります。一方、hardマウントは接続が復旧するまでプロセスのI/Oを待機させ、データを確実に保護します。timeo=600とretrans=2: 応答のないパケットを再送するまでのリカバリウィンドウを60秒(単位は0.1秒)に設定します。これにより、高負荷なNVMeやSATAアレイでフラッシュ処理がスパイクした際にも、十分な待機時間を確保できます。
/etc/fstabによる永続マウント設定
システムの再起動後も自動で再接続されるよう、/etc/fstabにマウント設定を追記します。ここで_netdevパラメータは必須です。ネットワークインターフェースが完全に起動するまでマウント処理を遅延させるようsystemdに指示します。
192.168.1.50:/srv/nfs/shared /mnt/shared nfs nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noatime,_netdev 0 0
再起動せずにfstabの構文をテストして即座に反映を確認します:
sudo umount /mnt/shared
sudo mount -a
findmnt /mnt/shared
現場で役立つ実践ノウハウとトラブルシューティング
1. ファイアウォールの制限
標準のNFSトラフィックは暗号化されていません。アクセスは必ず信頼できるプライベートIPアドレス範囲に厳密に制限してください。UbuntuでUFWを使用している場合:
sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp
sudo ufw status numbered
RHELやAlmaLinuxでfirewalldを使用している場合:
sudo firewall-cmd --permanent --zone=internal --add-service=nfs
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24
sudo firewall-cmd --reload
2. UIDとGIDの整合性確保
ユーザーID(UID)の不一致は、NFSにおけるパーミッション関連トラブルの9割を占めます。NFSはネットワーク経由でユーザー名ではなく生の数値IDをやり取りします。そのため、クライアント側でユーザーrunnerのUIDが1002であっても、サーバー側ではローカルでUID 1002を持つアカウントの権限が適用されてしまいます。これを解決するには2つの方法があります:
- 両方のシステム間でUIDとGIDを完全に一致させてユーザーを作成する。
/etc/exportsでall_squash,anonuid=1001,anongid=1001を指定し、すべての通信を共有ユーザーに強制的に集約する。
3. Stale File Handle(無効なファイルハンドル)の解除
エクスポート対象のディスクが再起動されたりエクスポートパスが変更されたりすると、クライアント側のコマンドがStale file handleエラーで停止します。通常のエミュントコマンド(umount)も応答しなくなります。このような場合は、Lazyアンマウント(遅延アンマウント)を実行してロックを解除します:
sudo umount -l /mnt/shared
デタッチできたら、sudo mount -aで再マウントします。
4. nfsstatによるボトルネックの特定
遅延の原因がネットワークの再送にあるのか、サーバーディスクの低速化にあるのかをnfsstatを使って調査します:
# クライアント側の統計情報
nfsstat -c
# サーバー側の統計情報
nfsstat -s
クライアントRPCのretransメトリクスを確認してください。データ転送中にこの数値が増加している場合、スイッチやNICでパケットドロップが発生しています。

