《重識雲原生系列》專題索引:
- 第一章——不謀全局不足以謀一域
- 第二章計算第1節——計算虛擬化技術總述
- 第二章計算第2節——主流虛擬化技術之VMare ESXi
- 第二章計算第3節——主流虛擬化技術之Xen
- 第二章計算第4節——主流虛擬化技術之KVM
- 第二章計算第5節——商用雲主機方案
- 第二章計算第6節——裸金屬方案
- 第三章雲存儲第1節——分佈式雲存儲總述
- 第三章雲存儲第2節——SPDK方案綜述
- 第三章雲存儲第3節——Ceph統一存儲方案
- 第三章雲存儲第4節——OpenStack Swift 對象存儲方案
- 第三章雲存儲第5節——商用分佈式雲存儲方案
- 第四章雲網絡第一節——雲網絡技術發展簡述
- 第四章雲網絡4.2節——相關基礎知識準備
- 第四章雲網絡4.3節——重要網絡協議
- 第四章雲網絡4.3.1節——路由技術簡述
- 第四章雲網絡4.3.2節——VLAN技術
- 第四章雲網絡4.3.3節——RIP協議
- 第四章雲網絡4.3.4節——OSPF協議
- 第四章雲網絡4.3.5節——EIGRP協議
- 第四章雲網絡4.3.6節——IS-IS協議
- 第四章雲網絡4.3.7節——BGP協議
- 第四章雲網絡4.3.7.2節——BGP協議概述
- 第四章雲網絡4.3.7.3節——BGP協議實現原理
- 第四章雲網絡4.3.7.4節——高級特性
- 第四章雲網絡4.3.7.5節——實操
- 第四章雲網絡4.3.7.6節——MP-BGP協議
- 第四章雲網絡4.3.8節——策略路由
- 第四章雲網絡4.3.9節——Graceful Restart(平滑重啓)技術
- 第四章雲網絡4.3.10節——VXLAN技術
- 第四章雲網絡4.3.10.2節——VXLAN Overlay網絡方案設計
- 第四章雲網絡4.3.10.3節——VXLAN隧道機制
- 第四章雲網絡4.3.10.4節——VXLAN報文轉發過程
- 第四章雲網絡4.3.10.5節——VXlan組網架構
- 第四章雲網絡4.3.10.6節——VXLAN應用部署方案
- 第四章雲網絡4.4節——Spine-Leaf網絡架構
- 第四章雲網絡4.5節——大二層網絡
- 第四章雲網絡4.6節——Underlay 和 Overlay概念
- 第四章雲網絡4.7.1節——網絡虛擬化與卸載加速技術的演進簡述
- 第四章雲網絡4.7.2節——virtio網絡半虛擬化簡介
- 第四章雲網絡4.7.3節——Vhost-net方案
- 第四章雲網絡4.7.4節vhost-user方案——virtio的DPDK卸載方案
- 第四章雲網絡4.7.5節vDPA方案——virtio的半硬件虛擬化實現
- 第四章雲網絡4.7.6節——virtio-blk存儲虛擬化方案
- 第四章雲網絡4.7.8節——SR-IOV方案
- 第四章雲網絡4.7.9節——NFV
- 第四章雲網絡4.8.1節——SDN總述
- 第四章雲網絡4.8.2.1節——OpenFlow概述
- 第四章雲網絡4.8.2.2節——OpenFlow協議詳解
- 第四章雲網絡4.8.2.3節——OpenFlow運行機制
- 第四章雲網絡4.8.3.1節——Open vSwitch簡介
- 第四章雲網絡4.8.3.2節——Open vSwitch工作原理詳解
- 第四章雲網絡4.8.4節——OpenStack與SDN的集成
- 第四章雲網絡4.8.5節——OpenDayLight
- 第四章雲網絡4.8.6節——Dragonflow
1 Docker簡述
1.1 什麼是Docker
Docker是一個開源的軟件項目,讓用戶程序部署在一個相對隔離的環境運行,藉此在Linux操作系統上提供一層額外的抽象,以及操作系統層虛擬化的自動管理機制。需要額外指出的是,Docker並不等於容器(containers),Docker只是容器的一種,其他的種類的容器還有Kata container,Rocket container等等。
Docker 使用 Google 公司推出的 Go 語言 進行開發實現,基於 Linux 內核的 cgroup,namespace,以及 AUFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於操作系統層面的虛擬化技術。由於隔離的進程獨立於宿主和其它的隔離的進程,因此也稱其爲容器。最初實現是基於 LXC,從 0.7 以後開始去除 LXC,轉而使用自行開發的 libcontainer,從 1.11 開始,則進一步演進爲使用 runC 和 containerd。
Docker 在容器的基礎上,進行了進一步的封裝,從文件系統、網絡互聯到進程隔離等等,極大的簡化了容器的創建和維護。使得 Docker 技術比虛擬機技術更爲輕便、快捷。
1.2 爲什麼使用容器
如今的系統在架構上較十年前已經變得非常複雜了。以前幾乎所有的應用都採用三層架構(Presentation/Application/Data),系統部署到有限的幾臺物理服務器上(Web Server/Application Server/Database Server)。
而今天,開發人員通常使用多種服務(比如 MQ,Cache,DB)構建和組裝應用,而且應用很可能會部署到不同的環境,比如虛擬服務器,私有云和公有云。
一方面應用包含多種服務,這些服務有自己所依賴的庫和軟件包;另一方面存在多種部署環境,服務在運行時可能需要動態遷移到不同的環境中。這就產生了一個問題:如何讓每種服務能夠在所有的部署環境中順利運行?
聰明的技術人員從傳統的運輸行業找到了答案。
幾十年前,運輸業面臨着類似的問題。
每一次運輸,貨主與承運方都會擔心因貨物類型的不同而導致損失,比如幾個鐵桶錯誤地壓在了一堆香蕉上。另一方面,運輸過程中需要使用不同的交通工具也讓整個過程痛苦不堪:貨物先裝上車運到碼頭,卸貨,然後裝上船,到岸後又卸下船,再裝上火車,到達目的地,最後卸貨。一半以上的時間花費在裝、卸貨上,而且搬上搬下還容易損壞貨物。
幸運的是,集裝箱的發明解決這個難題。
打一個比方,集裝箱(容器)對於遠洋運輸(應用運行)來說十分重要。集裝箱(容器)能保護貨物(應用),讓其不會相互碰撞(應用衝突)而損壞,也能保障當一些危險貨物發生規模不大的爆炸(應用崩潰)時不會波及其它貨物(應用)但是把貨物(應用)裝載在集裝箱(容器)中並不是一件簡單的事情。而出色的碼頭工人(Docker)的出現解決了這一問題。它(Docker)使得貨物裝載到集裝箱(容器)這一過程變得輕而易舉。對於遠洋運輸(應用運行)而言,用多艘小貨輪(虛擬機)代替原來的大貨輪(實體機)也能保證貨物(應用)彼此之間的安全,但是和集裝箱(容器)比,成本過高,但適合運輸某些重要貨物(應用)。
任何貨物,無論鋼琴還是保時捷,都被放到各自的集裝箱中。集裝箱在整個運輸過程中都是密封的,只有到達最終目的地才被打開。標準集裝箱可以被高效地裝卸、重疊和長途運輸。現代化的起重機可以自動在卡車、輪船和火車之間移動集裝箱。集裝箱被譽爲運輸業與世界貿易最重要的發明。
Docker 將集裝箱思想運用到軟件打包上,爲代碼提供了一個基於容器的標準化運輸系統。Docker 可以將任何應用及其依賴打包成一個輕量級、可移植、自包含的容器。容器可以運行在幾乎所有的操作系統上。
其實,“集裝箱” 和 “容器” 對應的英文單詞都是 “Container”。
“容器” 是國內約定俗成的叫法,可能是因爲容器比集裝箱更抽象,更適合軟件領域的原故吧。
1.3 Docker的優勢
- 更高效的利用系統資源
由於容器不需要進行硬件虛擬以及運行完整操作系統等額外開銷,Docker 對系統資源的利用率更高。無論是應用執行速度、內存損耗或者文件存儲速度,都要比傳統虛擬機技術更高效。因此,相比虛擬機技術,一個相同配置的主機,往往可以運行更多數量的應用。
- 更快速的啓動時間
傳統的虛擬機技術啓動應用服務往往需要數分鐘,而 Docker 容器應用,由於直接運行於宿主內核,無需啓動完整的操作系統,因此可以做到秒級、甚至毫秒級的啓動時間。大大的節約了開發、測試、部署的時間。
- 一致的運行環境
開發過程中一個常見的問題是環境一致性問題。由於開發環境、測試環境、生產環境不一致,導致有些 bug 並未在開發過程中被發現。而 Docker 的鏡像提供了除內核外完整的運行時環境,確保了應用運行環境一致性,從而不會再出現 “這段代碼在我機器上沒問題啊” 這類問題。
- 持續交付和部署
對開發和運維(DevOps)人員來說,最希望的就是一次創建或配置,可以在任意地方正常運行。
使用 Docker 可以通過定製應用鏡像來實現持續集成、持續交付、部署。開發人員可以通過 Dockerfile 來進行鏡像構建,並結合 持續集成(Continuous Integration) 系統進行集成測試,而運維人員則可以直接在生產環境中快速部署該鏡像,甚至結合 持續部署(Continuous Delivery/Deployment) 系統進行自動部署。
而且使用 Dockerfile 使鏡像構建透明化,不僅僅開發團隊可以理解應用運行環境,也方便運維團隊理解應用運行所需條件,幫助更好的生產環境中部署該鏡像。
- 更輕鬆的遷移
由於 Docker 確保了執行環境的一致性,使得應用的遷移更加容易。Docker 可以在很多平臺上運行,無論是物理機、虛擬機、公有云、私有云,甚至是筆記本,其運行結果是一致的。因此用戶可以很輕易的將在一個平臺上運行的應用,遷移到另一個平臺上,而不用擔心運行環境的變化導致應用無法正常運行的情況。
- 更輕鬆的維護和擴展
Docker 使用的分層存儲以及鏡像的技術,使得應用重複部分的複用更爲容易,也使得應用的維護更新更加簡單,基於基礎鏡像進一步擴展鏡像也變得非常簡單。此外,Docker 團隊同各個開源項目團隊一起維護了一大批高質量的官方鏡像,既可以直接在生產環境使用,又可以作爲基礎進一步定製,大大的降低了應用服務的鏡像製作成本。
1.4 Docker核心概念
1.4.1 鏡像(Image)
Docker鏡像(Image)類似於虛擬機的鏡像,可以將他理解爲一個面向Docker引擎的只讀模板,包含了文件系統。例如:一個鏡像可以完全包含了Ubuntu操作系統環境,可以把它稱作一個Ubuntu鏡像。鏡像也可以安裝了Apache應用程序(或其他軟件),可以把它稱爲一個Apache鏡像。
鏡像是創建Docker容器的基礎,通過版本管理和增量的文件系統,Docker提供了一套十分簡單的機制來創建和更新現有的鏡像。用戶可以從網上下載一個已經做好的應用鏡像,並通過命令直接使用。總之,應用運行是需要環境的,而鏡像就是來提供這種環境。
1.4.2 容器(Container)
Docker容器(Container)類似於一個輕量級的沙箱(因爲Docker是基於Linux內核的虛擬技術,所以消耗資源十分少),Docker利用容器來運行和隔離應用。容器是從鏡像創建的應用運行實例,可以將其啓動、開始、停止、刪除,而這些容器都是相互隔離、互不可見的。可以把每個容器看作一個簡易版的Linux系統環境(包括了root用戶權限、進程空間、用戶空間和網絡空間),以及與運行在其中的應用程序打包而成的應用盒子。鏡像自身是隻讀的。容器從鏡像啓動的時候,Docker會在鏡像的最上層創建一個可寫層,鏡像本身將保持不變。就像用ISO裝系統之後,ISO並沒有什麼變化一樣。
1.4.3 倉庫(Repository)
Docker倉庫(Repository)類似於代碼倉庫,是Docker集中存放鏡像文件的場所。有時候會看到有資料將Docker倉庫和註冊服務器(Registry)混爲一談,並不嚴格區分。實際上,註冊服務器是存放倉庫的地方,其上往往存放着多個倉庫。每個倉庫集中存放某一類鏡像,往往包括多個鏡像文件,通過不同的標籤(tag)來進行區分。例如存放Ubuntu操作系統鏡像的倉庫,稱爲Ubuntu倉庫,其中可能包括14.04,12.04等不同版本的鏡像。根據存儲的鏡像公開分享與否,Docker倉庫分爲公開倉庫(Public)和私有倉庫(Private)兩種形式。
目前,最大的公開倉庫是Docker Hub,存放了數量龐大的鏡像供用戶下載。國內的公開倉庫包括Docker Pool等,可以提供穩定的國內訪問。如果用戶不希望公開分享自己的鏡像文件,Docker也支持用戶在本地網絡內創建一個只能自己訪問的私有倉庫。當用戶創建了自己的鏡像之後就可以使用push明亮將它上傳到指定的公有或則私有倉庫。這樣用戶下次在另一臺機器上使用該鏡像時,只需將其從倉庫pull下來就可以了。Docker利用倉庫管理鏡像的設計理念甚至命令和git非常相似,也就意味着非常好上手。
1.5 Docker VS 虛擬機
從上面這幅圖就可以看出,虛擬機是正兒八經的存在一層硬件虛擬層,模擬出了運行一個操作系統需要的各種硬件,例如CPU,MEM,IO等設備。然後在虛擬的硬件上安裝了一個新的操作系統Guest OS。所以在Windows宿主機上面可以跑Linux虛擬機。因爲多了一層虛擬,所以虛擬機的性能必然會有所損耗。Docker容器是由Docker Deamon(Docker Deamon是運行在宿主機上面的一個後臺進程,負責拉起和設置容器)拉起的一個個進程,通過Docker Deamon設置完Namespace和Cgroup之後,本質上就是一個運行在宿主機上面的進程。因爲沒有一層虛擬的Guest OS,所以Docker輕量級很多。但是有利就有弊,由於Docker 容器直接運行在宿主機上面,安全性就相對較差些,另外也沒有辦法在Windows上面運行Linux的容器,如果容器裏面的應用對特定系統內核有要求也不能運行在不滿足要求的宿主機上面。
對比傳統虛擬機總結
1.6 使用場景
Docker通常用於如下場景:
- web應用的自動化打包和發佈;
- 自動化測試和持續集成、發佈;
- 在服務型環境中部署和調整數據庫或其他的後臺應用;
- 從頭編譯或者擴展現有的OpenShift或Cloud Foundry平臺來搭建自己的PaaS環境。
2 基本原理
Docker利用Linux中的核心分離機制,例如Cgroups,以及Linux的核心Namespace(名字空間)來創建獨立的容器。一句話概括起來Docker就是利用Namespace做資源隔離,用Cgroup做資源限制,利用Union FS做容器文件系統的輕量級虛擬化技術。Docker容器的本質還是一個直接運行在宿主機上面的特殊進程,看到的文件系統是隔離後的,但是操作系統內核是共享宿主機OS,所以說Docker是輕量級的虛擬化技術。
2.1 Namespace
Linux Namespace 是Linux 提供的一種內核級別環境隔離的方法,使其中的進程好像擁有獨立的操作系統環境。Linux Namespace 有 Mount Namespace,UTS Namespace, IPC Namespace, PID Namespace, Network Namespace, User Namespace, Cgroup Namespace。詳情看下錶:
分類 |
系統調用參數 |
隔離內容 |
內核版本 |
Mount Namespace |
CLONE_NEWNS |
文件系統掛載點 |
Linux 2.4.19(2002年) |
UTS Namespace |
CLONE_NEWUTS |
Hostname和domain name |
Linux 2.6.19 |
IPC Namespace |
CLONE_NEWIPC |
進程間通信方式,例如消息隊列 |
Linux 2.6.19 |
PID Namespace |
CLONE_NEWPID |
進程ID編號 |
Linux 2.6.24 |
Network Namespace, |
CLONE_NEWNET |
網絡設備,協議棧,路由表,防火牆規則,端口等 |
Linux 2.6.24 start Linux 2.6.29 end |
User Namespace |
CLONE_NEWUSER |
用戶及組ID |
Linux 2.6.23 start Linux 3.8 end |
Cgroup Namespace |
CLONE_NEWCGROUP |
Cgroup根目錄 |
Linux 4.6 |
上述系統調用參數CLONE_NEWNS等主要應用於以下三個系統調用:
- clone 創建新進程並設置它的Namespace,類似於fork系統調用,可創建新進程並且指定子進程將要執行的函數,通過上述CLONE_NEWNS等參數使某類資源處於隔離狀態。
函數聲明 :
#include int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
例如:
int pid = clone(call_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
會讓新創建的該進程執行call_function,例如/bin/bash,且該進程的PID進程編號是隔離狀態,也就是新的PID編號,該進程ps將會看到它的PID是1。
如果多次執行上述clone就會創建多個PID Namespace,而每個Namespace裏面的應用進程都認爲自己是當前容器裏的1號進程,它們既看不到宿主機裏的真實進程空間,也看不到其他PID Namespace裏面的具體情況。
- int unshare(int flags) 使進程脫離某個Namespace,flags參數和clone的用法一致。
- int setns(int fd, int nstype) 使進程進入某個已經存在的Namespace。經常用於從宿主機進入已經啓動的容器Network Namespace,然後設置它的網絡。
2.2 Cgroup
上面已經講過Docker 容器運行起來是一個直接運行在宿主機上面的進程,那麼如果限定每個容器最多消耗多少CPU資源呢?如果一個容器瘋狂的消耗資源豈不是會影響同一宿主機上面其他的容器?所以Docker就需要一個限制容器能夠使用資源上限的機制,那就是Linux Cgroup技術。Linux Cgroup 全稱是Linux Control Group。它最主要的作用是限制一個進程組能夠使用的資源上限,包括CPU,MEM,DISK,NET等等。
下面我將演示一個利用Cgroup限制進程CPU的例子:
[root@nginx-1 /sys/fs/cgroup/cpu]# ll
total 0
-rw-r--r-- 1 root root 0 Sep 26 2018 cgroup.clone_children
--w--w--w- 1 root root 0 Sep 26 2018 cgroup.event_control
-rw-r--r-- 1 root root 0 Sep 26 2018 cgroup.procs
-r--r--r-- 1 root root 0 Sep 26 2018 cgroup.sane_behavior
-r--r--r-- 1 root root 0 Sep 26 2018 cpuacct.stat
-rw-r--r-- 1 root root 0 Sep 26 2018 cpuacct.usage
-r--r--r-- 1 root root 0 Sep 26 2018 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_relax_thresh_sec
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Sep 26 2018 cpu.shares
-r--r--r-- 1 root root 0 Sep 26 2018 cpu.stat
drwxr-xr-x 9 root root 0 Jun 6 17:03 docker
-rw-r--r-- 1 root root 0 Sep 26 2018 notify_on_release
-rw-r--r-- 1 root root 0 Sep 26 2018 release_agent
-rw-r--r-- 1 root root 0 Sep 26 2018 tasks
[root@nginx-1 /sys/fs/cgroup/cpu]# mkdir mytest #創建mytest目錄,系統會自動添加以下文件
[root@nginx-1 /sys/fs/cgroup/cpu/mytest]# ll
total 0
-rw-r--r-- 1 root root 0 Jun 13 16:55 cgroup.clone_children
--w--w--w- 1 root root 0 Jun 13 16:55 cgroup.event_control
-rw-r--r-- 1 root root 0 Jun 13 16:55 cgroup.procs
-r--r--r-- 1 root root 0 Jun 13 16:55 cpuacct.stat
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpuacct.usage
-r--r--r-- 1 root root 0 Jun 13 16:55 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_relax_thresh_sec
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.shares
-r--r--r-- 1 root root 0 Jun 13 16:55 cpu.stat
-rw-r--r-- 1 root root 0 Jun 13 16:55 notify_on_release
-rw-r--r-- 1 root root 0 Jun 13 16:55 tasks
[root@nginx-1 /sys/fs/cgroup/cpu/mytest]# while : ; do : ; done & # 運行一個死循環命令
[1] 2459615
op觀察會發現該進程CPU跑到了100%,符合預期
主要的限制參數來源自文件cpu.cfs_quota_us,默認是-1,不做限制,如果改成20000說明限定20%的CPU上限。因爲總量存在於cpu.cfs_period_us,是100000,意思cpu時間總量是100000us,20000/100000=20%。然後將bash命令的PID寫到tasks文件中,改完之後的CPU佔用是20%,符合預期:
同理可限制MEM,DISK和NET,需要特殊指出的是MEM是硬限制,當容器的內存使用量超過了Cgroup限定值會被系統OOM。
2.3 Union FS
每個容器運行起來後都有一個獨立的文件系統,例如Ubuntu鏡像的容器能夠看到Ubuntu的文件系統,Centos能夠看到Centos的文件系統, 不是說容器是運行在宿主機上面的進程嗎,爲什麼能夠看到和宿主機不一樣的文件系統呢?那就要歸功於Union FS,全稱是Union File System,聯合文件系統。將多個不同位置的目錄聯合掛載到同一個目錄,將相同的部分合並。Docker利用這種聯合掛載能力,將容器鏡像裏面的多層內容呈現爲統一的rootfs(根文件系統),即root用戶能夠看到的根目錄底下所有的目錄文件。rootfs打包了整個操作系統的文件和目錄,是應用運行時所需要的最完整的“依賴庫”,也就是我們說的“鏡像”。
鏡像分爲基礎鏡像只讀層,和Init層,和讀寫層。
Init 層存放的是/etc/hostname,/etc/resolv.conf 等, docker commit的時候不提交。
讀寫層一開始的時候爲空,用戶如果修改了文件系統,比如說增刪改了文件,docker commit的時候就會提交這一層信息。
3 總結
Docker 容器總結起來就是利用Linux Namespace做資源隔離,Cgroup做資源上限限制,rootfs做文件系統 運行在宿主機上面的一個特殊進程。
參考鏈接
開始學習Docker啦--容器理論知識(一) - 小水滴18 - 博客園
Docker與Kubernetes系列(一): Docker的基本概念_沈鴻斌的博客-CSDN博客_docker kubernet
Docker與Kubernetes系列(二): Docker的基本用法_沈鴻斌的博客-CSDN博客
docker容器技術入門知識及思維導圖_adorable_的博客-CSDN博客_docker學習思維導圖
最新文章歡迎關注筆者公衆號“暢遊雲海”