同一個POD中默認共享哪些名稱空間


如果通過POD的形式來啟動多個容器那么它們的名稱空間會是共享的么,所以我這里討論是在默認情況下同一個POD的不同容器的哪些名稱空間是打通的。這里先說一下結論,共享的是UTS、IPC、NET、USER。

UTS名稱空間

主機名名稱空間,保存內核名稱、版本以及主機名和域名。默認情況下同一個POD的不同容器是共享UTS的,看下面的配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: centos-dep
  labels:
    app: centos
spec:
  replicas: 1
  selector:
    matchLabels:
      app: centos
  template:
    metadata:
      labels:
        app: centos
    spec:
      containers:
      - name: app1
        image: centos
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh", "-c"]
        args:
          - sleep 3600
      - name: app2
        image: centos
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh", "-c"]
        args:
          - sleep 3600

運行這個POD,然后分別登陸到不同容器去查看主機名,你會發現主機名一樣,而且就是POD的名字,如下圖:

另外你通過uname -a如果查看到的內容是一致的也說明是共享UTS名稱空間的。

實驗證明,默認情況下同一個POD中的不同容器的UTS名稱空間是共享的。

IPC名稱空間

進程間通信名稱空間,IPC的隔離就是阻斷進程間通信,主要是信號量、隊列和共享內存。運行主機進程通過上面的機制進行通信。

下面通過一個實驗來看看同一個POD的IPC名稱空間是否是共享的,在app2中通過命令ipcmk --queue來創建一個隊列,然后在app1中通過命令ipcs來查看,如果有這個隊列就說明是共享的,如下圖:

實驗證明,默認情況下同一個POD中的不同容器的IPC名稱空間是共享的。

MNT名稱空間

Mount名稱空間,提供對磁盤掛載點和文件系統的隔離能力。同一主機上的不同進程訪問相同的路徑會得到相同的內容,因為它們共享本地主機的磁盤和文件系統。

在同一POD內容器之間掛載點名稱空間是隔離的,如果該POD的多個容器掛載一個POD級別的Volume,那么它們就可以實現掛載點的共享,但共享的也僅僅是這一個Volume並不是整個文件系統。

實驗證明,默認情況下同一個POD中的不同容器的MNT名稱空間不是共享的。

NET名稱空間

網絡名稱空間,同一主機上的不同進程可以進行localhost或者本地unix socket通信。在單獨啟動容器的時候不同容器是隔離的,但是在POD中不同容器通過一個Infra容器來進行共享網絡名稱空間,其原理是其他用戶自己定義的容器都Join這個Infra容器的網絡。這里我啟動的就是一個Cetnos鏡像,無法做本地通信驗證。不過它的確是通過Infra容器來共享的。

PID名稱空間

進程ID名稱空間,同一主機上的不同進程在同一PID空間內可以看到其他進程的ID,並且同一PID空間的進程的ID不會重復。另外PID名稱空間有層級關系,子空間看不到父空間的內容,但是父空間可以管理子空間,比如發送信號。

在POD中則對應為同一POD內的不同容器可以看到對方的進程ID。默認不是共享的,可以設置POD的shareProcessNamespace這個值為true來進行共享,默認為false。我在App2中啟動一個top命令,然后在App1中通過ps命令查看,看下面的測試:

實驗證明,默認情況下同一個POD中的不同容器的PID名稱空間不是共享的。

USER名稱空間

隔離用戶、組以及相關用戶能力的。也就是在不同的User Namespace中,相同的用戶可以有不同的UID或者不同的權限。另外還可以通過映射的方式把某個User Namespace的用戶映射到另外一個User Namespace的用戶上,這樣這兩個名稱可能不同的用戶就具有相同的權限。如果想要在本機進行驗證需要查看一下這個文件:

cat /proc/sys/user/max_user_namespaces如果是0則表示沒有開啟,需要給它一個值echo "15000" > /proc/sys/user/max_user_namespaces,然后你再運行unshare -U或者unshare --user就不會報錯了。

在Docker中默認並沒有開啟user namespace。

可以看到當前Bash進程和Dockerd進程的名稱空間都一樣,因為它們都是在同一個名稱空間上運行的。另外需要說明的是uip_map的輸出,第一個數字是在當前名稱里的用戶ID,第二個數字是該用戶ID在當前名稱空間外部被映射到哪個用戶ID上,最后一個數字是映射范圍。

然后我們啟動一個包含兩個容器的POD來看一下,如下圖:

容器的User namespace和容器外的是一樣的,也就是說沒有單獨為容器創建User namespace,而且容器內的用戶ID是0,映射到容器外也是0,這就是意味着容器內的root用戶和容器外的root用戶擁有相同的權限。說白了就是容器中的進程是以root用戶權限運行的,並且這個容器中的root用戶和宿主機上的root用戶是同一個,看下圖,這2個容器進程就是以root運行的:

如果你需要驗證,那么你把宿主機上的一個只能由root打開的文件掛載容器中,你看看能不能打開就知道了。

就算你進入容器查看這個sleep 3600其實也是root運行的,簡單來說容器內UID為0的root用戶就是容器外UID為0的root用戶。為什么會是這樣呢?在整個系統共享一個內核,而內核只管理一套uid和gid,並且對內核來說只識別uid不識別用戶名,也就是說內核在做權限方面它通過uid來做,用戶名只是對於用戶來講方便辨認。

不要誤認為你在容器中創建一個用戶,然后在宿主機也可以看到,因為/etc/password這個文件在不同的文件系統上,容器和宿主機的文件系統還是隔離的。

但有些時候也不要被用戶名所迷惑,你應該檢查UID,查看容器進程的uid_map中的信息。

讓容器進程使用root賬號顯然不安全,因為它的root就是宿主機的root,所以通常我們會給dockerd進程建立單獨的賬號或者使用User Namespace。不過推薦使用User Namesapce,因為有些使用容器進程必須以root來運行,如果使用User Namespace的話,我們就可以把宿主機的一個普通用戶映射到容器中的root用戶,這樣容器進程以為自己是root並且在它所在的名稱空間內有各種權限,但是在宿主機上它還是普通用戶。

如何開啟User Namespace呢:

cat /boot/config-3.10.0-957.el7.x86_64 | grep _NS,先檢查一下你的內核是否開啟了User Namespace

檢查一下是否有下面的文件,如果沒有就手動建立:

你可以使用系統中有的用戶然后添加到這里,最后在docker的啟動參數中加入這個賬號,也可以讓dockerd自己來建立,如果讓dockerd自己來完成,在dockerd的啟動docker-daemon.json中加入下面的內容,default表示使用dockerd去建立賬號,它使用的名字為dockermap,如果你使用自己的就替換dufault:

{
    "userns-remap": "default",
}

在RHEL 7.5版本,上面的配置在dockerd啟動的時候會報錯"Can't create ID mappings: %!v(MISSING): No subuid ranges found for user "dockremap"",查詢之后判斷應該是系統BUG,可以看看Redhat官網的Bug說明Bug-1546870,它會在系統中建立dockremap賬號然后使用usermod -v參數來設置dockermap用戶的ID范圍,但是在Centos 7.5版本上的usermod命里沒有-v參數。這就意味着RHEL 7.5不支持動態添加subid。所以我們只能手動來做,不過據說其他發行版可以支持比如Ubantu或者Fedora。

向從屬用戶和組文件中添加范圍(如果你使用dockremap賬號,那么你無須手動建立,因為dockerd啟動的時候就會建立,如果上面的配置是default):

echo "dockremap:10000:65536" > /etc/subuid
echo "dockremap:10000:65536" > /etc/subgid

一共三個字段:

  • 第一個字段dockremap,這個一個宿主機上的用戶名

  • 第二個字段10000,表示子User Namespace中用戶ID從哪里開始

  • 第三個字段65536,表示子User Namespace中可以有多少個用戶ID

整體含義是宿主機的dockremap賬號一共有65536個從屬用戶,用戶ID從10000-165535。這個從事用戶的ID不是真實的,只是用來分配,它會從這個范圍里拿一個ID映射到容器進程里的用戶,比如容器進程還是用root用戶,其UID實0,那么我們就可以從dockremap這個從屬ID中拿一個來映射容器進程中的root。這樣容器中看起來是root且具有root權限,但是在宿主機上它就是一個普通賬號dockremap的權限。配置好后重啟dockerd進程。配置好重新啟動POD,如下:

同一個POD中的User Namespace是共享的,但此時它與宿主機的進程就已經不共享User Namespace了。再看一下uid_map

容器中的UID0映射到容器外的從屬ID 10000。

不過這樣雖然安全但是有些容器進程無論在容器內還是在容器外都需要root賬號,比如prometheus的node_explorer,它是以DaemonSet形式運行的需要共享宿主機的網絡名稱空間,如果以上的用戶來運行則會啟動失敗,如下圖:

其實這個和DaemonSet沒關系,主要是在docker上啟用User Namspace后會有一些限制,userns-remap ,也就是說啟用了User Namespace后容器將不能共享宿主機的PID和NET名稱空間。所以我想因為有一些限制所以docker默認才不開啟User Namespace。不過如果直接通過docker來啟動容器可以指定--usens=host來為某個容器禁用User Namespace,不過在Kubernetes中目前沒找到配置POD那個參數可以起到這個效果,有人知道請留言。

實驗證明:在默認情況下同一個POD是共享User Namespace的。

最簡單的辦法來驗證一下

在宿主機上找到該POD中的2個容器的容器ID,

通過docker inspect CONTINER_ID --format {{.State.Pid}}查看兩個容器在宿主機上的進程號

通過進程ID查看每個進程的ns情況,左側紅色的是被查看進程名稱空間文件,右側則是該文件指向的具體的Namespace文件,中括號里面的是具體Namespace文件號,如果兩個進程的指向的Namespace文件號相同,則說明它們處在同一名稱空間。

紅色箭頭編號相同的就是當前POD中2個容器所共享的名稱空間。不過在這里我也有些不明白,uts是共享的可是上圖中看到的編號確不一樣。因為在宿主機的當前終端運行unshare --uts /bin/bash命令將會在一個新的uts名稱空間打開一個bash程序,這個bash進程和之前那個就是在不同的uts中,看下圖:

進行namespace的api操作

對Namespace的API操作包括clone()、setns()和unshare()。它們有一些不同:

  • clone():創建新進場的同時可以創建namespace,通過在這個函數中加入不同的名稱空間標志來完成。

  • setns():它是加入一個已經存在的namespace,需要給它傳遞具體的namespace文件描述符。通常是在調用該函數之后調用clone(),其目的就是讓一個新進程在一個已經存在的namespace中運行。docker exec就是利用這種機制讓你指定的命令在容器中運行。

  • unshare():對當前的進程進行namespace隔離,換句話說它不啟動新進程,而是讓當前進程或者調用它的進程進入到一個新的namespace中。系統命令unshare就是利用這個調用來實現的。

注意在使用unshare系統調用或者命令或者setns系統調用的時候當涉及到PID Namespace的時候它的處理有些特殊,並不是讓調用者進入新的PID Namespace,而是讓子進程進入,成為該PID Namespace的1號進程。為什么為這樣呢?因為一個進程的PID在系統中是常量,一但一個進程運行它的PID就確定了從而它的父子進程也會被確定,所以不能讓它在調用setns或者unshare的時候發生變化,一但變化系統就無法維護這個進程表。

Namespace的資源隔離

Docker背后的內核知識1

Linux Namespace User

理解Docker容器的UID和GID

隔離Docker容器中的用戶


免責聲明!

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



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