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

docker掛載volume的用戶權限問題,理解docker容器的uid
目錄
遇到的問題
原因
容器共享宿主機的uid
如果不指定user,容器內部默認使用root用戶來運行
容器內部用戶的權限與外部用戶相同
一定要確保容器執行者的權限和掛載數據卷對應
一個更加明顯的demo
參考
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

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

數據文件確實有被寫入,內容可讀
容器寫入的文件的權限都是1111的uid。由於宿主機沒有這個用戶,直接顯示uid
查看進程,可以發現容器的進程也是1111
即-u指定容器內部執行的用戶,以及容器外在宿主機進程的用戶,同樣容器寫到數據卷的權限也由此指定。

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

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

參考
Understanding how uid and gid work in Docker containers
理解 docker 容器中的 uid 和 gid

本文作者:@Ryan Miao
本文鏈接:https://www.cnblogs.com/woshimrf/p/understand-docker-uid.html

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