Docker實現原理

來源: “極客時間”:深入剖析K8S

 

目錄

Docker核心技術

Namespace:

Cgroups

作用

這裏一些常用的子系統:

Cgroups使用

Docker設定

其他問題

Mount Namespace

chroot

層(Layer)

總結

Docker和虛擬機比較

容器是一個“單進程”模型


 

 

Docker核心技術

 

容器核心技術: 通過約束和修改進程的動態表現,從而爲其創造出一個“邊界”;

對於Docker等大多數Linux容器來說,

  • Cgroups:用來製造約束
  • Namespace:用來修改進程視圖
    其實是Linux創建新進程的一個可選參數
  • Mount NameSpace:
    掛載不同的目錄;

 

Namespace:

Linux提供了多種Namespace

  • PID:
    進程ID,在當前Namespace中新創建的進程PID重新從1開始;
  • Mount:
    用於讓被隔離進程只看到當前 Namespace 裏的掛載點信息
  • UTS
  • IPC
  • Network:
    用於讓被隔離進程看到當前 Namespace 裏的網絡設備和配置
  • User

 

Cgroups

Cgroups的全稱: Linux Control Group;

作用

主要作用:限制一個進程組能夠使用的資源上限,包括CPU/內存/磁盤/帶寬等等;此外,Cgroups 還能夠對進程進行優先級設置、審計,以及將進程掛起和恢復等操作。

在 Linux 中,Cgroups 給用戶暴露出來的操作接口是文件系統,即它以文件和目錄的方式組織在操作系統的 /sys/fs/cgroup 路徑下。

$ mount -t cgroup

這裏一些常用的子系統:

  • cpu:
    限制cpu資源;
  • cpuset:
    爲進程分配單獨的cpu核和對應的內存節點;
  • blkio:
    爲塊設備設定I/O限制,一般用於磁盤的設備;
  • memory:
    爲進程設定內存使用的限制

Cgroups使用

Linux Cgroups 的設計還是比較易用的,簡單粗暴地理解呢,它就是一個子系統目錄加上一組資源限制文件的組合。而對於 Docker 等 Linux 容器項目來說,它們只需要在每個子系統下面,爲每個容器創建一個控制組(即創建一個新目錄),然後在啓動容器進程之後,把這個進程的 PID 填寫到對應控制組的 tasks 文件中就可以了。

Docker設定

在Docker啓動時,可以設置對應的參數即可:

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

其他問題

在Docker中抓取性能問題:

   /proc 文件系統並不知道用戶通過 Cgroups 給這個容器做了什麼樣的資源限制,即:/proc 文件系統不瞭解 Cgroups 限制的存在,所以未加處理,在docker中進行性能統計,還是獲取的宿主機的性能;這個問題必須進行修正;

使用lxcfs:

Mount Namespace

Mount Namspace: 它對容器進程試圖的改變,一定是伴隨着掛載操作(mount)才能生效;

chroot

在Linux操作系統中,可以通過chroot命令(change root file system),可以輕鬆的重新掛載根目錄“/",將進程的根目錄改變到你指定的位置;

爲了能夠讓容器的根目錄看起來更“真實”,我們一般會在這個容器的根目錄下掛載一個完整操作系統的文件系統,比如 Ubuntu16.04 的 ISO。這樣,在容器啓動之後,我們在容器裏通過執行 "ls /" 查看根目錄下的內容,就是 Ubuntu 16.04 的所有目錄和文件。
而這個掛載在容器根目錄上、用來爲容器進程提供隔離後執行環境的文件系統,就是所謂的“容器鏡像”。它還有一個更爲專業的名字,叫作:rootfs(根文件系統)。


所以,一個最常見的 rootfs,或者說容器鏡像,會包括如下所示的一些目錄和文件,比如/bin,/etc,/proc 等等:

注:

rootfs 只是一個操作系統所包含的文件、配置和目錄,並不包括操作系統內核。在 Linux 操作系統中,這兩部分是分開存放的,操作系統只有在開機啓動時纔會加載指定版本的內核鏡像。所以說,rootfs 只包括了操作系統的“軀殼”,並沒有包括操作系統的“靈魂”。

那麼,對於容器來說,這個操作系統的“靈魂”又在哪裏呢?
實際上,同一臺機器上的所有容器,都共享宿主機操作系統的內核。

 

層(Layer)

Docker 在鏡像的設計中,引入了層(layer)的概念。也就是說,用戶製作鏡像的每一步操作,都會生成一個層,也就是一個增量 rootfs。

當然,這個想法不是憑空臆造出來的,而是用到了一種叫作聯合文件系統(Union FileSystem: UnionFS)的能力。

Union File System 也叫 UnionFS,最主要的功能是將多個不同位置的目錄聯合掛載(union mount)到同一個目錄下

 

 

總結

對 Docker 項目來說,它最核心的原理實際上就是爲待創建的用戶進程:

  1.  啓用 Linux Namespace 配置;
  2. 設置指定的 Cgroups 參數;
  3. 切換進程的根目錄(Change Root)。

這樣,一個完整的容器就誕生了。不過,Docker 項目在最後一步的切換上會優先使用pivot_root 系統調用,如果系統不支持,纔會使用 chroot。這兩個系統調用雖然功能類似,但是也有細微的區別;

 

 

Docker和虛擬機比較

以前常用的比較虛擬機和Docer的圖:

其實,更能表現他們之間差別的圖:

說明:

     Docker運用了 (Namespace+Cgroups)技術來創建進程,創建的進程還是直接運行在Linux操作系統中,都是有宿主機的操作系統統一管理,而並不是運行在Docker之上(並沒有一個真正的Docker容器運行在宿主機裏面);

Docker相對虛擬機優勢: 輕,“敏捷”和“高性能”

缺點:

  • 隔離不徹底:
    說到底,docker啓動的進程還是運行在宿主機操作系統上,多個容器之間使用的哈市同一個操作系統內核;
  • 環境:
    在Windows上運行Linux容器,或在低版本Linux宿主機上運行高版本Linux容器,都行不通的;

而 擁有硬件虛擬化技術和獨立Guest OS的虛擬機就沒這樣面問題;--- 有利就有弊;

因此,Docker要考慮:什麼能做,什麼不能做 --- 譬如修改時間,修改的就是宿主機的系統,對宿主機上的所有應用都有影響;

前面講過,Docker只有創建的rootfs,只是一個操作系統鎖包含的文件/配置/目錄,並不包含操作系統內核;同一個宿主機上的docker,都是共享宿主機的操作系統內核;

這就意味着,如果你的應用程序需要配置內核參數、加載額外的內核模塊,以及跟內核進行直接的交互,你就需要注意了:這些操作和依賴的對象,都是宿主機操作系統的內核,它對於該機器上的所有容器來說是一個“全局變量”,牽一髮而動全身。


這也是容器相比於虛擬機的主要缺陷之一:畢竟後者不僅有模擬出來的硬件機器充當沙盒,而且每個沙盒裏還運行着一個完整的 Guest OS 給應用隨便折騰。

另一方面來講,由於 rootfs 裏打包的不只是應用,而是整個操作系統的文件和目錄,也就意味着,應用以及它運行所需要的所有依賴,都被封裝在了一起。容器纔有了一個被反覆宣傳至今的重要特性:一致性。

這種深入到操作系統級別的運行環境一致性,打通了應用在本地開發和遠端執行環境之間難以逾越的鴻溝。

 

容器是一個“單進程”模型

由於一個容器的本質就是一個進程,用戶的應用進程實際上就是容器裏 PID=1 的進程,也是其他後續創建的所有進程的父進程。這就意味着,在一個容器中,你沒辦法同時運行兩個不同的應用,除非你能事先找到一個公共的 PID=1 的程序來充當兩個不同應用的父進程,這也是爲什麼很多人都會用 systemd 或者 supervisord 這樣的軟件來代替應用本身作爲容器的啓動進程。

(後續會有推薦其他更好的解決方法)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章