こんにちは、masm11 です。
弊社では、macOS 上の Vagrant 環境内から、macOS のディレクトリを NFS で mount して、 開発しています。 仮想マシンとしては VirtualBox を使用しています。 この度、私に貸与された mac の macOS を Catalina に上げた ところ、NFS mount ができない、という症状が発生しました。
今回はこの症状を解決するまでをご紹介します。
なお、macOS は Catalina 10.15.4、Vagrant は 2.2.7 です。
症状
vagrant up
すると、途中まではうまく処理が進むのですが、
NFS mount するところで、以下のように失敗します。
==> default: Exporting NFS shared folders... ==> default: Preparing to edit /etc/exports. Administrator privileges will be required... ==> default: Mounting NFS shared folders... The following SSH command responded with a non-zero exit status. Vagrant assumes that this means the command failed! mount -o vers=3,udp 192.168.33.1:/System/Volumes/Data/Users/masm/proj/ra/share /vagrant Stdout from the command: Stderr from the command: mount.nfs: requested NFS version or transport protocol is not supported
そして、少し経ってから vagrant ssh
でログインし、手動で
sudo mount -o vers=3,udp 192.168.33.1:/System/Volumes/Data/Users/masm/proj/ra/share /vagrant
を実行すると、なんと成功します。
おそらく、どこかで何かがタイムアウトしているのでしょう。
情報を漁ってみる
世の中で既にたくさん使われているはずの Catalina です。そして Vagrant もです。 情報がないはずがありません。
ぐぐってみると、たくさんの情報が見つかりました。一番多かったのがこれです。
/etc/exports
に書く macOS 側 path は/System/Volumes/Data/...
になっていること。しかし、最新の Vagrant なら既にそうなっています。上に書いたエラーメッセージでもそうなっていますね。
また、Vagrant の issue をいくつか見たところ、以下の情報が得られました。
既知のディレクトリ (
~/Documents
みたいな) を export しないこと。既知のディレクトリを export する場合は
/sbin/nfsd
にフルディスクアクセス権限を与える必要があるそうです。私の場合は独自のディレクトリですので、該当しません。そもそも権限が問題なら、時間が経ったら 成功する、などということはないはずです。
macOS 側
/etc/hosts
にlocalhost
をちゃんと書いておくこと。普通は書いてあると思います。私も書いてありました。
ただし、私の場合、
showmount -e localhost
が何も返さず黙ったまま、という症状もあり、 それは::1
もlocalhost
にしてあったのが原因でした。::1
を削除したところ、 この症状はおさまりました。Vagrantfile 内
synced_folder
は1つだけにすること。複数書くと、/etc/exports の1行に複数個のディレクトリが並ぶ場合があって、その場合に問題が起きていたようです。
このコメントに例付きで書いてあります。
最新の Vagrant では大丈夫かもしれませんが、追い切れていません。
私の場合、
synced_folder
は1つだけでした。
以上が、情報を漁った結果です。
さらに症状は続く…
しかし、これらの情報では解決に至りませんでした。
仕方ありません、独自の調査を試みてみます。
NFS をやめる
代わりに VirtualBox の sync folder を使うという手があります。
しかし sync folder は遅いのです。NFS が解決しなければ sync folder を 使うという手も考えましたが、遅くて開発に支障が出そうだったので、解決しない 場合は解決しないまま使うことにしました。問題なのは mount する時だけ ですから。
rpcinfo してみる
NFS で問題が起きたら、とりあえず rpcinfo だと思っています。
rpcinfo とは何でしょうか? 使う前に少し説明してみます。
普通、サービスにはポート番号が割り当てられていますね。smtp であれば 25番、 http であれば 80番です。しかし、ポート番号は 16bit であり、1~65535 しかなく、 しかもその中はざっくりと範囲に用途が決まっています。well-known ポートであったり、 エフェメラルポートであったり、NAT に使うものであったり、よくわからないサービスが 独自に割り当てて使ってしまうものであったりします。つまり、65535個って結構たくさん あるように見えて、割り当てられる番号はその極一部なのです。有限なのです。 使うかどうかも判らないようなサービスにポート番号を固定で割り当てたくはないのです。
すると、当然、「じゃぁ動的に割り当てよっか?」という発想が生まれます。つまり、 もっと広い空間の「プログラム番号」「プログラム名」を用意し、使われていないポート番号 から動的に割り当て、「プログラム番号」→「ポート番号」の表を管理しておくのです。 こういった機構を portmap などと呼んでいます。
rpcinfo -p 192.168.33.1
を実行すると、192.168.33.1 のマシンの中のその表を管理
しているプロセスから、その一覧を取得することができます。
試してみましょう。
yuukinombp:~ % rpcinfo -p 192.168.33.1 | grep nfs 100003 2 udp 2049 nfs 100003 3 udp 2049 nfs 100003 2 tcp 2049 nfs 100003 3 tcp 2049 nfs
100003 はプログラム番号、2 や 3 はプログラムのバージョン、 udp や tcp は使用するプロトコル、 2049 はポート番号、nfs はプログラム名です。つまり、nfs の vers=2 の UDP 版は ポート 2049 を使用する、と読みます。
192.168.33.1 の nfs に UDP でアクセスしたいクライアントは、 一旦 192.168.33.1 の portmap にリクエストを投げ、 nfs の vers=2 の UDP が 2049 であることがわかったら、 改めて 2049/udp に nfs のリクエストを投げる、 ということになります。
ただし、この仕組みは広くいろんなサービスに使われているわけではありません。 結局、NFS や NIS くらいでしか使われなかったように記憶しています。
さて、話を戻します。
NFS mount できる時とできない時で差がありました。できない時には mountd がありません。例をお見せします。
yuukinombp:~ % rpcinfo -p 192.168.33.1 | grep -E 'nfs|mountd' 100003 2 udp 2049 nfs 100003 3 udp 2049 nfs 100003 2 tcp 2049 nfs 100003 3 tcp 2049 nfs yuukinombp:~ % rpcinfo -p 192.168.33.1 | grep -E 'nfs|mountd' 100003 2 udp 2049 nfs 100003 3 udp 2049 nfs 100003 2 tcp 2049 nfs 100003 3 tcp 2049 nfs 100005 1 udp 632 mountd 100005 3 udp 632 mountd 100005 1 tcp 991 mountd 100005 3 tcp 991 mountd yuukinombp:~ %
上側が mount できない時、下側が mount できる時です。
たまたまかもしれません。もう少しタイミングをしっかり確認してみましょう。
macOS 側で以下を実行します。
while :; do rpcinfo -p 192.168.33.1 | grep -E 'nfs|mountd'; sleep 1; done
別の端末で、vagrant ssh
して以下を実行します。
while ! sudo mount -o vers=3,udp 192.168.33.1:/System/Volumes/Data/Users/masm/proj/ra/share /vagrant; do sleep 1; done
ようするに、rpcinfo を1秒おきに実行しながら、mount も1秒おきにやってみる、ということです。
少し時間が経つと、両方同時に変化がありました。 rpcinfo は mountd を出力するようになりましたし、mount も完了しました。 タイミング的には1秒未満の差です。関連してそう、と思って良いでしょう。
私もどのプログラムがどういった役割をしているのか、それほど把握しているわけでは ないのですが、mountd がいなければ mount はできないのでしょう。
しかし、何故そんなことになるのかはさっぱり原因がつかめません。
log を見る
困って更にぐぐっていたある日、syslog を見る方法を見つけました。
その名も log コマンド。
log stream --style=syslog
とすれば、syslog っぽく整形されたログがどんどんリアルタイムで出力されていきます。
これです! これが欲しかったんです! これさえあれば…
nfs に関するメッセージを探してみたところ、ありました!
2020-04-18 01:37:34.795213+0900 localhost nfsd[7277]: couldn't register NFS/TCP service.
これですね。
couldn't register
は、portmap の表に登録できない、という意味なのでしょう、おそらく。
そして、NFS/TCP
と書いてありますね。ならば、TCP を無効にしてみましょう。
/etc/nfs.conf
に以下のように書きます。
nfs.server.tcp = 0
これで vagrant up
しなおします。
==> default: Exporting NFS shared folders... ==> default: Preparing to edit /etc/exports. Administrator privileges will be required... Password: ==> default: Mounting NFS shared folders... ==> default: Mounting shared folders... default: /tmp/vagrant-chef/783542ad0dccc700ff72bcae0833b125/cookbooks => /Users/masm/proj/ra/cookbooks ==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision` ==> default: flag to force provisioning. Provisioners marked to run always will still run.
いけました! めでたく mount できました。
根本原因 (何故 TCP 版が登録できないのか) はわかりませんが、これでひとまず解決です。
考慮点
これでは TCP 版 NFS が使えません。が、Vagrant が UDP を指定しちゃっているので、 TCP を無効にしても影響はないでしょう。
ただ、今後、docker を使うなどで TCP での NFS を使いたくなった場合には、改めて 困るかもしれません。
まとめ
NFS トラブルの解決策が見つからなかったため、自力で解決してしまいました。 ただ、ぐぐって情報を集めても、同様の症状はほとんど見当たりませんし、 たまにあっても解決していませんでした。少数派なのでしょうね。
それにしても、最近、どの OS も syslog をバイナリ化してしまうので、困ります。 テキストファイルなら、テキストファイルを扱えるコマンドは無数にありますから、 好みの方法で使えます。しかしバイナリ化されていると、専用コマンドの使い方を 覚えて、一旦テキスト化してやる必要があります。 テキストでいいじゃん… とは思うのですが、バイナリにもそれなりのメリットが あるのでしょうね。
後日談
と、ここまで書ききった後で、後日談が生まれました。
社内で「ESET をバージョンアップしたら NFS mount できなくなった!」 「NFS mount できない時は ESET ファイアウォールを一旦無効にしてます!」 という声があり、どうやら ESET が影響しているということが判りました。
パーソナルファイアウォールのログを確認したところ、ターゲットのポートが 111
なのは予想通りでしたが、ソースの IP アドレスが ::
となっていました。
IPv6 です。
しかし ::
をルールの IP/IPv6アドレスに入力してもルールが作成できません。
代わりに ::0
と入力すると作成できました。
まとめると、以下のルールを作成することで、NFS mount できるようになりました。
- 名前:
RPC/NFS
等、適当なもの - すべてのアプリケーション: ON
- アクション: 許可
- 方向: 両方
- プロトコル: TCP
- ポート: ローカル
- ローカルポート:
111
- 宛先: IPアドレス
- IP/IPv6 アドレス:
::0
私の ESET は ESET Cyber Security Pro 6.8.300.0 です。
nfs.conf に手を加えるか、パーソナルファイアウォールに設定するか、 どちらかお好みの方法で良いと思います。
ではまた!