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