1、容器基本概念
容器其實就是一種特殊的進程,容器是一個 ‘單進程’模型。
Namespace :隔離 Namespace 技術實際上修改了應用進程看待整個計算機“視圖”,即它的“視線”被操作系統做了限制,只能“看到”某些指定的內容
Linux Cgroups :限制 它最主要的作用,就是限制一個進程組能夠使用的資源上限,包括 CPU、內存、磁盤、網絡帶寬等等。
通過修改 配置文件來限制某個進程的可使用的資源
可以看到,在 /sys/fs/cgroup 下面有很多諸如 cpuset、cpu、 memory 這樣的子目錄,也叫子系統。這些都是我這台機器當前可以被 Cgroups 進行限制的資源種類。而在子系統對應的資源種類下,你就可以看到該類資源具體可以被限制的方法。比如,對 CPU 子系統來說,我們就可以看到如下幾個配置文件,這個指令是:
ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
如果熟悉 Linux CPU 管理的話,你就會在它的輸出里注意到 cfs_period 和 cfs_quota 這樣的關鍵詞。這兩個參數需要組合使用,可以用來限制進程在長度為 cfs_period 的一段時間內,只能被分配到總量為 cfs_quota 的 CPU 時間。
-1代表不限制
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us -1 $ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us 100000
接下來,我們可以通過修改這些文件的內容來設置限制。
比如,向 container 組里的 cfs_quota 文件寫入 20 ms(20000 us):
$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
它意味着在每 100 ms 的時間里,被該控制組限制的進程只能使用 20 ms 的 CPU 時間,也就是說這個進程只能使用到 20% 的 CPU 帶寬。
接下來,我們把被限制的進程的 PID 寫入 container 組里的 tasks 文件,上面的設置就會對該進程生效了:
$ echo 226 > /sys/fs/cgroup/cpu/container/tasks
可以看到,計算機的 CPU 使用率立刻降到了 20%(%Cpu0 : 20.3 us)。
除 CPU 子系統外,Cgroups 的每一項子系統都有其獨有的資源限制能力,比如:
- blkio,為塊設備設定I/O 限制,一般用於磁盤等設備;
- cpuset,為進程分配單獨的 CPU 核和對應的內存節點;
- memory,為進程設定內存使用的限制。
Linux Cgroups 的設計還是比較易用的,簡單粗暴地理解呢,它就是一個子系統目錄加上一組資源限制文件的組合。而對於 Docker 等 Linux 容器項目來說,它們只需要在每個子系統下面,為每個容器創建一個控制組(即創建一個新目錄),然后在啟動容器進程之后,把這個進程的 PID 填寫到對應控制組的 tasks 文件中就可以了。
當然這些如果使用Kubernetes 這些都會幫咱們做了
Mount Namespace 和 rootfs :夠為進程構建出一個完善的文件系統隔離環境
容器的rootfs由下圖所示的三部分組成:

第一部分,只讀層。
它是這個容器的 rootfs 最下面的五層,對應的正是 ubuntu:latest 鏡像的五層。可以看到,它們的掛載方式都是只讀的(ro+wh,即 readonly+whiteout,至於什么是 whiteout,我下面馬上會講到)。
這時,我們可以分別查看一下這些層的內容:
$ ls /var/lib/docker/aufs/diff/72b0744e06247c7d0... etc sbin usr var $ ls /var/lib/docker/aufs/diff/32e8e20064858c0f2... run $ ls /var/lib/docker/aufs/diff/a524a729adadedb900... bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
可以看到,這些層,都以增量的方式分別包含了 Ubuntu 操作系統的一部分。
第二部分,可讀寫層。
它是這個容器的 rootfs 最上面的一層(6e3be5d2ecccae7cc),它的掛載方式為:rw,即 read write。在沒有寫入文件之前,這個目錄是空的。而一旦在容器里做了寫操作,你修改產生的內容就會以增量的方式出現在這個層中。
可是,你有沒有想到這樣一個問題:如果我現在要做的,是刪除只讀層里的一個文件呢?
為了實現這樣的刪除操作,AuFS 會在可讀寫層創建一個 whiteout 文件,把只讀層里的文件“遮擋”起來。
比如,你要刪除只讀層里一個名叫 foo 的文件,那么這個刪除操作實際上是在可讀寫層創建了一個名叫.wh.foo 的文件。這樣,當這兩個層被聯合掛載之后,foo 文件就會被.wh.foo 文件“遮擋”起來,“消失”了。這個功能,就是“ro+wh”的掛載方式,即只讀 +whiteout 的含義。我喜歡把 whiteout 形象地翻譯為:“白障”。
所以,最上面這個可讀寫層的作用,就是專門用來存放你修改 rootfs 后產生的增量,無論是增、刪、改,都發生在這里。而當我們使用完了這個被修改過的容器之后,還可以使用 docker commit 和 push 指令,保存這個被修改過的可讀寫層,並上傳到 Docker Hub 上,供其他人使用;而與此同時,原先的只讀層里的內容則不會有任何變化。這,就是增量 rootfs 的好處
第三部分,Init 層。
它是一個以“-init”結尾的層,夾在只讀層和讀寫層之間。Init 層是 Docker 項目單獨生成的一個內部層,專門用來存放 /etc/hosts、/etc/resolv.conf 等信息。
需要這樣一層的原因是,這些文件本來屬於只讀的 Ubuntu 鏡像的一部分,但是用戶往往需要在啟動容器時寫入一些指定的值比如 hostname,所以就需要在可讀寫層對它們進行修改。
可是,這些修改往往只對當前的容器有效,我們並不希望執行 docker commit 時,把這些信息連同可讀寫層一起提交掉。
所以,Docker 做法是,在修改了這些文件之后,以一個單獨的層掛載了出來。而用戶執行 docker commit 只會提交可讀寫層,所以是不包含這些內容的。
最終,這 7 個層都被聯合掛載到 /var/lib/docker/aufs/mnt 目錄下,表現為一個完整的 Ubuntu 操作系統供容器使用。
虛擬機與容器的對比圖

這幅圖的左邊,畫出了虛擬機的工作原理。其中,名為 Hypervisor 的軟件是虛擬機最主要的部分。它通過硬件虛擬化功能,模擬出了運行一個操作系統需要的各種硬件,比如 CPU、內存、I/O 設備等等。然后,它在這些虛擬的硬件上安裝了一個新的操作系統,即 Guest OS。
這樣,用戶的應用進程就可以運行在這個虛擬的機器中,它能看到的自然也只有 Guest OS 的文件和目錄,以及這個機器里的虛擬設備。這就是為什么虛擬機也能起到將不同的應用進程相互隔離的作用。
而這幅圖的右邊,則用一個名為 Docker Engine 的軟件替換了 Hypervisor。這也是為什么,很多人會把 Docker 項目稱為“輕量級”虛擬化技術的原因,實際上就是把虛擬機的概念套在了容器上。
2、k8s基本概念
Kubernetes 項目最主要的設計思想是,從更宏觀的角度,以統一的方式來定義任務之間的各種關系,並且為將來支持更多種類的關系留有余地。
Kubernetes 項目核心功能的“全景圖”。

Kubernetes 項目中,我們所推崇的使用方法是:
- 首先,通過一個“編排對象”,比如 Pod、Job、CronJob 等,來描述你試圖管理的應用;
- 然后,再為它定義一些“服務對象”,比如 Service、Secret、Horizontal Pod Autoscaler(自動水平擴展器)等。這些對象,會負責具體的平台級功能。
這種使用方法,就是所謂的“聲明式 API”。這種 API 對應的“編排對象”和“服務對象”,都是 Kubernetes 項目中的 API 對象(API Object)。
例子:
最后,我來回答一個更直接的問題:Kubernetes 項目如何啟動一個容器化任務呢?
比如,我現在已經制作好了一個 Nginx 容器鏡像,希望讓平台幫我啟動這個鏡像。並且,我要求平台幫我運行兩個完全相同的 Nginx 副本,以負載均衡的方式共同對外提供服務。
-
如果是自己 DIY 的話,可能需要啟動兩台虛擬機,分別安裝兩個 Nginx,然后使用 keepalived 為這兩個虛擬機做一個虛擬 IP。
-
而如果使用 Kubernetes 項目呢?你需要做的則是編寫如下這樣一個 YAML 文件(比如名叫 nginx-deployment.yaml):
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports:
- containerPort: 80
在上面這個 YAML 文件中,我們定義了一個 Deployment 對象,它的主體部分(spec.template 部分)是一個使用 Nginx 鏡像的 Pod,而這個 Pod 的副本數是 2(replicas=2)。
然后執行:
kubectl create -f nginx-deployment.yaml
這樣,兩個完全相同的 Nginx 容器副本就被啟動了。
重點
Kubernetes 項目所擅長的,是按照用戶的意願和整個系統的規則,完全自動化地處理好容器之間的各種關系。這種功能,就是我們經常聽到的一個概念:編排。
所以說,Kubernetes 項目的本質,是為用戶提供一個具有普遍意義的容器編排工具。
不過,更重要的是,Kubernetes 項目為用戶提供的不僅限於一個工具。它真正的價值,乃在於提供了一套基於容器構建分布式系統的基礎依賴
