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

若是經過POD的形式來啓動多個容器那麼它們的名稱空間會是共享的麼,因此我這裏討論是在默認狀況下同一個POD的不一樣容器的哪些名稱空間是打通的。這裏先說一下結論,共享的是UTS、IPC、NET、USER。html

UTS名稱空間

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

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的名字,以下圖:docker

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

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

IPC名稱空間

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

下面經過一個實驗來看看同一個POD的IPC名稱空間是不是共享的,在app2中經過命令ipcmk --queue來建立一個隊列,而後在app1中經過命令ipcs來查看,若是有這個隊列就說明是共享的,以下圖:安全

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

MNT名稱空間

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

在同一POD內容器之間掛載點名稱空間是隔離的,若是該POD的多個容器掛載一個POD級別的Volume,那麼它們就能夠實現掛載點的共享,但共享的也僅僅是這一個Volume並非整個文件系統。app

實驗證實,默認狀況下同一個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容器中的用戶

相關文章
相關標籤/搜索