優雅な生活の設計と実装

The Design and Implementation of the Gracious Days


LAST MODIFIED: 2011/07/24 07:02:00 UTC

新しい秩序の確立は、他の何にも増して難しく、
成功する可能性が低く、危険な事業である。
改革者は旧秩序から利益を得ている
全ての者を敵にまわし、
新秩序から利益を受けるはずの者からは
及び腰の支持しか集められない。
--- Niccolo Machiavelli, The Prince

この種の「保護」は初心者を保護するかも知れないが、
熟練ユーザを窮地に追い込むことになる。
というのは、何が親切であり、何が適切でないかかという
オペレーティングシステムの考え方の裏をかくことばかりに
かなりの労力を費やさなければならないからである。
--- A.S.Tannenbaum, Modern Operating Systems


不定期更新の日記です。ディスクスペースの関係上、 あまりに古くなったものは順次消していきます。 この日記の更新は、今野さんの *BSD Diary Links から取得することが可能です。

July 2011

感想はこちらまで (内容は匿名のメールで送られます)

コメント:

注: お返事が必要な場合は直接メールください。 ただし、確実にお返事するかどうかはわかりませんのであしからず。

Saturday, July 23

* ZFS panic

allbsd.org のファイルサーバが 22 日から不調に。 NFS export している ZFS 領域へのアクセスで panic が発生していることを確認。

システムを復旧させようと、とりあえず kernel dump をとって再起動をかけたところ、 ZFS のカーネルモジュールを読んでから、zpool を認識するところで同じ panic が繰り返される状態に。 panic 内容は、次のとおり。 システムは FreeBSD/amd64 の 8-STABLE。

panic: Solaris(panic): zfs: allocating allocated segment(offset=%llu size=%llu)
cpuid = 1
KDB: stack backtrace:
db_trace_self_wrapper() at db_trace_self_wrapper+0x2a
kdb_backtrace() at kdb_backtrace+0x37
panic() at panic+0x187
vcmn_err() at vcmn_err+0xbe
zfs_panic_recover() at zfs_panic_recover+0x7a
space_map_add() at space_map_add+0xe4
space_map_load() at space_map_load+0x198
metaslab_activate() at metaslab_activate+0x11a
metaslab_alloc() at metaslab_alloc+0x6e6
zio_dva_allocate() at zio_dva_allocate+0x70
zio_execute() at zio_execute+0xc3
taskqueue_run_locked() at taskqueue_run_locked+0x85
taskqueue_thread_loop() at taskqueue_thread_loop+0x4e
fork_exit() at fork_exit+0x11f
fork_trampoline() at fork_trampoline+0xe
--- trap 0, rip = 0, rsp = 0xffffff8256feed00, rbp = 0 ---

どうもデータが損傷しているもよう。

zpool が認識できないとなると、5TB 近いミラーデータが全部ふっとんでしまうことになる。 正攻法ではどうにもならなそうなので、方針を変更。 ZFS はデータ完全性のチェックが厳しいので、 多少データが壊れてても、あとで検出くらいはできるだろう、 という発想で、無理矢理にでも認識させることを目標にする。

ファイルサーバのシステムディスクは UFS なので、 シングルユーザモードで起動。panic を発生させているコードを読むと、 vfs.zfs.recover=1 にすれば、データの不整合による panic を発生させないことが可能なようなので、とりあえずそれを定義してみる。

そうすると、別の panic が発生した。

panic: solaris assert:
sm->sm_space == space (0xd01a60400 == 0xd01a61c00),
file: /usr/src/sys/modules/zfs/../../cddl/contrib/opensolaris/uts/common/fs/zfs/space_map.c, line: 334

VERIFY3U という、データ構造に対するアサーションにひっかかったらしい。 これは loader tunable では無視できないので、 コメントアウトしてカーネルを再構築。

再起動させたところ、無事に zpool が認識された。 念のため、scrub を仕掛けておく。メーリングリストを検索してみると、 おなじ panic ではまっている人は他にもいるようだ。

OpenSolaris のコードでは、panic recovery の他に、 assertion を無視する aok という変数があり、 これを設定することで、緊急避難的な操作ができるようになっている。 この類の障害(zpool の認識時に panic してどうにもならん)が発生した時には、 panic_recover と aok の両方を 1 にするというのが最終手段。 FreeBSD のコードには後者がないので、そこでコケると、結構厳しい。

とりあえずシステムは復旧したようなので、様子見。 Solaris の knob があるなら、FreeBSD で対応しない理由もなかろう、ということで、 vfs.zfs.aok という名前で操作できるコードを FreeBSD に入れてみた。 あとでパッチを送っておこう。

* ZFS panic #2

panic その 2。

Fatal trap 12: page fault while in kernel mode
cpuid = 1; apic id = 01
fault virtual address   = 0x1698f010
fault code              = supervisor read data, page not present
instruction pointer     = 0x20:0xffffffff8103356e
stack pointer           = 0x28:0xffffff825783af30
frame pointer           = 0x28:0xffffff825783aff0
code segment            = base 0x0, limit 0xfffff, type 0x1b
                        = DPL 0, pres 1, long 1, def32 0, gran 1
processor eflags        = interrupt enabled, resume, IOPL = 0
current process         = 45077 (nfsd: service)
[thread pid 45077 tid 100522 ]
Stopped at      dbuf_read+0xee: cmpq    $0,0x50(%rax)

デバッガで追っかけてみると、コード的にはこの部分。

(kgdb) f 10
#10 0xffffffff8103356e in dbuf_read (db=0xffffff00398aa380, 
    zio=0xffffff002f4376e0, flags=10)
    at /usr/src/sys/modules/zfs/../../cddl/contrib/opensolaris/uts/common/fs/zfs/dbuf.c:548
548             if (db->db_blkptr == NULL || BP_IS_HOLE(db->db_blkptr) ||

この panic は、上述の panic とは別に、 ある時期から頻発して発生するようになった。 どうも発生タイミングが掴めなかったのだけど、 nfsd(8) を起動した瞬間に発生するので、かなり困った事例。

複数回の panic ログをみながら backtrace を比較してみると、 どうも削除操作に起因しているらしいことに気がついた。

#21 0xffffffff8082cc80 in nfsrv_remove (nfsd=0xffffff825783b9c0, slp=0x0, 
    mrq=0xffffff825783b9b0) at /usr/src/sys/nfsserver/nfs_serv.c:1819

大規模な削除を行なっている NFS クライアントのプロセスを止めてみると、 たしかに panic が発生しない。そのプロセスが削除していたファイル群を、 NFS サーバ上で別のディレクトリに移動させて、1個1個削除していくと、 あるファイルを削除しようとした瞬間、同じ panic が発生。

つまり、ファイルシステム上のデータがおかしくなっているということ。 他のプロセスが触らないように移動させると、panic 連発状態は回避できた。 しかし依然として、削除しようとすると panic が発生するのは変わらない。 scrub が検出してくれることを祈りつつ、結果待ち。

Thursday, July 21

* Long Time

少しは文章を書かないといかんだろう、 ということで久々に更新。

* FreeBSD 9.0R Release Process

FreeBSD 9.0R に向けたリリース作業がそろそろ本格的に動きはじめたので、 いろいろと作業中。現在は -BETA1 をつくる、という段階。 スケジュールはこのへんを参照のこと。

* FreeBSD 9.0 解説シリーズ その 1: NFS Improved

FreeBSD の日本語情報が最近少なくなったよね、 という話を良く聞くようになって久しいので、 少しは宣伝しなきゃという気持ちで 9.0 の話を書くことにしました。 第一弾は NFS です。

NFS (Network File System) とは

UNIX 系 OS の使用経験者ならおなじみの、 今はなき Sun Microsystems が 1984 年に開発した、 ネットワーク透過なファイルシステムを実現するプロトコル[1]です。 Sun は、NFS を実現するために VFS と Sun RPC を開発し、 1985 年に SunOS 2.0 に導入しました。どちらの実装も現在に至るまで大活躍しており、 VFS は BSD 系、System V 系の両方に取り込まれ、Sun RPC は ONC RPC (RFC 1831, 5531) としてオープンな技術になっています。

単純で明示的な TCP/IP 通信と違って RPC を使うことから、 管理者からは「分かりにくい面倒なもの」という印象を持たれていますが、 それは設計の主眼だった「ユーザからは、 とにかくローカルでの操作と区別がつかないように見えること」の裏返しでもあります。

NFS マウントしたディレクトリへの操作と、ローカルでの操作との間は、 ユーザランドプログラムから見て、ほとんど違いがありません。 今使っているプログラムを変更することなく、それを ネットワーク経由でリモートマシンにあるファイルに対しても適用できる、 というのは、今でこそいろいろな方法で実現されていますが、当時は非常に挑戦的なことだったわけです。

実装としては v2, v3, v4 のバージョンがあります。 違いはたくさんありますが、ざっくり書くと次のとおりです。

v2 v3 v4
トランスポート UDP TCP or UDP TCP
一回の通信における 最大転送データ量制限 8192 バイト なし なし
ファイル名の長さ制限 255バイト なし なし
パス名の長さ制限 1024バイト なし なし
ファイルサイズ制限 32-bit (2GB) 64-bit 64-bit
非同期書き込み なし あり あり
ファイルロック なし(別プロトコルであり) なし(別プロトコルであり) 標準装備
ステート保持 なし(別プロトコルであり) なし(別プロトコルであり) あり
標準化 RFC 1094 RFC 1813 RFC 3010, 3530, 5661

Linux 等の PC-UNIX でふつう使われているのは v3 です。 v2 と v3 は設定方法にほとんど違いがないので、 気づかずに v2 を使っているというケースもあるかも知れません。 2GB 以上のファイルを扱ったりすると、はまります。

基本的に、 実現できること(=リモートマシンのファイルシステムを、 ローカルのファイルシステムのように操作すること)は同じです。

9.0 で変わったところ

FreeBSD の NFS 実装は、Sun が公開した NFS コードが 4.3BSD Reno に取り込まれた後、 BSD で発展してきたコードの流れを継承したものです。 NFSv2 と v3 に対応しており、 性能を向上させるための独自の改良がいくつか追加されています[2]。 しかし、長い間 NFS のコードは進化することなく、 メンテナンス担当不在のまま、バグ修正だけが細々と続けられる状況が続いていました。

一昨年、4.4BSD の NFS 実装の作業をしていた Rick Macklem 氏が FreeBSD committer として参加することが決まりました。 そして彼の作業をベースに、NFS 実装の大幅な刷新に入ります。 もちろん、もともと彼が 4.4BSD の時代に書いたコードが FreeBSD に入っていたわけですが、 それはもう古いものなので、大きく書き換えたものを作成しました。 9.0 に入っている NFS 実装は、古いものと、書き換えたものの両方が入っており、 新しいものがデフォルトになっています。

新しい実装は、v2/v3 は細かな不具合を修正した程度で、操作上の大きな違いはありません。 違いが大きいのは、v4 の実装が入ったことです。FreeBSD 6.X や 7.X にも NFSv4 の実装(ミシガン大学に由来するもの)が入ってはいたのですが、 満足に動作する状態に維持されておらず、事実上使えないままでした。 NFS の専門家が開発者として参画したことで、ようやく FreeBSD も、まともな NFSv4 を獲得することができた、というわけです。

FreeBSD 9.0 以降、古い実装は mount_oldnfs コマンドのように、oldnfs と呼ばれます。FreeBSD 8.X にも、実は新しい実装がすでに入っていて、mount_newnfs のように、newnfs と呼ばれています。ちょっと混乱するかも知れませんが、 8.X では nfs + newfs という組み合わせ、9.X 以降では oldnfs + nfs という組み合わせというように、名前だけが変わったのです。どちらも、「nfs」と呼んでいるのがデフォルトですが、中身が入れ替わりました。8.2-RELEASE にも newnfs という名前で入っていますので、新機能を試したい場合は、newnfs を使いましょう。

NFS の基礎と設定のおさらい

NFS の構造は複雑なので、使っているよ、という人でも 意味をちゃんと理解していないことが多かったりします。 技術的な部分を理解すれば、「どうしてこうなっているのか」が分かりやすいのですが、 それは大変なので、プロトコルの技術的な側面を最小限にしつつ、 管理者が知っておくべき知識を再整理してみましょう。

/a というディレクトリを 192.168.0.0/24 のネットワークに公開する NFSv3 サーバ (IP アドレスが 192.168.0.1) をつくりたい場合、 設定ファイルは次のようになります。

まずは /etc/rc.conf です。

nfs_server_enable="YES"
rpcbind_enable="YES"
nfsd_enable="YES"
mountd_enable="YES"
rpc_lockd_enable="YES"
rpc_statd_enable="YES"

/etc/exports は次のとおりです。

/a    -network 192.168.0.0 -mask 255.255.255.0

手動で起動させたい場合は、 rpcbind, statd, lockd, nfsd, mountd の順番で起動すると良いです。 rc.d スクリプトを直接実行してしまってください。

# /etc/rc.d/rpcbind start
# /etc/rc.d/statd start
# /etc/rc.d/lockd start
# /etc/rc.d/nfsd start
# /etc/rc.d/mountd start

クライアントでは、/etc/fstab に次のようなエントリを書きます。

192.168.0.1:/a    /mnt nfs rw 0 0

これで終了です。

NFS v2/v3 の落し穴

NFS v2/v3 を使うときにハマりがちな落し穴を知っておきましょう。

NFSv4 を使ってみよう

さて、NFSv3 の設定は目新しいものではないので、 9.0 からデフォルトになった新しい NFS 実装で使える NFSv4 の設定方法をみてみましょう。 8.2R を使っている人も、デフォルトになっていないだけで、実は使えます。 次のカーネルモジュールを読み込ませてください。

# kldload nfscl
# kldload nfsd

NFSv4 の設定に入る前に、v4 の予備知識が必要です。

まず、v4 で大きく違うのは、MOUNT プロトコルです。 v2 や v3 では「最初のファイルハンドル」をサーバから得るために使われるもので、 /etc/exports に並べた公開マウントポイントを mountd が読みとって、 クライアントからの要求に応じてファイルハンドルを渡す、というものでした。

NFSv4 では、この公開マウントポイントが、サーバ 1 つに対して 1 個しか定義できません。 つまり、NFS サーバで公開したいディレクトリが複数あった場合、 それはすべてあるディレクトリの下に存在していなければならない、ということです。 NFSv4 では、サーバは仮想的なディレクトリツリーを 1 個持っていることを仮定しています。

次に、もともと NFS コアプロトコルとは別個のものだった NLM と NSM が、必須プロトコルになりました。サーバの状態は、/var/db/nfs-stablerestart に保存されます。rpc.lockd と rpc.statd は、必要ありません。

コアプロトコルにおける、UID/GID の取り扱いが変わりました。 v2, v3 では、クライアントが送った UID/GID を、そのままサーバ上の UID/GID に対応づける単純なものでしたが、NFSv4 ではユーザ名とグループ名を「user@domain」という文字列で扱います。 こうすることで、UID/GID がサーバとクライアントで共通になっていなくても、 名前が合っていれば同じユーザからのアクセスと認識されるわけです。

このユーザ情報と UID/GID との対応は、nfsuserd(8) というデーモンが担当します。 したがって、サーバとクライアントの両方で、このデーモンが動いている必要があります。 これがないと、ファイルの所有者等の情報が正しく扱えません。

対応関係は、前述したとおり user@domain という形です。user は、ログインユーザ名が使われます。domain は、デフォルトでは FQDN のドメインパートです。ただし、このドメインは DNS 名とは直接関係があるものではありませんので、サーバとクライアントで共通になっていれば、それで OK です。

ここまでをまとめましょう。必要なデーモンは次のとおりです。

v2, v3 と共存するには、rpc.lockd や rpc.statd を起動する必要がありますが、 v4 のみであれば、起動しなくて問題ありません。

/etc/exports の書き方は、次のようになります。192.168.0.2 は、NFS クライアントマシンの IP アドレスです。

V4: /   -network 192.168.0.0 -mask 255.255.255.0
/a  -maproot=0:0 192.168.0.2

V4: から始まる行が、NFSv4 で使われる公開マウントポイントです。 前述したとおり、NFSv4 では公開マウントポイントが 1 個しか持てず、 公開するディレクトリツリーは、すべてそのポイントの下位になければならない、という制約があります。

「/ って書いたら、ファイルシステム全体が公開されてしまうのでは」と心配になる書き方ですが、「マウントできること」と「アクセスできること」は、全く違う概念であることを思い出してください。繰り返しになりますが、/etc/exports と mountd(8) は、MOUNT プロトコルを担当するものです。MOUNT プロトコルは、クライアントがサーバにアクセスするための、「最初のファイルハンドル」を得るために使われるものでした。NFSv4 では、この最初のファイルハンドルに対応するものが 1 個になっています。

クライアントは、V4: の行で指定されたポイントより下位のディレクトリを、最初のファイルハンドルを使うことで知ることが可能です。しかし、マウントできる、あるいはそこにアクセスできるかどうかは、「最初のファイルハンドルを使って、アクセスしたいファイルハンドルが得られるかどうか」に依存します。/etc/exports の V4: 行は、あくまで最初のファイルハンドルへのアクセスを許可するだけで、それより下位のディレクトリのファイルハンドルへのアクセスは、無条件に許可しません。v2 や v3 の時と同様、公開マウントポイントは、別個の行として定義する必要があり、その定義の有無でアクセス許可が決まります。

つまり、NFSv4 を使いたければ、V4: 行を追加することが必要ですが、あとの個別のマウントポイントの設定は同一なのです。

V4: 行のアクセス制限は、NFS クライアント全体をカバーするように工夫する必要があります。個別の公開マウントポイント指定行でアクセス制限ができるので、極端に神経質になる必要はありませんが、ノーガードはやめましょう。

設定したら、マウントしてみましょう。サーバでの操作手順をまとめると、次のようになります。

まず、/etc/exports は次のとおりです。

V4: / -network 192.168.0.0 -mask 255.255.255.0
/a  -maproot=0:0 192.168.0.2

/etc/rc.conf は次のようになります。

nfs_server_enable="YES"
nfsv4_server_enable="YES"
rpcbind_enable="YES"
nfsd_enable="YES"
nfsuserd_enable="YES"
nfsuserd_flags="-domain example.com"
mountd_enable="YES"

nfsuserd には、ドメイン名を指定するためのオプション -domain があります。ホスト名を変更したりすることを考えると、付けておいたほうが無難でしょう。

有効化するためのコマンドは、たとえば次のようになります。

# /etc/rc.d/rpcbind start
# /etc/rc.d/nfsd start
# /etc/rc.d/nfsuserd start
# /etc/rc.d/mountd start
# showmount -e
Exports list on localhost:
/a                           192.168.0.0

最後の showmount -e は、mountd(8) がちゃんと /etc/exports を読み込んだかどうかのチェックです。

クライアントでは、次のようにします。

# mount -o nfsv4 192.168.0.1:/a /mnt

ここでは、サーバの IP アドレスが 192.168.0.1, ローカルのマウントポイントに /mnt を選択しています。-o nfsv4 は必須です。デフォルトでは、最初に NFSv3 を使ってマウントしようとしてしまうからです。

NFSv4 設定の細かな話

V4: に設定するディレクトリ階層は、「/」を使いましょう。「/」以外でも、もちろん使えるのですが、その場合は NFSv4 の基点が指定したディレクトリになってしまいます。クライアントからマウントする時は、基点からのパス名を指定するので、

V4: /a    -network 192.168.0.0 -mask 255.255.255.0
/a/b    -maproot=0:0 192.168.0.2

という設定をすると、マウントする側は

# mount -o nfsv4 192.168.0.1:/b

のように、指定する必要が生じます。v2, v3 では /a/b になるので、 共存させようとした場合は、管理者が混乱することでしょう。 「/」以外を選択することはセキュリティ的な利点が予想されますが、 実際のところ、そうすることで得られる効果は小さいです。

また、nfsd(8) が提供するサービスのバージョンを制限したい、 と思うケースがあると思います。たとえば、「NFSv2 は古いプロトコルなので、 クライアントが間違って使わないようにサーバ側で受け付けないようにしたい」というのが典型例でしょう。その場合は、サーバ側で sysctl 変数を設定します。

# sysctl vfs.nfsd.server_min_nfsvers=3

FreeBSD 8.X では、vfs.newnfs.server_min_nfsvers という名前になっています。受け付ける NFS プロトコルのバージョンの下限を指定するものです。デフォルトは 2 (NFSv2 という意味) になっています。これを 3 にすれば、v2 でのアクセスはできません。4 にすれば、NFSv4 専用サーバになります。max_nfsvers というのもあり、そちらは上限を設定します。デフォルトは 4 です。

NFS v2/v3/v4 を共存させたい場合には、次のことに注意してください。

雑多なこと

NFSv4 には delegation という、性能を向上させるためのクライアントサイドキャッシュ機能が追加されています。FreeBSD の実装では nfscbd(8) が担当するものです。クライアントで起動させると一部の NFS 操作がクライアント側だけで完結するようになり、理論上は性能が向上します。ただ、現在の 9.0 では実装が一部しか行なわれていないため、効果が限定的です。クライアントで起動しておいたほうが良いものだ、と覚えておくと良いでしょう。

また、NFSv4 の実装では、デバイスをまたいだ export が可能なものがありますが、FreeBSD の実装はできません。このあたりの制限は NFSv2, v3 と同様です。

NFS のセキュリティは、RPC のセキュリティに頼っています。GSSAPI を使って認証や暗号化を行なうことができ、FreeBSD の実装も対応しています。基本的に Kerberos サーバが必要になり、ここで簡単い詳細を説明することは難しいですが、exports(5) マニュアルページに -sec= というオプションが載っています。

Kerberos も、国内ではあまり情報がなくて困る、という類のサービスです。昔 UNIX USER にまとめ記事を書きましたが、機会があれば、またまとめてみようかと考えています。

感想はこちらまで (内容は匿名のメールで送られます)

コメント:

注: お返事が必要な場合は直接メールください。 ただし、確実にお返事するかどうかはわかりませんのであしからず。