容器核心技術 :Cgroup 與 Namespace


容器 = cgroup + namespace + rootfs + 容器引擎

  • Cgroup: 資源控制
  • namespace: 訪問隔離
  • rootfs:文件系統隔離。鏡像的本質就是一個rootfs文件
  • 容器引擎:生命周期控制

一、Cgroup

     Cgroup 是 Control group 的簡稱,是 Linux 內核提供的一個特性,用於限制和隔離一組進程對系統資源的使用。對不同資源的具體管理是由各個子系統分工完成的。

 

 

  Cgroup 可以對進程進行任意分組,如何分組由用戶自定義。

子系統介紹

  1. cpuset 子系統
    cpuset 可以為一組進程分配指定的CPU和內存節點。 cpuset 一開始用在高性能計算上,在 NUMA(non-uniform memory access) 架構的服務器上,通過將進程綁定到固定的 CPU 和內存節點上,來避免進程在運行時因跨節點內存訪問而導致的性能下降。

cpuset 的主要接口如下:

  • cpuset.cpus: 允許進程使用的CPU列表
  • cpuset.mems: 允許進程使用的內存節點列表
  1. cpu 子系統
    cpu 子系統用於限制進程的 CPU 利用率。具體支持三個功能
    第一,CPU 比重分配。使用 cpu.shares 接口。
    第二,CPU 帶寬限制。使用 cpu.cfs_period_us 和 cpu.cfs_quota_us 接口。
    第三, 實時進程的 CPU 帶寬限制。使用 cpu_rt_period_us 和 cpu_rt_quota_us 接口。
  1. cpuacct 子系統
    統計各個 Cgroup 的 CPU 使用情況,有如下接口:
  • cpuacct.stat: 報告這個 Cgroup 在用戶態和內核態消耗的 CPU 時間,單位是 赫茲。
  • cpuacct.usage: 報告該 Cgroup 消耗的總 CPU 時間。
  • cpuacct.usage_percpu:報告該 Cgroup 在每個 CPU 上的消耗時間。
  1. memory 子系統
    限制 Cgroup 所能使用的內存上限。
  • memory.limit_in_bytes:設定內存上限,單位字節。
    默認情況下,如果使用的內存超過上限,Linux 內核會試圖回收內存,如果這樣仍無法將內存降到限制的范圍內,就會觸發 OOM,選擇殺死該Cgroup 中的某個進程。
  • memory.memsw,limit_in_bytes: 設定內存加上交換內存區的總量。
  • memory.oom_control: 如果設置為0,那么內存超過上限時,不會殺死進程,而是阻塞等待進程釋放內存;同時系統會向用戶態發送事件通知。
  • memory.stat: 報告內存使用信息。
  1. blkio
    限制 Cgroup 對 阻塞 IO 的使用。
  • blkio.weight: 設置權值,范圍在[100, 1000],屬於比重分配,不是絕對帶寬。因此只有當不同 Cgroup 爭用同一個 阻塞設備時才起作用
  • blkio.weight_device: 對具體設備設置權值。它會覆蓋上面的選項值。
  • blkio.throttle.read_bps_device: 對具體的設備,設置每秒讀磁盤的帶寬上限。
  • blkio.throttle.write_bps_device: 對具體的設備,設置每秒寫磁盤的帶寬上限。
  • blkio.throttle.read_iops_device: 對具體的設備,設置每秒讀磁盤的IOPS帶寬上限。
  • blkio.throttle.write_iops_device: 對具體的設備,設置每秒寫磁盤的IOPS帶寬上限。
  1. devices 子系統
    控制 Cgroup 的進程對哪些設備有訪問權限
  • devices.list: 只讀文件,顯示目前允許被訪問的設備列表,文件格式為
    類型[a|b|c] 設備號[major:minor] 權限[r/w/m 的組合]
    a/b/c 表示 所有設備、塊設備和字符設備。

  • devices.allow: 只寫文件,以上述格式描述允許相應設備的訪問列表。

  • devices.deny: 只寫文件,以上述格式描述禁止相應設備的訪問列表。


二、 Namespace

  Namespace 是將內核的全局資源做封裝,使得每個namespace 都有一份獨立的資源,因此不同的進程在各自的namespace內對同一種資源的使用互不干擾。
舉個例子,執行 sethostname這個系統調用會改變主機名,這個主機名就是全局資源,內核通過 UTS Namespace可以將不同的進程分隔在不同的 UTS Namespace 中,
在某個 Namespace 修改主機名時,另一個 Namespace 的主機名保持不變。


目前,Linux 內核實現了6種 Namespace。

 

 與命名空間相關的三個系統調用:

clone創建全新的Namespace,由clone創建的新進程就位於這個新的namespace里。創建時傳入 flags參數,可選值有 CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWUSER, 分別對應上面六種namespace。

unshare為已有進程創建新的namespace。

setns把某個進程放在已有的某個namespace里。


6種命名空間

  1. UTS namespace
    UTS namespace 對主機名和域名進行隔離。為什么要隔離主機名?因為主機名可以代替IP來訪問。如果不隔離,同名訪問會出沖突。

  2. IPC namespace
    Linux 提供很多種進程通信機制,IPC namespace 針對 System V 和 POSIX 消息隊列,這些 IPC 機制會使用標識符來區別不同的消息隊列,然后兩個進程通過標識符找到對應的消息隊列。
    IPC namespace 使得 相同的標識符在兩個 namespace 代表不同的消息隊列,因此兩個namespace 中的進程不能通過 IPC 來通信。

  3. PID namespace
    隔離進程號,不同namespace 的進程可以使用相同的進程號。
    當創建一個 PID namespace 時,第一個進程的PID 是1,即 init 進程。它負責回收所有孤兒進程的資源,所有發給 init 進程的信號都會被屏蔽。

  4. Mount namespace
    隔離文件掛載點,每個進程能看到的文件系統都記錄在/proc/$$/mounts里。在一個 namespace 里掛載、卸載的動作不會影響到其他 namespace。

  5. Network namespace
    隔離網絡資源。每個 namespace 都有自己的網絡設備、IP、路由表、/proc/net 目錄、端口號等。網絡隔離可以保證獨立使用網絡資源,比如開發兩個web 應用可以使用80端口。
    新創建的 Network namespace 只有 loopback 一個網絡設備,需要手動添加網絡設備。

  6. User namespace
    隔離用戶和用戶組。它的厲害之處在於,可以讓宿主機上的一個普通用戶在 namespace 里成為 0 號用戶,也就是 root 用戶。這樣普通用戶可以在容器內“隨心所欲”,但是影響也僅限在容器內。最后,回到 Docker 上,經過上述討論,namespace 和 cgroup 的使用很靈活,需要注意的地方也很多。 Docker 通過 Libcontainer 來做這些臟活累活。用戶只需要使用 Docker API 就可以優雅地創建一個容器。docker exec 的底層實現就是上面提過的 setns

 

三、rootfs

  rootfs 代表一個 Docker 容器在啟動時(而非運行后)其內部進程可見的文件系統視角,或者叫 Docker 容器的根目錄。
先來看一下,Linux 操作系統內核啟動時,內核會先掛載一個只讀的 rootfs,當系統檢測其完整性之后,決定是否將其切換到讀寫模式。
Docker 沿用這種思想,不同的是,掛載rootfs 完畢之后,沒有像 Linux 那樣將容器的文件系統切換到讀寫模式,而是利用 聯合掛載技術,
在這個只讀的 rootfs 上掛載一個讀寫的文件系統,掛載后該讀寫文件系統空空如也。Docker 文件系統簡單理解為:只讀的 rootfs + 可讀
寫的文件系統。
假設運行了一個 Ubuntu 鏡像,其文件系統簡略如下

 

 

在容器中修改用戶視角下文件時,Docker 借助 COW(copy-on-write) 機制節省不必要的內存分配。








免責聲明!

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



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