Docker container中mount文件內容無法同步的問題解決

  這篇博文主要講述了我遇到的一個Docker使用mount volume中的一個“坑”,即在宿主機中修改了某個已經mount進container的文件,但在container中發現卻沒有改變。我們嘗試找解決方法,也順便嘗試知道是什麼原因。

1. 問題描述

在Linux(Oracle Linux Server release 7.7)本地創建一個文件"test.txt"(默認權限664),然後再用如下命令mount進一個container:

docker run -v "/home/ec2-user/deleme_me/test.txt:/test.txt" --rm -it ubuntu:18.04 /bin/bash

在container中看到了"/test.txt"的內容與宿主機相同。

然後在宿主機上用vim(7.4.1099)修改文件內容,增加一行內容,再到container中,發現/test.txt並沒有隨之變化。

很顯然,這不是我們所期望的,按照我們的設想,在這樣的mount之後,宿主機和container中文件的變更應該是雙向同步的,即在容器中改變文件內容,宿主機中應當即時得到更新,反之亦然,因爲mount本身就表示同一個文件,只是藉由docker使其其出現在了不同的 Mount namespaces 中。

那究竟是什麼原因導致這樣與設想大相徑庭的結果呢?

2. 問題分析

我們知道,在Docker中,mount volume的原理是借用了Linux Namespace中的 Mount NameSpace,隔離系統中不同進程的掛載點視圖,實際文件是沒有變化的,比如上面的例子,在container中,/bin/bash實際就是一個運行在宿主機上的進程,被Docker用Linux分別隔離了Mount Namespace、UTS Namespace、IPC Namespace、PID Namespace、Network Namespace和User Namespace,使得它看上去好像運行在了一個獨立的、相對隔離的系統上,但實際它的一切資源都是宿主機在不同Namespace中的一個投影,文件也不例外。

所謂文件沒有變化,我們如何證明呢?在Linux中,證明文件是否相同的根本途徑是,判斷其inode,如果兩個文件的inode相同,兩個文件必定爲同一文件,從而兩個文件的內容也必然相同。

那我們來看看宿主機和container中看到的text.txt是不是一樣:

// container中
root@e365010a98f6:/# stat test.txt File: test.txt Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ UNKNOWN) Gid: ( 1000/ UNKNOWN) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:35:38.779485729 +0000 Birth: -

 

// 宿主機中
$ stat test.txt File: 'test.txt' Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ec2-user) Gid: ( 1000/ec2-user) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:35:38.779485729 +0000 Birth: -

以上兩個代碼塊中,上圖是container中的,下圖時候宿主機中的。我們看到在容器中和宿主機中的test.txt的inode號都是4510745,即可認爲是同一文件。

這時,回到我們剛開始遇到的那個問題,我們在宿主機中修改test.txt文件,用Vim打開文件,追加一行,然後保存退出,然後打開container中的文件,發現出現了怪事,container中的文件沒有更新,也就是重現了我們一開始描述的問題。

那我們再來看看他們的文件信息:

// container中
root@e365010a98f6:/# stat test.txt File: test.txt Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 0 Access: (0664/-rw-rw-r--) Uid: ( 1000/ UNKNOWN) Gid: ( 1000/ UNKNOWN) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:42:25.252402227 +0000 Birth: -

 

// 宿主機中
$ stat test.txt File: 'test.txt' Size: 14 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 6553165 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ec2-user) Gid: ( 1000/ec2-user) Access: 2020-04-27 01:42:25.252402227 +0000 Modify: 2020-04-27 01:42:25.252402227 +0000 Change: 2020-04-27 01:42:25.252402227 +0000 Birth: -

由上面的輸出可以看到,container中的inode沒有變化,但宿主機中的已經變化了,也就是說,從文件系統層面來看,此時container中和宿主機中它們已經不是同一個文件了。既然不是同一個文件,那又有什麼同步可言?

那好了,那問題的初步原因明白了,就是用vim在宿主機編輯了被mount進container的文件後,宿主機上的文件inode變化了,導致兩個文件內容無法同步。

那問題就變成vim爲何有什麼神奇的邏輯呢?

原來,Linux默認情況下,vim爲了防止在你修改文件的過程中,由於磁盤或者系統出現問題而導致當前被修改的文件的損壞,它做了類似如下邏輯:

  • 複製出一個需要修改文件的副本,命名爲在原來文件的基礎上增加".swp"後綴以及"."前綴
  • 修改內容保存到有".swp"後綴的文件,並flush到磁盤
  • 交換原文件和swp文件的名稱
  • 刪除swp文件

如此一來,在宿主機看來,原來inode就被釋放掉了,可是在container看來,他還是需要留着這個inode。

3. 解決方法

提到解決,實際上無解的,因爲以上出現的每一方站在其自身的角度看,都是合理的,這些邏輯也是by design的。於是乎,我們就尋找workaround的角度來嘗試繞過我們的問題吧。

以下幾種方法均可以成功繞過該問題:

  • 用echo等代替vim文件修改

既然這個修改文件的邏輯是vim引入的,那麼我們替換vim就可以解決問題了,增加文件內容的方法很多,比如我們可以echo代替:

root@2c8d3dfc7d57:/# echo hello >> test.txt
  • 修改vim配置

打開vim,輸入

:scriptnames

找到vimrc的路徑,例如是"/etc/vimrc",再打開"/etc/vimrc",添加如下兩行:

set backup
set backupcopy=yes

這樣可以解決問題,不過也有一個很大的副作用,那就是每次用vim編輯文件保存之後,vim會生成一個類似該被修改文件,但末尾增加了一個"~"後綴,用以保存修改之前的文件內容。

  • 修改文件權限

我們發現,當文件的權限修改爲"其他user有寫權限"後,以上說的vim邏輯就變了,修改保存後,原文件的inode不再變化,這就使得我們workaround變得非常簡單:

$ chmod 666 test.txt

這個應該是目前爲止,最爲輕量的workaround了。

4. 參考

1. https://forums.docker.com/t/modify-a-file-which-mount-as-a-data-volume-but-it-didnt-change-in-container/2813

2. https://unix.stackexchange.com/questions/36467/why-inode-value-changes-when-we-edit-in-vi-editor

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章