以下のブログに詳しく書いてある。
特に、以下の図が分かりやすい。
chromestatus.com 他のブラウザも多分同じ対処をしている。
localとほぼ同義のよう。 英語ネイティブの人でも分かりにくいのか。
以下のクエリ文字列をつけてGoogleにアクセスして検索する。
data = ['a', 'b', 'c']
data = [ ... ]
data.append(100)
data = [ ... ] index = data.index(100) # may raise ValueError
data = [ ... ] for item in data: pass
data = [ ... ] for index, item in enumerate(data): pass
data = {'a': 1, 'b': 2, 'c': 3}
data = { ... } sorted_data = dict(sorted(data.items(), key=lambda item: item[0]))
data = { ... } for key in data.keys(): pass
data = { ... } for value, value in data.values(): pass
data = { ... } for key, value in data.items(): pass
class MyClass: def __init__(self, data): self.data = data def my_method(self): return self.data
class BaseClass: def __init__(self, data): self.data = data class MyClass(BaseClass): def __init__(self, data, exstra_data): super().__init__(data) self.exstra_data = exstra_data
data = '''abc def ghi'''
末尾の[1:-1]
で前後の余分な空行を削除している。
data = ''' abc def ghi '''[1:-1]
import urllib.parse parts = ( 'http', 'localhost:8080', '/foo/bar', '', urllib.parse.urlencode({'a b': 'c&b', 'e/f': 'g?h'}), 'zz' ) url = urllib.parse.urlunparse(parts) print(url) # http://localhost:8080/foo/bar?a+b=c%26b&e%2Ff=g%3Fh#zz
記述量は多いが「タプルの何番目の要素が何だっけ」と迷わずにすむ。
import urllib.parse import collections parts = { 'scheme': 'http', 'netloc': 'localhost:8080', 'path': '/foo/bar', 'params': '', 'query': urllib.parse.urlencode({'a b': 'c&b', 'e/f': 'g?h'}), 'fragment': 'zz' } url = urllib.parse.urlunparse(collections.namedtuple('_', parts.keys())(**parts)) print(url) # http://localhost:8080/foo/bar?a+b=c%26b&e%2Ff=g%3Fh#zz
以下は標準入力からa&b
を読み込んでエンコードする例。
$ echo -n "a&b" | python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read()))" a%26b
以下は標準入力からa%26b
を読み込んでデコードする例。
$ echo -n "a%26b" | python3 -c "import urllib.parse, sys; print(urllib.parse.unquote(sys.stdin.read()))" a&b
以下は引数a&b
をエンコードする例。
$ python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "a&b" a%26b
以下は引数a%26b
をデコードする例。
$ python3 -c "import urllib.parse, sys; print(urllib.parse.unquote(sys.argv[1]))" "a%26b" a&b
RHEL系のパッケージ管理でよく使うコマンドをまとめておく。
以下は/bin/bash
がどのパッケージに含まれているかを調べる例。
$ rpm -q --whatprovides /bin/bash bash-4.4.19-14.el8.x86_64
NFSを利用してファイル共有をしているサーバ環境において、その環境をコンテナ化する際に、コンテナ間でNFSによるファイル共有ができるのかを検証した。 これまでのノウハウが使えるだろうという想定で、CentOS 8のNFSサーバをコンテナ化しようとしたが、安定稼働は無理と思って諦めた。 色々調べたので、試行錯誤の結果を残しておく。
「安定稼働は無理」と言っても、コンテナでNFSサーバを構築するのが無理というわけではなく、CentOS 8のSystemdで制御された世界をコンテナで再現するのが困難という意味。
あと、できるだけスタンダードな方法を選択する方針にしていて、「やろうと思えばできる」という方法は取らない方針とした。
まずは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すると成功するのではと予想。
コンテナの起動前に、コンテナにマウントするホスト側のディレクトリを作成する。
ここでは、ディレクトリ/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
にマウントされていることが分かる。
ボリュームを作成する際には、ホスト側に固定の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
にボリュームがマウントされていることが確認できた。
コマンド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を利用するなら影響はなさそうだが、詳細は不明。 また、他のログなどを確認したが、起動に失敗した理由は分からなかった。
TODO
TODO
TODO