= NFSv4サーバーの作り方 = <> == 検証環境 == * FreeBSD/amd64 10.3-RELEASE をNFSv4【サーバー】とする。 * CentOS をサーバーにするケースは今回は無しで。 * 使用するプロトコルはNFSv4に絞る(重要なので2回ry)。 * [[NFSv4/クライアント|NFSv4クライアント]]については別に行う設定を前提とする。 * NFSv4 on ZFS 構成とし、/etc/exports ではなく sharenfs プロパティによりNFS共有設定を制御する。 * また1つのディレクトリ(≒Zプール)以下に集約する(NFSv4の名前空間的要求による)。 * もちろんバックエンドストレージをZFSにしようがUFSにしようが、違いは無い。UFSの場合、sharenfs でなく、/etc/exports で設定を行うくらいの違いで。 * 今回ディレクトリ作成(mkdir)=ファイルシステム作成(zfs create)としているが、sharenfs プロパティの設定をそのまま /etc/exports に転載したものと思えばよい。 * また、ZFSでもUFSでもそうだが、階層外にあるものをNFS共有したいなら、nullfs で引っ張れば良い。 * バージョンの選択についてだが、過去との互換性?今時 v2 はおろか、v3 でもないでしょ、ということで、v4 ないしは v4.1(pNFS はまだ使えない orz)一択で。 == NFSセキュリティメモ == <> = セットアップ = == ZFS領域確保(作業例) == {{{ sysctl vfs.zfs.min_auto_ashift=12 zpool create -O atime=off export /dev/da1 }}} * 今回、/export 上にNFS共有するファイルシステムを専用に用意するものとする(必須ではない)。 * またマウント可否は sharenfs プロパティで制御するものとする(デフォルトは off)。 * sharenfs プロパティを設定すると、/etc/zfs/exports に展開された上、mountd(8)にHUPシグナルを送られる。 == /boot/loader.conf(環境依存) == {{{ vfs.zfs.trim.enabled="0" }}} ※検証環境に使用した VMware ESXi 6.0U2 上(5.5/5.5U1/5.5U2/6.0/6.0U1 でも確認、いずれも LSI Logic SAS アダプタによる)では TRIM/UNMAP がエラーになってログがウザいので ZFS TRIM 機能をオフにする。 物理環境下なら今時のHDDであれば TRIM/UNMAP 試してエラーになることは無いと思われるので、CAMのエラーメッセージがうるさい時だけ設定する。 どういうエラーメッセージが表示されるかというと、下記のようなカーネルメッセージ(SCSI/SAS なので TRIM というよりは UNMAP が実行された)で3回リトライするので3セット出力される。 {{{ (da1:mpt1:0:0:0): UNMAP. CDB: 42 00 00 00 00 00 00 00 18 00 (da1:mpt1:0:0:0): CAM status: SCSI Status Error (da1:mpt1:0:0:0): SCSI status: Check Condition (da1:mpt1:0:0:0): SCSI sense: Vendor Specific asc:0,0 (No additional sense information) (da1:mpt1:0:0:0): Error 5, Unretryable error }}} このあたり、dmesg(5) コマンドの結果や /var/log/messages ファイルの中を確認のこと。 == /etc/rc.conf(NICチューニング) == {{{ ifconfig_vmxn="inet XXX.XXX.XXX.XXX netmask XXX.XXX.XXX.XXX mtu 9000" }}} 今時のファイル共有ならGbEや10GbE当たり前なので、MTU値を設定する。 MTU値はネットワーク依存で、通信に絡んでいる一番小さな値を指定することになる。必ずしも9000(バイト)とは限らないので、ちゃんと調べよう(&設定しよう)。 なお手動で設定できるが、その場合、MTU値を反映するために一度、下記のようにインターフェースをダウンさせる必要がある。 {{{ ifconfig vmxn mtu 9000 ifconfig vmxn down ifconfig vmxn up }}} リンクダウン時に、そのインターフェースが持っていた経路情報を失うので(設定してなければ問題無い)、再起動(shutdown -r now)してしまうのが吉。 再起動後、インターフェースの設定を確認する。 {{{ # ifconfig vmxn vmxn: flags=8843 metric 0 mtu 9000 options=60039b ether 00:0c:29:XX:XX:XX inet XXX.XXX.XXX.XXX netmask 0xXXXXXXXX broadcast XXX.XXX.XXX.XXX nd6 options=29 media: Ethernet autoselect status: active }}} == /etc/sysctl.conf == === NFSの最低利用バージョンの指定 === {{{ vfs.nfsd.server_min_nfsvers=4 }}} なお最大利用バージョンの指定は rc.conf 中の「nfsv4_server_enable="YES"」設定で行われている。 === ZFSのチューニング === {{{ vfs.zfs.min_auto_ashift=12 vfs.zfs.vdev.async_write_max_active=31 }}} 特に async_write_max_active はドライブが対応しているNCQ・TCQの数(-1)を指定する(台数があれば台数分を乗算する)。 === NFSのチューニング(未検証) === {{{ vfs.nfsd.issue_delegations=0 vfs.nfsd.enable_locallocks=0 }}} ==== vfs.nfsd.issue_delegations ==== * NFSサーバーでデリゲーション機能の提供をする(1)かしない(0)かを決める。 * この「デリゲーション」(委任)機能は、クライアントの操作を一部サーバー側で代替するというもの。 * どの程度委任できるかは不明だけど、NFSクライアントも対応している必要がある。 * しかし、調べた範囲だと、NFSクライアントでの使用が「明言されていない」というか確認できなかったので、とりあえずオフっておく。 ==== vfs.nfsd.enable_locallocks ==== * ロックについて、NFSサーバーとNFSクライアント間でのロック共有ができるようになる。 * しかし、以下の組み合わせで、できたりできなかったりする。 * NFSクライアント(CentOS 6.8)→NFSサーバー(FreeBSD 10.3-R)の順でロックすると問題無くロックできる。 * NFSサーバー(FreeBSD 10.3-R)→NFSクライアント(CentOS 6.8)の順でロックするとNFSクライアント側でロックされずにエラーになる。 * この問題について調査が足りてないので、とりあえずオフっておく。 * もちろんこの設定があろうが、なかろうが、NFSv4の売り文句であるところの、NFSクライアント(内の複数のプロセス)でのロック取得について問題無し。 * また、この話とは別に、NFSクライアント間のロック共有が可能かは未検証。 == /etc/exports == {{{ V4: /export -network ネットワークアドレス -mask ネットマスク }}} とりあえず、/export がNFSv4共有できるようになる。ただし、/export 自体は sharenfs=off なので、どこからからもマウントできない(厳密にはできるけどアクセスできない)。 == /etc/rc.conf == * NFSv4サーバーとして運用するために必要なデーモンは以下の通り。 {{{ rpcbind_enable="YES" nfsuserd_enable="YES" mountd_enable="YES" nfs_server_enable="YES" nfsv4_server_enable="YES" }}} * また、以下の設定によりある程度のアクセス制限を行う。 {{{ rpcbind_flags="-h 公開するNFSサーバーのIPアドレス" nfsuserd_flags="-domain アカウント識別用ドメイン" mountd_flags="-l -h 公開するNFSサーバーのIPアドレス" nfs_server_flags="-t -h 公開するNFSサーバーのIPアドレス" }}} このうち、nfsuserd_flags で指定する「ドメイン」の指定が重要となる。 NFSv4では、NFSサーバーとNFSクライアント間のユーザー識別子を「ユーザー@ドメイン」で識別するため、合わせておく必要がある。 これはNFS共有するようなサーバー群では、同じドメインである場合、本設定を省略しても良い。 しかし、ホスティング環境でのバックアップサーバー提供のような、NFSクライアントのサーバーのドメインが統一できないような場合、サービス提供者側として、別途決め打ちする必要がある。 = アカウント作成 = * 先にも述べたがNFSv4におけるNFSサーバーとNFSクライアント間のアカウント識別は「ユーザー@ドメイン」である。 * これはNFSv3/v2までと違い、UID、GIDという数字では識別していないことを意味する(問題については後述)。 * 「ドメイン」については先に決めたとおりとする。 * よく使いそうな「ユーザー」については事前に作成しておくこと。 * NFSサーバーとNFSクライアント間でアカウントミスマッチが起きると、以下の現象が発生する。 * NFSクライアント(CentOS 6.8): nobody ユーザー・nobody グループにマップされる。 * NFSサーバー(FreeBSD 10.3-R):32767 ユーザー・32767 グループにマップされる。 == アカウント作成例 == {{{ pw groupadd postgres -g 5432 pw useradd postgres -u 5432 -g postgres -c "postgres" -s /usr/sbin/nologin install -d -o postgres -g postgres -m 0755 /home/postgres }}} ※FreeBSD の PostgreSQL 用アカウントとして pgsql (/usr/ports/UIDs ファイル参照のこと)があるが、CentOS の PostgreSQL 用アカウント(postgres)とはマッチしないので、別途作成する。 == CentOS 6.x との相互運用問題 == <> = 領域の共有設定 = == 共有設定 == /export をどう共有するかによるが「/export/NFSv4クライアント名」として、クライアント単位で共有設定する場合は、下記のようにパーティションを切る。 {{{ #!sh zfs create -o sharenfs="-maproot=root NFSクライアントIP" export/NFSクライアント名 }}} sharenfs 設定を行うと、/etc/zfs/exports ファイルに掃き出されるのと、合わせて mountd(8) に再読み込み(HUP シグナル)が実施される。 一つの領域を複数のNFSクライアント間で共有するなら、/etc/exports に集約するのが良いと思われ(未実験)。 なおNFSクライアントからは、「/NFSクライアント名」でマウントすることになる(/export がトップディレクトリとなって見えなくなる)。 更にお好みに応じて、exec=off、setuid=off も追加しよう。これはNFSサーバーとしてはこれら属性を「解釈しない」という意味であって、NFSクライアントはNFSクライアントで、別途設定が必要となる。 == 設定確認 == mountd(8) の再読み込みによりNFSクライアントからマウントできるようになるわけだが、これが成功したのか失敗したのかはよくわからない。 そこで、「showmount -e」コマンドと「zfs get -t filesystem sharenfs」コマンドの実行結果を見比べて判断すること(設定した内容が反映されてないなど)。 {{{ # showmount -e Exports list on localhost: /export/NFSクライアント1 XXX.XXX.XXX.XXX /export/NFSクライアント2 YYY.YYY.YYY.YYY # zfs get -t filesystem sharenfs NAME PROPERTY VALUE SOURCE export sharenfs off default export/NFSクライアント1 sharenfs -maproot=root XXX.XXX.XXX.XXX local export/NFSクライアント2 sharenfs -maproot=root YYY.YYY.YYY.YYY local }}} ※zfs get sharenfs やべぇ。スナップショットもリストアップして大変な量に…。 orz = よくある質問とその答え = == Q.ジャンボパケットで通信してますかー? == A.大丈夫だ。問題無い。(なぜ?) サーバーやクライアントはもちろんのこと、サーバーとクライアントの間を結んでいるネットワーク機器の全てでジャンボパケットに対応している必要がある。 単純にジャンボパケットが通るか(設定が正しいのか)確認するのに ping を使う。 {{{ # ping -D -s8972 NFSv4クライアントのIP PING NFSv4クライアントのIP (NFSv4クライアントのIP): 8972 data bytes 8980 bytes from NFSv4クライアントのIP: icmp_seq=0 ttl=64 time=0.256 ms 8980 bytes from NFSv4クライアントのIP: icmp_seq=1 ttl=64 time=0.289 ms ^C --- NFSv4クライアントのIP ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.256/0.272/0.289/0.017 ms }}} 1バイトでもオーバーすると下記のようなメッセージが出力される。 {{{ # ping -D -s8973 NFSv4クライアントのIP PING NFSv4クライアントのIP (NFSv4クライアントのIP): 8973 data bytes ping: sendto: Message too long ping: sendto: Message too long ^C --- NFSv4クライアントのIP ping statistics --- 2 packets transmitted, 0 packets received, 100.0% packet loss }}} しかし、サーバーサイドは問題無くても、途中経路またはクライアントサイドにMTUミスマッチ(典型的には 1500 のまま)があると無応答となる。 この場合、クライアントサイドに問題が無いとすると、特定は非常に困難になる。 一台一台、地味に特定していく必要がある。 '''ping での通信に問題が無ければ、最低限、ネットワーク周りの設定に問題は無い。''' しかし実際にNFS通信(over TCP)がその状態で通信できているか確認するのは難しい。 特に手動で設定した直後は、設定前のMTU値で通信していることがある。 この場合、tcpdump(8)を使えば観測できなくもないが、その場合、length 8948 というパケットが観測できれば、問題無い。 {{{ # tcpdump -npivmxn host NFSv4クライアントのIP : XX:XX:XX.XXXXXX IP XXX.XXX.XXX.XXX.nfs > YYY.YYY.YYY.YYY.782: Flags [.], seq 54720:63668, ack 3705, win 1588, options [nop,nop,TS val 444521882 ecr 816267359], length 8948 : }}} 下記のように length 1448 のままだとMTU値変更が反映されていないか、クライアント側もMTU値が反映されていない可能性がある。 {{{ # tcpdump -npivmxn host NFSv4クライアントのIP : XX:XX:XX.XXXXXX IP XXX.XXX.XXX.XXX.nfs > XXX.XXX.XXX.XXX.782: Flags [.], seq 333288:334736, ack 621, win 1040, options [nop,nop,TS val 2971896848 ecr 2075608049], length 1448 : }}} そのあたり反映させるための操作が面倒なら、クライアントもサーバーも shutdown -r now してしまうのが吉。 = 参考文献 = * [[https://www.allbsd.org/~hrs/diary/201107.html#d2103|FreeBSD 9.0 解説シリーズ その 1: NFS Improved]] * [[https://man.freebsd.org/query=nfsv4(4)|NFSV4(4)]] * [[https://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/network-nfs.html|Network File System (NFS)]] * [[https://bugzilla.redhat.com/show_bug.cgi?id=829362|Bug 829362 - wrong username/domain passed in nfs4 attribute requests]]