在 Docker 中掛載單一檔案所遇到的問題
前幾天前輩來找我討論一個奇怪的問題,他說他在 Docker 容器中掛載單一檔案時,會遇到檔案同步效果時有時無的情況。他嘗試在啟動容器時掛載單一檔案,然後發現在主機上修改檔案的內容後,有時候修改的內容並不會同步到容器中。
這個問題我之前都沒有遇到過,因為我還真的沒有只掛載過單一檔案,都是選擇掛載一個資料夾。在找了一些資料後,我發現這個問題背後的原因很有趣,就趁機來水一篇文章 😆。
如何在 Docker 容器中掛載檔案?
在 Docker 中,你可以透過 --mount
來將主機上的某個資料夾掛載到容器中。 這個掛載是雙向同步的,當你在主機上修改資料夾底下的某個檔案,那麼容器中的檔案也會同步修改的內容。 反過來也是,當你在容器中修改某個檔案,那麼主機上的檔案也會同步修改。
過去掛載的指令是
--volume
,但現在更建議使用功能更為方便且彈性的--mount
。
想要啟動一個掛載本地資料夾的容器非常簡單,指令如下:
docker run -it --mount type=bind,source=./src,target=/app ubuntu:latest bash
如此一來就能啟動一個運行 Ubuntu 的容器,並將本機的 src/
資料夾掛載到容器中的 app/
。你對 src/
底下檔案的任何修改,都會同步到容器中的 app/
。
如果你希望讓掛載資料夾底下的檔案在容器中是唯讀的,可以使用 readonly
或 ro
。
docker run -it --mount type=bind,source=./src,target=/app,readonly ubuntu:latest bash
能不能掛載單一檔案?
簡單的介紹了 Docker 的掛載功能後,我們回到主題,容器能不能只掛載單一檔案,而不是資料夾呢?
可以!但不建議,因為同步的效果有可能會失效 😅。
為什麼會這樣呢?這是因為容器追蹤檔案的變化,是透過 inode (index node) 來達成的。如果掛載的 inode 發生變化產生一個新的 inode,那麼 Docker 並不會理會新的 inode,而是繼續關注舊的 inode。
inode 是許多類 Unix 檔案系統中的一種資料結構,主要用於描述檔案系統對象,包括檔案、資料夾、裝置檔案、Socket、管道等。
當我們在修改資料夾底下的檔案時,資料夾的 inode 是不會發生變化的,但是檔案的 inode 是有可能因為我們的修改,而產生一個新的 inode。
例如 sed
或者 vim
,透過這些工具來編輯檔案,會導致檔案的 inode 發生變更,帶有新 inode 的檔案並不會即時被掛載到容器中。
Docker 只會注意啟動時掛載的 inode 內容有沒有發生變化。
簡單做個小實驗,建立一個新的檔案 plain.txt
,並查看這個檔案的 inode。
touch plain.txt
# Use -i flag to print the file's file serial number (inode number).
ls -i
# Result:
# 262219571 plain.txt
我們可以看到 plain.txt
的 inode 是 262219571
。
接下來我們嘗試使用 Vim 來修改這個檔案,並在存檔後再次查看 inode。
# Use vim to update plain.txt, for example, adding 'Hello world!',
# then check inode again
ls -i
# Result:
# 262220007 plain.txt
你會發現 inode 發生了變化,變成了 262220007
。因為 Docker 只會關注 inode 為 262219571
的檔案內容是否有變化,所以我們剛剛做的修改,並不會同步到容器中的檔案。
使用某些方式來修改檔案並不會導致檔案的 inode 發生變化,例如:
echo 'append a new line' >> plain.txt
使用 VSCode 修改檔案也能避免檔案 inode 發生變化。這就是為什麼掛載檔案的同步效果時有時無的原因。是不是很有趣呢?