Docker是如何實現隔離的? 1 Namespace 1.1 容器為何需要進程隔離
• 被其他容器修改文件,導致安全問題
• 資源的并發寫入導致不一致性
• 資源的搶占,導致其他容器被影響
docker run -it --name demo_docker busybox /bin/sh
/ # ps -ef
在宿主機查看進程ID
ps -ef|grep busybox
真實的 docker 容器 pid
這就是進程資源隔離表象:
-
• 對宿主機,
docker run
啟動的只是個進程,pid=44451 -
• 而容器程序本身被隔離了,容器的內部都只能看到自己內部的進程
-
• 這其實是基于linux的Namespace技術(windows也有類似Namespace技術)
Linux命名空間對全局os資源進行了抽象:
-
• 對命名空間內的進程,他們擁有獨立的資源實例,在命名空間內部的進程可實現資源可見
-
• 對命名空間外部的進程,則不可見,實現了資源隔離
這種技術廣泛的應用于容器技術里。Namespace修改了應用進程看待整個計算機“視圖”,即它的“視線”被os限制了,只能“看到”某些指定內容。對于宿主機,這些被“隔離”的進程跟其他進程并無區別。
1.3 Docker Engine 使用了如下 Linux 的隔離技術
-
• The pid namespace:管理 PID namespace (PID: Process ID)
-
• The.NET namespace: 管理網絡namespace(NET: Networking)
-
• The ipc namespace: 管理進程間通信命名空間(IPC: InterProcess Communication)
-
• The mnt namespace:管理文件系統掛載點命名空間(MNT: Mount).
-
• The uts namespace: Unix 時間系統隔離(UTS: Unix Timesharing System).
Linux Namespaces是Linux創建新進程時的一個可選參數,在Linux系統中創建進程的系統調用:
int clone(int (*fn) (void *),void *child stack,
int flags, void *arg, . . .
/* pid_ t *ptid, void *newtls, pid_ t *ctid */ ) ;
調用該方法,這個進程會獲得一個獨立的進程空間,pid是1,且看不到宿主機上的其他進程,即在容器內執行PS命令的結果。
不應將Docker Engine或任何容器管理工具放在跟Hypervisor相同的位置,因為它們并不像Hypervisor那樣對應用進程的隔離環境負責,也不會創建任何實體的“容器”,真正對隔離環境負責的是宿主機os:
該對比圖應該把Docker畫在跟應用同級別并且靠邊的位置。
用戶運行在容器里的應用進程,跟宿主機上的其他進程一樣,都由宿主機os管理,只不過這些被隔離的進程擁有額外設置過的Namespace參數,Docker在這里更多的是輔助和管理。
這也解釋了
1.5 為何Docker項目比VM更好?
使用虛擬化技術作為應用沙盒,須由Hypervisor負責創建虛擬機,這個虛擬機是真實存在的,它里面必須運行一個完整的Guest OS才能執行用戶的應用進程。這就不可避免地帶來額外的資源消耗和占用。
據實驗,一個運行著centos的KVM虛擬機啟動后,在不做優化的情況下,虛擬機自己就需要占用100~200MB內存。此外,用戶應用運行在虛擬機里面,它對宿主機操作系統的調用就不可避免地要經過虛擬化軟件的攔截和處理,這本身又是一層性能損耗,尤其對計算資源、網絡和磁盤I/O的損耗大。
而容器化后的用戶應用:
-
• 依然還是宿主機上的一個普通進程,即不存在虛擬化帶來的各種性能損耗
-
• 使用Namespace作為隔離手段的容器,無需單獨的Guest OS,使得容器額外的資源占用可忽略不計
“敏捷”和“高性能”是容器相較VM最大優勢。
1.6 Namespace的缺點 - 隔離不徹底 ① 多容器間使用的還是同一宿主機的os內核
盡管可在容器里通過Mount Namespace
單獨掛載其他不同版本的os文件,如 CentOS 或者 Ubuntu,但不能改變共享宿主機內核的事實!所以不可能在Windows宿主機運行Linux容器或在低版本Linux宿主機運行高版本Linux容器。
而擁有硬件虛擬化技術和獨立Guest OS的虛擬機,比如Microsoft的云計算平臺Azure,就是運行于Windows服務器集群,但可在其上面創建各種Linux VM。
② Linux內核很多資源無法被Namespace
最典型的比如時間。若你的容器中的程序使用settimeofday(2)
系統調用修改時間,整個宿主機的時間都會被隨之修改,這并不符合用戶預期。
而相比于在虛擬機里可自己隨便折騰,在容器里部署應用時,“什么能做,什么不能做”,用戶都須考慮。
尤其是共享宿主機內核:
-
• 容器給應用暴露出來的攻擊面很大 應用“越獄”難度也比虛擬機低得多。 盡管可使用Seccomp等技術,過濾和甄別容器內部發起的所有系統調用來進行安全加固,但這就多了一層對系統調用的過濾,一定會拖累容器性能。默認情況下,也不知道到底該開啟哪些系統調用,禁止哪些系統調用。 所以,在生產環境中,無人敢把運行在物理機上的Linux容器直接暴露至公網。
基于虛擬化或者、獨立內核技術的容器實現,則能較好地在隔離與性能之間平衡。2 限制容器
Linux Namespace創建了“容器”,為何還對容器“限制”?以PID Namespace為例: 雖然容器內的第1號進程因為“障眼法”只能看到容器里的情況,但在宿主機,它作為第100號進程與其他所有進程之間依然是平等競爭關系。 雖然第100號進程表面上被隔離,但它所能夠使用到的資源(如CPU、內存),可隨時被宿主機其他進程(或容器)占用。當然,該100號進程也可能自己就把所有資源吃光。這些顯然都不是一個“沙盒”的合理行為。
于是,就有了下面的
3 Cgroups( control groups)
2006由google發起,曾將其命名為“進程容器”(process container)。實際上,在Google內部,“容器”術語長期形容被Cgroups限制過的進程組。后來Google說,他們的KVM虛擬機也運行在Borg所管理的“容器”,其實也是運行在Cgroups“容器”中。和今天說的Docker容器差別很大。
2008年并入Linux Kernel 2.6.24。它最主要的作用,就是限制一個進程組能夠使用的資源上限,包括CPU、內存、磁盤、網絡帶寬等等。Docker實現CPU、內存、網絡的限制也均通過cgroups實現。
Cgroups還能對進程進行優先級設置、審計,將進程掛起和恢復等操作。
Linux中,Cgroups給用戶暴露出來的操作接口是文件系統,即它以文件和目錄的方式組織在os的/sys/fs/cgroup路徑。
筆者的 CentOS7 VM里,可用mount指令展示
輸出結果是一系列文件系統目錄。/sys/fs/cgroup下面有很多諸如cpuset、cpu、 memory這樣的子目錄,也叫子系統。這些都是我這臺機器當前可以被Cgroups進行限制的資源種類。在子系統對應的資源種類下,可看到該類資源具體可以被限制的方法。譬如,對CPU子系統能看到如下配置文件
注意到cfs_period和cfs_quota這樣的關鍵詞,這兩個參數需要組合使用,可用來限制進程在長度為cfs_period的一段時間內,只能被分配到總量為cfs_quota的CPU時間
3.1 如何使用配置文件
需在對應的子系統下面創建一個目錄,如現在進入/sys/fs/cgroup/cpu目錄下:
這個目錄就稱為一個“控制組”。os會在你新創建的container目錄下,自動生成該子系統對應的資源限制文件。
3.2 Cgroups實戰 3.2.1 創建 CPU 100%的進程
-
• 執行腳本
-
• 死循環可致CPU 100%,top確認:
查看container目錄下的文件:
container控制組里的CPU quota還沒任何限制(即-1)
CPU period則是默認的0.1s=100 ms=100000 us:
3.2.2 限制該進程
修改這些文件的內容重設限制。
向container組里的cfs_quota文件寫入20 ms(20000 us)
即100ms,被該控制組限制的進程只能使用20ms的CPU,即該進程只能使用到20%的CPU帶寬。接下來把被限制的進程的PID寫入container組里的tasks文件,上面的設置就會對該進程生效
top,可見CPU使用率立刻降到20%
除CPU子系統外,Cgroups的每一項子系統都有其獨有的資源限制能力,如:
-
• blkio,為塊設備設定I/O限制,一般用于磁盤等設備
-
• cpuset,為進程分配單獨的CPU核和對應的內存節點
-
• memory,為進程設定內存使用的限制
Cgroups 就是一個子系統目錄加上一組資源限制文件的組合
。對于Docker等Linux容器,只需在每個子系統下面,為每個容器創建一個控制組(即創建一個新目錄),然后在啟動容器進程之后,把這個進程的PID填寫到對應控制組的tasks文件。
至于在這些控制組下面的資源文件里填上什么值,就靠用戶執行docker run時的參數指定
-
• Docker ≥1.13
docker run -it --cpus=".5" ubuntu /bin/bash
-
• Docker ≤1.12
docker run -it --cpu-period=100000
--cpu-quota=50000 ubuntu /bin/bash
啟動容器后,查看Cgroups文件系統下,CPU子系統中,“docker”這個控制組里的資源限制文件的內容來確認:
cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us
5 總結
xxx
cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us
xxx
容器只是一種特殊的進程,一個正在運行的Docker容器,就是一個啟用了多個Linux Namespace的應用進程,而該進程能夠使用的資源量,則受Cgroups限制。即容器是一個“單進程”模型
。
由于一個容器本質就是一個進程,用戶的應用進程實際上就是容器里PID=1的進程,也是其他后續創建的所有進程的父進程。 這意味著,在一個容器,無法同時運行兩個不同應用,除非你能事先找到一個公共的PID=1的程序充當兩個不同應用的父進程,這也解釋了為何很多人會用systemd
或supervisord
這樣的軟件代替應用本身作為容器的啟動進程。
容器本身設計就是希望容器和應用能同生命周期,這對容器的編排很重要。否則,一旦出現類似于“容器是正常運行的,但是里面的應用早已經掛了”的情況,編排系統處理起來就非常麻煩了。
跟Namespace的情況類似,Cgroups對資源的限制能力也有很多不完善的地方,被提及最多的就是/proc文件系統的問題。 如果在容器里執行top,會發現它顯示的信息是宿主機的CPU和內存數據,而不是當前容器的。造成這個問題的原因就是,/proc文件系統并不知道用戶通過Cgroups給這個容器做了什么樣的資源限制,即:/proc文件系統不了解Cgroups限制的存在。
在生產環境中,這個問題必須修正,否則應用程序在容器里讀取到的CPU核數、可用內存等信息都是宿主機上的數據,這會給應用的運行帶來非常大的困惑和風險。這也是在企業中,容器化應用碰到的一個常見問題,也是容器相較于虛擬機另一個不盡如人意的地方
參考
-
• Docker官網
-
• Docker實戰
-
• 深入剖析Kubernetes
-
• https://tech.meituan.com/2015/03/31/cgroups.html
歡迎加入后端 ,關注本公眾號添加我本人微信,邀請進群 。
最近在準備面試BAT,特地整理了一份面試資料,覆蓋JAVA核心技術、JVM、Java并發、SSM、微服務、數據庫、數據結構等等。在這里,我為大家準備了一份2021年最新最全的互聯網大廠Java面試經驗總結。