1. 程式人生 > >Docker從入門到實戰(一)

Docker從入門到實戰(一)

roc serve net lin 軟件 系統調用 生命 etc before

一步一步走,寫小白都能看懂的文章,將持續更新中,敬請期待!

Docker從入門到實戰(一)

一:容器技術與Docker概念

1 什麽是容器

容器技術並不是一個全新的概念,它又稱為容器虛擬化。虛擬化技術目前主要有硬件虛擬化、半虛擬化、操作系統虛擬化等。
1.1關於虛擬化
虛擬化技術的分類與定義在不同領域有不同的理解。對於計算機領域,虛擬化技術主要分為兩大類:一類基於硬件虛擬化,另一類基於軟件虛擬化。硬件虛擬化並不多見,大都是半虛擬化與軟件結合,應用較為廣泛的則是基於軟件的虛擬化技術。
基於軟件虛擬化又可分為應用虛擬化(如Wine)和平臺虛擬化(如虛擬機),容器技術屬於操作系統虛擬化,屬於平臺虛擬化的一種。

技術分享圖片
1.2容器的定義
所謂容器,顧名思義就是來放東西的道具。在剛進入國內時,還有一段時間在討論Container這個單詞翻譯為“容器合適”,還是翻譯為“集裝箱”合適。大可把容器理解為一個沙盒,每個容器是獨立的,容器間可以相互通信。

2 容器的前世今生

如果說工業上的集裝箱是從一個箱子開始的,那麽軟件行業的容器則是從文件系統隔離開始的。
2015年微軟公司也在Windows Server上為其基於Windows的應用添加了容器支持,稱之為Windows Containers,與Windows Server 2016一同發布,通過該實現,Docker可以原生的在Windows上運行Docker容器,而不需要再啟動一個虛擬機來運行Docker(Windows上早期運行Docker需要使用Linux虛擬機)。同年,MacOS也原生支持運行Docker容器。

3 容器的原理

容器本質上是宿主機上的進程。容器技術通過namespace實現資源隔離,通過Cgroups(Google公司的Control Groups技術,2007年被合並到Linux2.6.24內核中)實現資源控制,通過rootfs實現文件系統隔離,再加上容器搜索引擎自身的特性來管理容器的生命周期。
3.1認識namespace
在分布式的環境下,容器必須要有獨立的IP、端口和路由等,自然就有了網絡隔離。同時,進程通信隔離、權限隔離等也要考慮到,因此基本上一個容器需要做到6項基本隔離。
技術分享圖片
對namespace的操作主要通過clone(),setns(),unshare()這三個系統調用來完成的。
3.1.1查看當前的namespance

root用戶:ls -l /proc/$$/ns
技術分享圖片
這裏$$指的是當前進程ID號,可以看到4026531839這樣的數字,表示當前進程指向的namespace.當兩個進程指向同一串數字時,表示他們處於同一個namespace下。
3.1.2使用clone()創建新的namespace
創建一個namespace的方法是使用clone()系統調用,它會創建一個新的進程。為了說明創建的過程,給出clone()的原型如下:
int clone (int(child_func) (void ) , void child_stack, int flags , voidarg) ;
如果調用clone()時設置了一個CLONE_NEW的標誌,一個與之對應的新的命名空間將被創建,新的進程屬於該命名空間,可以使用多個CLONE_NEW標誌的組合。
3.1.3使用一個sents()關聯一個已經存在namespace
當一個namespace沒有進程時還保持打開,這麽做是為了後續添加進程到該namesapce.而添加這個功能就是使用sents()系統調用來完成,這使得調用的進程能夠和namespace關聯,docker exec 就需要用到這個方法:
int setns (int fd, int nstyps);
fd參數指明了關聯的namespace,其指向了\proc\PID\ns目錄系一個符號連接的文件描述符。可以通過發開這些符號鏈接指向的文件或者打開一個綁定到符號鏈接的文件來獲得文件描述符。
nstype參數運行調用者檢查fd指向的命名空間的類型,如果這個參數等於數,將不會檢查,當調用者已經知道namespace的類型時這會很有用。當nstype被賦值為CLONE_NEW*的常量時,內核會檢查fd指向的namespace的類型。
要把namespace利用起來,還要使用execve()函數(或者其他的exec()函數),使得我們能夠構建一個簡單但是有用的工具,該函數可以執行用戶命令。
3.1.4使用unshare()在已有進程上進行namespace隔離
unshare()和clone()有些像,不同的地方是前者運行在原有進程上,相當於跳出原來namespace操作,Linux自帶的unshare()就是通過調用unshare()這個API來實現。
[root@VM_110_98_centos ~]# unshare --help
Usage:
unshare [options] <program> [<argument>...]

Run a program with some namespaces unshared from the parent.

Options:
-m, --mount unshare mounts namespace
-u, --uts unshare UTS namespace (hostname etc)
-i, --ipc unshare System V IPC namespace
-n, --net unshare network namespace
-p, --pid unshare pid namespace
-U, --user unshare user namespace
-f, --fork fork before launching <program>
--mount-proc[=<dir>] mount proc filesystem first (implies --mount)
-r, --map-root-user map current user to root (implies --user)
--propagation <slave|shared|private|unchanged>
modify mount propagation in mount namespace
-s, --setgroups allow|deny control the setgroups syscall in user namespaces

-h, --help display this help and exit
-V, --version output version information and exit

For more details see unshare(1).
由於docker沒有使用這個系統調用,所以不展開。
3.2 認識Cgroups
Cgroups是linux內核提供的一種可以限制、記錄、隔離進程組(process groups)所使用的物理資源(如CPU,內存,I/O等)的機制。最初由Google公司的工程師提出看,後來被整合經linux內核。
這麽說理解起來有點吃力,我們通過命令來掛載cgroupfs
提示已經掛載,這個動作一般情況下已經在linux啟動的時候做了。
技術分享圖片
在主流linux發行版下,都可以通過/etc/cgconfig.conf或者cgroup-bin的相關指令來配置Cgroups。
mount {
cpuset = /sys/fs/cgroup/cpuset;
momory = /sys/fs/cgroup/momory;
}
group cnsworder/test {
perm {
task {
uid = root;
gid = root;
}
admin {
uid = root;
gid = root;
}
}
cpu {
cpu.shares = 1000;
}
}
然後通過命令行把一個進程移動到這個Cgroups之中。
#mount -t group -o cpu cpu /sys/fs/cgroup/cpuset
#cgcreate -g cpu,momory:/cnsworder
#chown root:root /sys/fs/cgroup/cpuset/cnsworder/test/*
#chown root:root /sys/fs/cgroup/cpuset/cnsworder/test/task
#cgrun -g cpu,momory:/cnsworder/test bash
3.3容器的創建
3.3.1系統調用clone()創建新進程,擁有自己的namespace
該進程擁有自己的pid,mount,user,net,ipc和uts namespace。
#pid =clone(fun,stack,flags,clone_arg);
3.3.2將pid寫入Cgroup子系統這樣就受到Cgroups子系統控制
#echo$pid >/sys/fs/cgroup/cpu/tasks
#echo$pid >/sys/fs/cgroup/cpuset/tasks
#echo$pid >/sys/fs/cgroup/bikio/tasks
#echo$pid >/sys/fs/cgroup/memory/tasks
#echo$pid >/sys/fs/cgroup/devices/tasks
#echo$pid >/sys/fs/cgroup/feezer/tasks
3.3.3通過pivot_root系統調用,使進程進入一個新的rootfs,之後通過exec()系統調用,在新的namespace,Cgroups,rootfs中執行/bin/bash.
fun () {
pivot_root ("path_of_rootfs/", path);
exec ("/bin/bash");
}
通過上面的操作,成功的在一個容器中運行了/bin/bash。

Docker從入門到實戰(一)