Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

CentOS8のベースイメージでNFSサーバを構築しようとして諦めた

はじめに

NFSを利用してファイル共有をしているサーバ環境において、その環境をコンテナ化する際に、コンテナ間でNFSによるファイル共有ができるのかを検証した。 これまでのノウハウが使えるだろうという想定で、CentOS 8のNFSサーバをコンテナ化しようとしたが、安定稼働は無理と思って諦めた。 色々調べたので、試行錯誤の結果を残しておく。

「安定稼働は無理」と言っても、コンテナでNFSサーバを構築するのが無理というわけではなく、CentOS 8のSystemdで制御された世界をコンテナで再現するのが困難という意味。

あと、できるだけスタンダードな方法を選択する方針にしていて、「やろうと思えばできる」という方法は取らない方針とした。

NFSサーバの検証 (1周目)

まずはNFSサーバの検証の1周目。 言い換えると、この検証は失敗したので、2周目があるということ。

コンテナの起動

以下のDockerfileを準備する。 NFSサーバをインストールして、ディレクト/nfs-exportをエクスポートする構成にしている。

FROM centos:8

RUN dnf install -y nfs-utils
RUN systemctl enable nfs-server
RUN mkdir /nfs-export
RUN echo "/nfs-export *(fsid=0,rw,no_root_squash)" > /etc/exports.d/test.exports

ENTRYPOINT ["/sbin/init"]

以下のコマンドでビルドする。

$ docker build --tag nfs-container .

以下のコマンドでコンテナを起動する。 NFS Version 4の場合はTCPの2049番ポートのみを公開しておけばよいはず。 NFS Version 3以前の場合は関連するサービス(RPCとか)のポートも公開する必要があるようだが、ここでは割愛する。 また、Systemdを利用しているので、オプション--privilegedを付加しておく。

$ docker run \
    --name nfs-container \
    --publish 2049:2049 \
    --privileged \
    --detach \
    nfs-container:latest

マウントの試行 (自コンテナから) …… 失敗

まずは問題発生時のネットワーク絡みの要素を少なくしたかったので、自コンテナから、つまりNFSサーバ内でマウントを試してみる。 以下のようにコマンドmountを実行したが、マウントに失敗した。

$ docker exec -it nfs-container mount localhost:/ /mnt
Job for rpc-statd.service failed because a timeout was exceeded.
See "systemctl status rpc-statd.service" and "journalctl -xe" for details.
mount.nfs: mounting localhost:/ failed, reason given by server: No such file or directory

エラーメッセージからは原因がよく分からなかったので、サービスnfs-serverの状態を確認してみる。

$ docker exec -it nfs-container systemctl status nfs-server
● nfs-server.service - NFS server and services
   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
  Drop-In: /run/systemd/generator/nfs-server.service.d
           └─order-with-mounts.conf
   Active: active (exited) since Sat 2021-10-09 17:19:11 UTC; 14s ago
  Process: 170 ExecStart=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
  Process: 169 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
  Process: 168 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=1/FAILURE)
 Main PID: 170 (code=exited, status=0/SUCCESS)
    Tasks: 0 (limit: 101361)
   Memory: 0B
   CGroup: /docker/9ffb21c4437ccdc5a7ce25526d82924483d038a601f17c56872776f8df26fd5e/system.slice/nfs-server.service

Oct 09 17:19:10 9ffb21c4437c systemd[1]: Starting NFS server and services...
Oct 09 17:19:10 9ffb21c4437c exportfs[168]: exportfs: /nfs-export does not support NFS export
Oct 09 17:19:11 9ffb21c4437c systemd[1]: Started NFS server and services.

以下の部分より、/usr/sbin/exportfs -rの実行が失敗していることが分かる。

  Process: 168 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=1/FAILURE)

また以下の部分より、コンテナのファイルシステム(OverlayFS ?)がNFSに対応できないことが分かる。

Oct 09 17:19:10 9ffb21c4437c exportfs[168]: exportfs: /nfs-export does not support NFS export

そこで、バインドマウントでホスト側のファイルシステムをコンテナにアタッチして、それをNFSでexportすると成功するのではと予想。

NFSサーバの検証 (2周目)

コンテナの起動

コンテナの起動前に、コンテナにマウントするホスト側のディレクトリを作成する。 ここでは、ディレクト/var/nfs-exportを作成している。

$ sudo mkdir /var/nfs-export

Dockerfileは1周目の検証から変更はないので、先ほど作成したコンテナイメージnfs-containerを再利用する。

そして、以下のコマンドを実行してコンテナを起動する。 1周目のdocker runとの違いは、オプション--mountを利用して、ホスト側の/var/nfs-exportを、コンテナの/nfs-exportにバインドマウントしている点である。

$ docker run \
    --name nfs-container \
    --publish 2049:2049 \
    --privileged \
    --detach \
    --mount type=bind,source=/var/nfs-export,target=/nfs-export \
    nfs-container:latest

NFSサーバの状態を確認してみると、今回は起動が成功していることが分かる。

$ docker exec -it nfs-container systemctl status nfs-server
● nfs-server.service - NFS server and services
   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
  Drop-In: /run/systemd/generator/nfs-server.service.d
           └─order-with-mounts.conf
   Active: active (exited) since Sat 2021-10-09 17:25:56 UTC; 22s ago
  Process: 191 ExecStart=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
  Process: 190 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
  Process: 189 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
 Main PID: 191 (code=exited, status=0/SUCCESS)
    Tasks: 0 (limit: 101361)
   Memory: 0B
   CGroup: /docker/944de95c38e4f26fed5e8b5cb3c1825820858753fe4e174040a391355f398c0d/system.slice/nfs-server.service

Oct 09 17:25:56 944de95c38e4 systemd[1]: Starting NFS server and services...
Oct 09 17:25:56 944de95c38e4 systemd[1]: Started NFS server and services.

エクスポートの状況を確認するために、以下のコマンドを実行する。 期待通り/nfs-exportがエクスポートされていることが分かる。

$ docker exec -it nfs-container exportfs -v
/nfs-export     <world>(sync,wdelay,hide,no_subtree_check,fsid=0,sec=sys,rw,secure,no_root_squash,no_all_squash)

以降でマウントを試行していくが、サーバ起動直後の初回のマウントは何度行っても時間がかかる。 調査結果は後述する。

マウントの試行 (自コンテナから) …… 成功

1周目の検証と同様に、まずは自コンテナからマウントを試してみる。

$ docker exec -it nfs-container mount localhost:/ /mnt

エラーメッセージは表示されず、マウントが成功したようなので、結果を確認する。

$ docker exec -it nfs-container df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         687G  585G   68G  90% /
tmpfs            64M     0   64M   0% /dev
tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
shm              64M     0   64M   0% /dev/shm
/dev/sda2       687G  585G   68G  90% /nfs-export
tmpfs           7.8G  8.6M  7.8G   1% /run
localhost:/     687G  585G   68G  90% /mnt

期待通りコンテナ内の/mntにマウントされていることが分かる。

マウントの試行 (他コンテナから) …… 成功

次に、エクスポートしたディレクトリを他のコンテナでマウントしてみる。

他のコンテナについては、コンテナイメージnfs-containerを流用して、以下のように起動することにした。 流用した理由は、NFS関連のパッケージが導入済みであり、従ってmount.nfs4が利用可能なため。 コンテナnfs-containerにアクセスするために、オプション--linkを付け加えている。 また、このコンテナでNFSサーバとして起動する必要はないので、オプション--publish--mountは削除している。

$ docker run \
    --name other-container \
    --privileged \
    --detach \
    --link nfs-container \
    nfs-container:latest

コンテナの起動後に、以下のコマンドを実行して他コンテナからマウントを試してみる。

$ docker exec -it other-container mount nfs-container:/ /mnt

エラーメッセージは表示されず、マウントが成功したようなので、結果を確認する。

$ docker exec -it other-container df -h
Filesystem       Size  Used Avail Use% Mounted on
overlay          687G  585G   68G  90% /
tmpfs             64M     0   64M   0% /dev
tmpfs            7.8G     0  7.8G   0% /sys/fs/cgroup
shm               64M     0   64M   0% /dev/shm
/dev/sda2        687G  585G   68G  90% /etc/hosts
tmpfs            7.8G  8.6M  7.8G   1% /run
nfs-container:/  687G  585G   68G  90% /mnt

期待通りコンテナ内の/mntにマウントされていることが分かる。

マウントの試行 (ホスト側から) …… 成功

まずは、ホスト側にマウントポイントのディレクトリを作成する。 ここでは、ディレクト/var/nfs-sharedを作成している。

$ sudo mkdir /var/nfs-shared

以下のコマンドを実行してホスト側からマウントを試してみる。 NFSサーバのコンテナの起動時にオプション--publishを付与してホスト側にポートを公開しているので、ホスト側のIPアドレスを利用してNFSマウントする。 ここで、ホスト側のIPアドレス192.168.0.145とする。

$ sudo mount -t nfs4 192.168.0.145:/ /var/nfs-shared

エラーメッセージは表示されず、マウントが成功したようなので、結果を確認する。

$ df -h
Filesystem       Size  Used Avail Use% Mounted on
udev             7.8G     0  7.8G   0% /dev
tmpfs            1.6G  2.2M  1.6G   1% /run
(省略)
192.168.0.145:/  687G  585G   68G  90% /var/nfs-shared

期待通りホスト側の/var/nfs-sharedにマウントされていることが分かる。

NFSボリュームの作成の試行 …… 成功

ボリュームを作成する際には、ホスト側に固定のIPアドレスかホスト名が必要になる。 実験的に試してみたところ、ホスト側の通常のIPアドレスに加えて、dockerが作成したブリッジのIPアドレスでも正常動作した。 しかしながら、ループバックアドレス(localhost等)ではマウントができなかった。 ここで、ホスト側のIPアドレス192.168.0.145を利用したとする。

$ docker volume create \
    --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.0.145,rw,nfsvers=4 \
    --opt device=:/ \
    nfs-volume

このボリュームをコンテナにアタッチしてみる。 前出のマウントの試行と同様に、コンテナイメージnfs-containerを流用する。

docker run \
    --name other-container \
    --privileged \
    --detach \
    --mount type=volume,source=nfs-volume,target=/mnt \
    nfs-container:latest

問題なくコンテナが起動できたので、結果を確認する。

$ docker exec -it other-container df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         687G  585G   68G  90% /
tmpfs            64M     0   64M   0% /dev
tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
shm              64M     0   64M   0% /dev/shm
:/              687G  585G   68G  90% /mnt
/dev/sda2       687G  585G   68G  90% /etc/hosts
tmpfs           7.8G  8.6M  7.8G   1% /run

IPアドレスの表示はないものの、期待通り/mntにボリュームがマウントされていることが確認できた。

課題

NFSサーバの起動が遅い

コマンドsystemd-analyzeを利用して調べた結果、サービスrpc-statdの起動失敗によるタイムアウト待ちに引きずられて、サービスnfs-serverの起動開始が遅くなっているように見えた。 具体的には、以下のコマンドを実行して、視覚的に確認した。

$ systemd-analyze plot > /nfs-export/report.svg

また、以下のコマンドの実行結果より、Systemdの管理下にあるサービスの中で、rpc-statdが最も時間を要していることが分かる。

# systemd-analyze blame         
    1min 30.163s rpc-statd.service
          2.036s proc-fs-nfsd.mount
          1.451s systemd-udevd.service
           884ms gssproxy.service
(以下略)

そこで、サービスrpc-statdの状態を確認してみると、起動に失敗していることが分かった。

$systemctl status rpc-statd.service
● rpc-statd.service - NFS status monitor for NFSv2/3 locking.
   Loaded: loaded (/usr/lib/systemd/system/rpc-statd.service; static; vendor preset: disabled)
   Active: failed (Result: timeout) since Sun 2021-10-10 04:50:51 UTC; 8h ago
  Process: 30 ExecStart=/usr/sbin/rpc.statd (code=exited, status=0/SUCCESS)

Oct 10 04:49:23 496ea2458964 rpc.statd[44]: Version 2.3.3 starting
Oct 10 04:49:23 496ea2458964 rpc.statd[44]: Flags: TI-RPC
Oct 10 04:49:23 496ea2458964 systemd[1]: rpc-statd.service: New main PID 44 does not belong to service, and PID file is not owned by roo>
Oct 10 04:49:23 496ea2458964 systemd[1]: rpc-statd.service: New main PID 44 does not belong to service, and PID file is not owned by roo>
Oct 10 04:50:51 496ea2458964 systemd[1]: rpc-statd.service: start operation timed out. Terminating.
Oct 10 04:50:51 496ea2458964 systemd[1]: rpc-statd.service: Failed with result 'timeout'.
Oct 10 04:50:51 496ea2458964 systemd[1]: Failed to start NFS status monitor for NFSv2/3 locking..
Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

サービスの説明より「NFS status monitor for NFSv2/3 locking」とあるので、NFS Version 4を利用するなら影響はなさそうだが、詳細は不明。 また、他のログなどを確認したが、起動に失敗した理由は分からなかった。

NFSボリュームのための静的なネットワークの構築を検討する必要がある

TODO

Docker Composeを利用した検証

TODO

docker-compose.ymlの準備

TODO