標題:Docker 核心技術

taibeihacker

Moderator

Docker 核心技术​

1 背景​

Docker 是一個開源的應用容器引擎,基於Go 語言開發。 Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後發佈到任何流行的Linux 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何接口,更重要的是容器性能開銷極低。
基於Linux 內核的Cgroup,Namespace,以及Union FS 等技術,對進程進行封裝隔離,屬於操作系統層面的虛擬化技術,由於隔離的進程獨立於宿主和其它的隔離的進程,因此也稱其為容器。
最初實現是基於LXC,從0.7 以後開始去除LXC,轉而使用自行開發的Libcontainer,從1.11 開始,則進一步演進為使用runC 和Containerd。
Docker 在容器的基礎上,進行了進一步的封裝,從文件系統、網絡互聯到進程隔離等等,極大的簡化了容器的創建和維護,使得Docker 技術比虛擬機技術更為輕便、快捷。

1.1 Docker 与虚拟机的区别​

202110212029044.png-water_print

Docker 有著比虛擬機更少的抽象層。由於docker 不需要Hypervisor 實現硬件資源虛擬化,運行在docker 容器上的程序直接使用的都是實際物理機的硬件資源。因此在CPU、內存利用率上docker 將會在效率上有優勢。
Docker 利用的是宿主機的內核,而不需要Guest OS。因此,當新建一個容器時,docker 不需要和虛擬機一樣重新加載一個操作系統內核。引導、加載操作系統內核是一個比較費時費資源的過程,當新建一個虛擬機時,虛擬機軟件需要加載Guest OS,這個新建過程是分鐘級別的。而docker 由於直接利用宿主機的操作系統,則省略了這個過程,因此新建一個docker 容器只需要幾秒鐘。另外,現代操作系統是複雜的系統,在一台物理機上新增加一個操作系統的資源開銷是比較大的,因此,docker 對比虛擬機在資源消耗上也佔有比較大的優勢。事實上,在一台物理機上我們可以很容易建立成百上千的容器,而只能建立幾個虛擬機。
性能對比
202110212030650.png-water_print

容器主要特性
202110212041255.png-water_print

1.2 容器操作​

啟動:docker run
-it 交互
-d 後台運行
-p 端口映射
-v 磁盤掛載
啟動已終止容器
docker start
停止容器
docker stop
查看容器進程
docker ps
查看容器細節
docker inspect [containerid]
進入容器
docker attach
PID=$(docker inspect --format '{{ .State.Pid }}' [container])
nsenter --target $PID --mount --uts --ipc --net --pid

1.3 Dockerfile​

cat Dockerfile
1
2
3
FROM ubuntu
ENV MY_SERVICE_PORT=80
ADD bin/amd64/httpserver /httpserver ENTRYPOINT /httpserver
將Dockerfile 打包成鏡像
1
2
docker build -t cncamp/httpserver:${tag} .
docker push geekby/httpserver:v1.0
運行容器
1
docker run -d geekby/httpserver:v1.0

1.4 Docker 引擎架构​

202110221536913.png-water_print

2 Namespcce​

2.1 背景​

Linux Namespace 是一種Linux Kernel 提供的資源隔離方案。
系統可以為進程分配不同的Namespace
並保證不同的Namespace 資源獨立分配、進程彼此隔離,即不同的Namespace 下的進程互不干擾
Linux 內核代碼中Namespace 的實現如下:
202110212043342.png-water_print

2.2 类别​

202110212044915.png-water_print

2.2.1 进程编号 - PID​

當啟動了多個container,然後在每個container 內部用ps 命令看一下,會發現它們都有一個PID 為1 的進程。不同用戶的進程就是通過PID namespace 隔離開的,且不同的namespace 中可以有相同的PID。有了PID namespace,每個namespace 中的PID 能夠相互隔離。

2.2.2 网络设备 - Network​

在每個network space 內部,可以有獨立的網絡設備(虛擬的或者真實的)、IP 地址、路由表和防火牆規則等。其中的應用所bind 的端口也是per-namespace 的,比如http 默認使用的是80 端口,使用network space 後,同一host 上的各個container 內部就都可以運行各自的web server。
Docker 默認採用veth 的方式將container 中的虛擬網卡同host 上的一個docker bridge: docker0 連接在一起。

2.2.3 进程间通信 - IPC​

利用這個space,進程間的通信(InterProcessCommunication)就被限定在了同一個space 內部,即一個container 中的某個進程只能和同一container 中的其他進程通信。
container 的進程間交互實際上還是host 上具有相同Pid namespace 中的進程間交互,因此需要在IPC 資源申請時加入namespace 信息- 每個IPC 資源有一個唯一的32 位ID。

2.2.4 文件系统 - Mount​

mnt namespace 允許不同namespace 的進程看到的文件結構不同,這樣每個namespace 中的進程所看到的文件目錄就被隔離開了。

2.2.5 主机域名 - UTS​

由於多個container 是共享OS 內核的,因而像UTS 裡的os type 和os release 等信息是不可能更改的,但是每個container 可以有自己獨立的host name 和domain name,以便於標識和區分(比如可以通過主機名來訪問網絡中的機器),這就是UTS namespace 的作用。

2.2.6 用户控制 - USR​

每個container 可以有不同的user 和group id, 也就是說可以在container 內部用container 內部的用戶執行程序而非Host 上的用戶。

2.3 linux 对 namespace 的操作方法​

clone
在創建新進程的系統調用時,可以通過flags 參數指定需要新建的Namespace 類型。
clone 函數原型:
1
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
sets
該系統調用可以讓調用進程加入某個已經存在的Namespace 中:
1
int setns(int fd, int nstype)
unshare
該系統調用可以將調用進程移動到新的Namespace 下:
1
int unshare(int flags)

2.4 关于 namespace 的常用操作​

查看當前系統的namespace:
lsns -t [type]
202110212104299.png-water_print

查看進程的namespace:
ls -la /proc/[pid]/ns/
202110212109003.png-water_print

進入某namespace 運行命令:
nsenter -t [pid] -n ip addr
202110212108750.png-water_print

3 Cgroup​

3.1 背景​

Cgroups (Control Groups)是Linux 下用於對一個或一組進程進行資源控制和監控的機制
可以對諸如CPU 使用時間、內存、磁盤I/O 等進程所需的資源進行限制;
不同資源的具體管理工作由相應的Cgroup 子系統(Subsystem)來實現;
針對不同類型的資源限制,只要將限制策略在不同的的子系統上進行關聯即可;
Cgroups 在不同的系統資源管理子系統中以層級樹(Hierarchy)的方式來組織管理:每個Cgroup 都可以包含其他的子Cgroup,因此子Cgroup 能使用的資源除了受本Cgroup 配置的資源參數限制,還受到父Cgroup 設置的資源限制。
Linux 內核代碼中Cgroups 的實現:
202110220926427.png-water_print

3.2 类别​

cgroups 實現了對資源的配額和度量
blkio:這個子系統設置限制每個塊設備的輸入輸出控制。例如:磁盤,光盤以及USB 等等
cpu:這個子系統使用調度程序為cgroup 任務提供CPU 的訪問
cpuacct:產生cgroup 任務的CPU 資源報告
cpuset:如果是多核心的CPU,這個子系統會為cgroup 任務分配單獨的CPU 和內存
devices:允許或拒絕cgroup 任務對設備的訪問
freezer:暫停和恢復cgroup 任務
memory:設置每個cgroup 的內存限制以及產生內存資源報告
net_cls:標記每個網絡包以供cgroup 方便使用
ns:名稱空間子系統
pid:進程標識子系統

3.2.1 cpu 子系统​

cpu.shares:可出讓的能獲得CPU 使用時間的相對值。
cpu.cfs_period_us:用來配置時間週期長度,單位為us(微秒)。
cpu.cfs_quota_us:用來配置當前Cgroup 在cfs_period_us 時間內最多能使用的CPU 時間數,單位為us(微秒)。
cpu.stat:Cgroup 內的進程使用的CPU 時間統計。
nr_periods:經過cpu.cfs_period_us 的時間週期數量。
nr_throttled:在經過的周期內,有多少次因為進程在指定的時間週期內用光了配額時間而受到限制。
throttled_time:Cgroup 中的進程被限制使用CPU 的總用時,單位是ns(納秒)。
示例
利用cgroup cpu 子系統控制資源訪問
在cgroup cpu 子系統目錄中創建目錄結構:
1
2
3
cd /sys/fs/cgroup/cpu
mkdir cpudemo
cd cpudemo
運行如下代碼:
1
2
3
4
5
6
7
8
9
package main
func main() {
go func(){
for {
}
}()
for {
}
}
執行top 查看CPU 使用情況,CPU 佔用200%:
202110221100824.png-water_print

再通過cgroup 限制cpu:
1
2
3
4
5
6
7
cd /sys/fs/cgroup/cpu/cpudemo
# 把進程添加到cgroup 進程配置組
echo ps -ef|grep poc|grep -v grep|awk '{print $2}' cgroup.procs
# 設置cpuquota
echo 10000 cpu.cfs_quota_us
202110221104034.png-water_print

執行top 查看CPU 使用情況,CPU 佔用變為10%:
202110221104202.png-water_print

刪除分組:
1
cgdelete -g cpu:cpudemo

3.2.2 Memory 子系统​

memory.usage_in_bytes:
cgroup 下進程使用的內存,包含cgroup 及其子cgroup 下的進程使用的內存
memory.max_usage_in_bytes:
cgroup 下進程使用內存的最大值,包含子cgroup 的內存使用量。
memory.limit_in_bytes
設置Cgroup 下進程最多能使用的內存。如果設置為-1,表示對該cgroup 的內存使用不做限制。
memory.oom_control
設置是否在Cgroup 中使用OOM(Out of Memory)Killer,默認為使用。當屬於該cgroup 的進程使用的內存超過最大的限定值時,會立刻被OOM Killer 處理。

4 文件系统​

4.1 背景​

docker 的創新點:Union FS 運用到文件系統中
將不同目錄掛載到同一個虛擬文件系統下(unite several directories into a single virtual filesystem) 的文件系統
支持為每一個成員目錄(類似Git Branch)設定readonly、readwrite 和whiteoutable 權限
文件系統分層, 對readonly 權限的branch 可以邏輯上進行修改(增量地, 不影響readonly 部分的)
通常Union FS 有兩個用途, 一方面可以將多個disk 掛到同一個目錄下,另一個更常用的就是將一個readonly 的branch 和一個writeable 的branch 聯合在一起。
202110221445172.png-water_print

4.2 Docker 的文件系统​

典型的Linux 文件系統組成:
Bootfs(boot file system)
Bootloader - 引導加載kernel,
Kernel - 當kernel 被加載到內存中後umount bootfs。
rootfs (root file system)
/dev,/proc,/bin,/etc 等標準目錄和文件。
對於不同的linux 發行版,bootfs 基本是一致的, 但rootfs 會有差別。
202110221453388.png-water_print

4.2.1 启动过程的差异​

Linux 的启动:在啟動後,首先將rootfs 設置為readonly,進行一系列檢查,然後將其切換為readwrite供用戶使用。
Docker 的启动:初始化時也是將rootfs 以readonly 方式加載並檢查,然而接下來利用union mount 的方式將一個readwrite 文件系統掛載在readonly 的rootfs 之上。並且允許再次將下層的FS 設定為readonly 並向上疊加。這樣一組readonly 和一個writeable 的結構構成了一個container 的運行時態,每一個FS 被稱作為一個FS 層。

4.2.3 关于写操作​

由於鏡像具有共享特性,所以對容器可寫層的操作需要依賴存儲驅動提供的寫時復制和用時分配機制,以此來支持對容器可寫層的修改,進而提高對存儲和內存資源的利用率。
寫時復制
寫時復制,即Copy-on-Write。一個鏡像可以被多個容器使用,但是不需要在內存和磁盤上做多個拷貝。在需要對鏡像提供的文件進行修改時,該文件會從鏡像的文件系統被複製到容器的可寫層的文件系統進行修改, 而鏡像裡面的文件不會改變。不同容器對文件的修改都相互獨立、互不影響。
用時分配
按需分配空間,而非提前分配,即當一個文件被創建出來後,才會分配空間。

4.2.4 容器存储驱动​

202110221506732.png-water_print

202110221506663.png-water_print

2.4.5 OverlayFS​

OverlayFS 也是一種與AUFS 類似的聯合文件系統,同樣屬於文件級的存儲驅動,包含了最初的Overlay 和更新更穩定的overlay2。
Overlay 只有兩層:upper 層和lower 層,Lower 層代錶鏡像層,upper 層代表容器可寫層。
202110221507598.png-water_print

示例
1
2
3
4
5
6
7
8
9
10
11
12
mkdir upper lower merged work
echo 'from lower' lower/in_lower.txt
echo 'from upper' upper/in_upper.txt
echo 'from lower' lower/in_both.txt
echo 'from upper' upper/in_both.txt
sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
cat merged/in_both.txt
rm merged/in_both.txt
rm merged/in_lower.txt
rm merged/in_upper.txt

5 网络​

在Docker 中提供了多種網
 
返回
上方