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,並通過核心級別的系統呼叫來決定是否通過請求的許可權。
這裡沒有使用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