docker掛載volume的用戶權限問題,理解docker容器的uid


docker掛載volume的用戶權限問題,理解docker容器的uid

在剛開始使用docker volume掛載數據卷的時候,經常出現沒有權限的問題。
這里通過遇到的問題來理解docker容器用戶uid的使用,以及了解容器內外uid的映射關系。

遇到的問題

本地有一個node的項目需要編譯,采用docker來run npm install.

sudo docker run -it --rm  --name ryan  \
-v `pwd`:`pwd`  \
-w `pwd`  node \ 
npm install --registry=https://registry.npm.taobao.org

可以看到,install之后,node_modules文件的權限變成root了。那么,作為使用者的我們就沒有權限去刪除這個文件了。

為什么docker輸出的文件權限會是root?

原因

Docker容器運行的時候,如果沒有專門指定user, 默認以root用戶運行。我們的node鏡像的Dockerfile里沒有指定user.

容器里的執行用戶的id是0,輸出文件的權限也是0.

以下參考Understanding how uid and gid work in Docker containers

容器共享宿主機的uid

首先了解uid,gid的實現。Linux內核負責管理uid和gid,並通過內核級別的系統調用來決定是否通過請求的權限。
比如,當一個進程嘗試去寫文件,內核會檢查創建這個進程的的user的uid和gid,來決定這個進程是否有權限修改這個文件。
這里沒有使用username,而是uid。

當docker容器運行在宿主機上的時候,仍然只有一個內核。容器共享宿主機的內核,所以所有的uid和gid都受同一個內核來控制。

那為什么我容器里的用戶名不一定和宿主內核一樣呢? 比如,superset容器的用戶叫做superset, 而本機沒有superset這個用戶。這是因為username不是Linux kernel的一部分。簡單的來說,username是對uid的一個映射。
然而,權限控制的依據是uid,而不是username。

That’s because the username (and group names) that show up in common linux tools aren’t part of the kernel, but are managed by external tools (/etc/passwd, LDAP, Kerberos, etc). So, you might see different usernames, but you can’t have different privileges for the same uid/gid, even inside different containers

如果不指定user,容器內部默認使用root用戶來運行

我們繼續使用node鏡像, 你可以在github查看Dockerfile. 里面創建了一個
uid為1000的用戶node,但沒指定運行user。

docker run -d --rm --name ryan node sleep infinity

我執行的用戶為ryan(uid=1000), 讓容器后台執行sleep程序。

可以看到,容器外執行sleep的進程的用戶是root。容器內部的用戶也是0(root). 雖然執行docker run的用戶是ryan.

也就是說,我一個普通用戶居然可以以root的身份去執行一個命令。看起來挺恐怖的樣子。

容器內部用戶的權限與外部用戶相同

權限是通過uid來判斷的。接下來測試,相同uid的用戶可以修改歸屬於這個uid的文件。

宿主機有一個用戶ryan:

剛才使用的node鏡像的Dockerfile也定義了1000的用戶node:

我們在本地寫一個文件a, 歸屬用戶ryan

然后,通過volume掛載的方式,指定運行user為1000, 啟動容器node:

docker run -d --rm --name test -u 1000:1000 -v $(pwd):/tmp node sleep infinity

可以看到, 容器外執行sleep的進程,user是ryan(另一個sleep進行是前面的root用戶執行的實例,沒刪除)。
即,docker run -u 可以指定宿主機運行docker命令的用戶, -u指定的uid就是docker實際運行的進程擁有者

接下來去容器內部,看看能不能修改掛載的文件。

可以看到,我們掛載的文件a在容器內部顯示owner是node,即uid=1000的用戶。並且有權限查看和修改。
然后,我們寫一個文件b,在容器內部,這個b自然屬於uid=1000的node。來看看容器外:

同樣的,容器外顯示b從屬於uid=1000的用戶ryan,並且有權限查看和修改。

如此,可以證明容器內外共享uid和對應的權限。

一定要確保容器執行者的權限和掛載數據卷對應

本文最初的問題就是因為容器執行者和掛載數據卷的權限不同。容器內部運行是uid=0的用戶,數據卷從屬與uid=1000的ryan。最終導致容器寫入數據卷的文件權限升級為root, 從而普通用戶無法訪問。

如果掛載了root的文件到容器內部,而容器內部執行uid不是0,則報錯沒有權限。我在掛載npm cache的時候遇到了這個問題,於是有了本文。

一個更加明顯的demo

上面的demo恰好宿主機器和容器都存在一個uid=1000的用戶,於是很和諧的實現了文件權限共享。接下來測試一個更加明顯的demo。

宿主機器和容器都沒有uid=1111, 我們以1111來執行容器:

docker run -d --rm --name demo -u 1111:1111 -v $(pwd):/tmp node sleep infinity

  1. 當前數據卷有文件a和dir any_user. 文件a歸屬與uid=1000, dir any_user任何人可以寫
  2. 運行容器,並以uid=1111執行
  3. 登錄容器內部,查看數據卷,發現文件a和dir any_user都歸屬於uid=1000的node(uid映射)
  4. 由於容器內部沒有uid=1111的用戶,所以顯示I have no name!, 沒有username,沒有home。
  5. 在容器內部執行數據卷的寫操作,提示沒權限。(因為數據卷的權限是uid=1000)
  6. 在容器內部寫入一個文件到公共數據區(777).

接下來看看容器外的表現:

  • 數據文件確實有被寫入,內容可讀
  • 容器寫入的文件的權限都是1111的uid。由於宿主機沒有這個用戶,直接顯示uid
  • 查看進程,可以發現容器的進程也是1111

即-u指定容器內部執行的用戶,以及容器外在宿主機進程的用戶,同樣容器寫到數據卷的權限也由此指定。

如此,這個demo更容易理解容器內外的uid的對應關系。理解了以后我們掛載數據卷的時候就不會出現權限問題了。

由於安全問題,通常也是建議不用使用root來運行容器的。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM