1. 程式人生 > >docker掛載volume的使用者許可權問題,理解docker容器的uid

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來執行容器的。

參考

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