Docker技術三大要點:cgroup, namespace和unionFS的理解

www.docker.com的網頁有這樣一張有意思的動畫:java

從這張gif圖片,咱們不難看出Docker網站想傳達這樣一條信息, 使用Docker加速了build,ship和run的過程。spring

Docker最先問世是2013年,以一個開源項目的方式被你們熟知。docker

Docker的奠定者是dotcloud,一家開發PaaS平臺的技術公司。apache

不過惋惜的是,這家公司把Docker開源以後,於2016年倒閉了,由於其主業務PaaS沒法和微軟,亞馬遜等PaaS業界巨頭競爭,不由讓人唏噓。bash

Docker實際上是容器化技術的具體技術實現之一,採用go語言開發。不少朋友剛接觸Docker時,認爲它就是一種更輕量級的虛擬機,這種認識實際上是錯誤的,Docker和虛擬機有本質的區別。容器本質上講就是運行在操做系統上的一個進程,只不過加入了對資源的隔離和限制。而Docker是基於容器的這個設計思想,基於Linux Container技術實現的核心管理引擎。服務器

爲何資源的隔離和限制在雲時代更加劇要?在默認狀況下,一個操做系統裏全部運行的進程共享CPU和內存資源,若是程序設計不當,最極端的狀況,某進程出現死循環可能會耗盡CPU資源,或者因爲內存泄漏消耗掉大部分系統資源,這在企業級產品場景下是不可接受的,因此進程的資源隔離技術是很是必要的。網絡

我當初剛接觸Docker時,覺得這是一項新的技術發明,後來才知道,Linux操做系統自己從操做系統層面就支持虛擬化技術,叫作Linux container,也就是你們處處能看到的LXC的全稱。學習

LXC的三大特點:cgroup,namespace和unionFS。動畫

cgroup:網站

CGroups 全稱control group,用來限定一個進程的資源使用,由Linux 內核支持,能夠限制和隔離Linux進程組 (process groups) 所使用的物理資源 ,好比cpu,內存,磁盤和網絡IO,是Linux container技術的物理基礎。

namespace:

另外一個維度的資源隔離技術,你們能夠把這個概念和咱們熟悉的C++和Java裏的namespace相對照。

若是CGroup設計出來的目的是爲了隔離上面描述的物理資源,那麼namespace則用來隔離PID(進程ID),IPC,Network等系統資源。

咱們如今能夠將它們分配給特定的Namespace,每一個Namespace裏面的資源對其餘Namespace都是透明的。

不一樣container內的進程屬於不一樣的Namespace,彼此透明,互不干擾。

咱們用一個例子來理解namespace的必要。

假設多個用戶購買了一臺Linux服務器的Nginx服務,每一個用戶在該服務器上被分配了一個Linux系統的帳號。咱們但願每一個用戶只能訪問分配給其的文件夾,這固然能夠經過Linux文件系統自己的權限控制來實現,即一個用戶只能訪問屬於他自己的那些文件夾。

可是有些操做仍然須要系統級別的權限,好比root,但咱們確定不可能給每一個用戶都分配root權限。所以咱們就可使用namespace技術:

咱們可以爲UID = n的用戶,虛擬化一個namespace出來,在這個namespace裏面,該用戶具有root權限,可是在宿主機上,該UID =n的用戶仍是一個普通用戶,也感知不到本身其實不是一個真的root用戶這件事。

一樣的方式能夠經過namespace虛擬化進程樹。

在每個namespace內部,每個用戶都擁有一個屬於本身的init進程,pid = 1,對於該用戶來講,彷彿他獨佔一臺物理的Linux服務器。

對於每個命名空間,從用戶看起來,應該像一臺單獨的Linux計算機同樣,有本身的init進程(PID爲1),其餘進程的PID依次遞增,A和B空間都有PID爲1的init進程,子容器的進程映射到父容器的進程上,父容器能夠知道每個子容器的運行狀態,而子容器與子容器之間是隔離的。從圖中咱們能夠看到,進程3在父命名空間裏面PID 爲3,可是在子命名空間內,他就是1.也就是說用戶從子命名空間 A 內看進程3就像 init 進程同樣,覺得這個進程是本身的初始化進程,可是從整個 host 來看,他其實只是3號進程虛擬化出來的一個空間而已。

看下面的圖加深理解。

父容器有兩個子容器,父容器的命名空間裏有兩個進程,id分別爲3和4, 映射到兩個子命名空間後,分別成爲其init進程,這樣命名空間A和B的用戶都認爲本身獨佔整臺服務器。

Linux操做系統到目前爲止支持的六種namespace:

unionFS:

顧名思義,unionFS能夠把文件系統上多個目錄(也叫分支)內容聯合掛載到同一個目錄下,而目錄的物理位置是分開的。

要理解unionFS,咱們首先要認識bootfs和rootfs。

1. boot file system (bootfs):包含操做系統boot loader 和 kernel。用戶不會修改這個文件系統。

一旦啓動完成後,整個Linux內核加載進內存,以後bootfs會被卸載掉,從而釋放出內存。

一樣內核版本的不一樣的 Linux 發行版,其bootfs都是一致的。

2. root file system (rootfs):包含典型的目錄結構,包括 /dev, /proc, /bin, /etc, /lib, /usr, and /tmp

就是我下面這張圖裏的這些文件夾:

等再加上要運行用戶應用所須要的全部配置文件,二進制文件和庫文件。這個文件系統在不一樣的Linux 發行版中是不一樣的。並且用戶能夠對這個文件進行修改。

Linux 系統在啓動時,roofs 首先會被掛載爲只讀模式,而後在啓動完成後被修改成讀寫模式,隨後它們就能夠被修改了。

不一樣的Linux版本,實現unionFS的技術可能不同,使用命令docker info查看,好比個人機器上實現技術是overlay2:

看個實際的例子。

新建兩個文件夾abap和java,在裏面用touch命名分別建立兩個空文件:

新建一個mnt文件夾,用mount命令把abap和java文件夾merge到mnt文件夾下,-t執行文件系統類型爲aufs:

sudo mount -t aufs -o dirs=./abap:./java none ./mnt

mount完成後,到mnt文件夾下查看,發現了來自abap和java文件夾裏總共4個文件:

如今我到java文件夾裏修改spring,好比加上一行spring is awesome, 而後到mnt文件夾下查看,發現mnt下面的文件內容也自動被更新了。

那麼反過來會如何呢?好比我修改mnt文件夾下的aop文件:

而java文件夾下的原始文件沒有受到影響:

實際上這就是Docker容器鏡像分層實現的技術基礎。若是咱們瀏覽Docker hub,能發現大多數鏡像都不是從頭開始製做,而是從一些base鏡像基礎上建立,好比debian基礎鏡像。

而新鏡像就是從基礎鏡像上一層層疊加新的邏輯構成的。這種分層設計,一個優勢就是資源共享。

想象這樣一個場景,一臺宿主機上運行了100個基於debian base鏡像的容器,難道每一個容器裏都有一份重複的debian拷貝呢?這顯然不合理;藉助Linux的unionFS,宿主機只須要在磁盤上保存一份base鏡像,內存中也只須要加載一份,就能被全部基於這個鏡像的容器共享。

當某個容器修改了基礎鏡像的內容,好比 /bin文件夾下的文件,這時其餘容器的/bin文件夾是否會發生變化呢?

根據容器鏡像的寫時拷貝技術,某個容器對基礎鏡像的修改會被限制在單個容器內。

這就是咱們接下來要學習的容器 Copy-on-Write 特性。

容器鏡像由多個鏡像層組成,全部鏡像層會聯合在一塊兒組成一個統一的文件系統。若是不一樣層中有一個相同路徑的文件,好比 /text,上層的 /text 會覆蓋下層的 /text,也就是說用戶只能訪問到上層中的文件 /text。

假設我有以下這個dockerfile:

FROM debian

RUN apt-get install emacs

RUN apt-get install apache2

CMD ["/bin/bash"]

執行docker build .看看發生了什麼。

生成的容器鏡像以下:

當用docker run啓動這個容器時,實際上在鏡像的頂部添加了一個新的可寫層。這個可寫層也叫容器層。

容器啓動後,其內的應用全部對容器的改動,文件的增刪改操做都只會發生在容器層中,對容器層下面的全部只讀鏡像層沒有影響。

要獲取更多Jerry的原創文章,請關注公衆號"汪子熙":

相關文章
相關標籤/搜索