ホスト側のファイル変更が bind-mount 先に反映されないときのトラブルシューティング

Docker でログ収集サーバを立てていて、ホスト側の conf を修正してもコンテナ側 bind-mount 先 conf に反映されなかった件が解決したという話。

メモ程度なので時間あるときに書きなおします。

なぜなのか

Vim で上書き時に inode 番号が変わってしまったから。Vim が悪い。

つまり

Docker is not updating the mounted file | Medium

調べた限りでは Vim に限らず、emacs とか sed とかでも同じことが起こるようだが、エディタが既存ファイル a.txt を編集して上書きするという処理を、a.txt.tmp みたいな一時ファイルを作成してから、 もとの a.txt を消去して a.txt.tmpa.txt にリネームするという、工程を踏んで行うことに起因している。a.txt の inode 番号が 1 だったら、a.txt.tmp の inode 番号は 2、これをリネームしても inode 番号は変わらないから、みかけとしては a.txt の inode 番号は 2 に変わる。

この a.txt が Docker container 上でマウントされたファイルだとすると、コンテナ側では a.txt の inode は 1 のまま。bind-mount においてホストとコンテナ間のファイル同期はこの inode を見てやっているようなので、ホスト側 a.txt の inode 番号が変わったら当然コンテナ側から照合できなくなる。ホスト側でいくら変更を加えてもコンテナ側から見えないというわけ、らしい。

対処法

ググってヒットしたサイトで2つの方法が挙げられていた。内容的にはほぼコピペなので一応リンク張っておきます。
dockerで単体ファイルをマウントしたときに更新が反映されない問題の対策 #Docker – Qiita
Dockerでファイルをbind mountしたら同期されなかった話 (zenn.dev)

ファイル単位ではなく、ディレクトリ単位で bind-mount する

volumes:
      - ./config/loki-config.yaml:/etc/loki/local-config.yaml

私が作成した docker-compose.yaml の一部だが、このようにホスト側のファイルを1対1でコンテナに紐づけると inode 番号がホスト側で変わったときにコンテナで検知できない。

volumes:
      - ./config:/etc/loki/

このように親ディレクトリ同士でマウントしてやれば、子ファイルの inode 番号が変わったとしても、親ディレクトリの inode 番号は変わらないので、コンテナ側で変更を検知できるという理屈(多分)。

ただ、ホスト側 ./config にコンテナには置きたくないファイルがある場合とかになると、マウント対象から除外するとかややこしいことをやらなくてはならないので、今回この方法は却下した。

エディタの設定を変える

ここにある通り、Vim の場合は :set backupcopy=yes で inode を変えずにファイルの上書き保存ができるようだ(このコメントによれば、:set noswapfile は inode とは関係がないらしい)1。これを機に .vimrc に書いておくことにした。
Sublime をお使いなら、こちらの設定でいけるみたい2

ぼやき

QNAP 製 NAS を Linux サーバとして使うのは、一般の Linux ディストリビューションと大分違うところがあるのでそんなには勧められないかもしれない。Docker がプリインストールされてるのでその上に Linux コンテナつくって遊べばいいんだけど、QNAP 謹製の Container Station では Docker Container 設定の細かいところを弄りずらいので、結局 QTS で管理せざるを得ないのです。

  1. アトミック処理(atomic save)にすればよいということ。UNIX 系の Vim でのデフォルト値は yes だが、QTS だと auto だったせいだと思われる(filesystems – How does vim save files? – Stack Overflow)。
  2. Sublime 使ったことないので責任取れません