docker——核心實現技術

做爲一種容器虛擬化技術,Docker深度應用了操做系統的多項底層支持技術。

早期版本的Docker是基於已經成熟的Linux Container(LXC)技術實現的。
自從0.9版本起,Docker逐漸從LXC轉移到新的libcontainer上,
並積極推進開放容器規則runc,試圖打造更通用的底層容器虛擬化庫。

從操做系統功能上看,目前Docker底層依賴的核心技術主要包括:
Linux操做系統的命令空間(Namesspace)控制組(Control Group)聯合文件系統(Union File System)和Linux網絡虛擬化支持

git

1、基本架構

Docker目前採用了標準的C/S架構。
客戶端和服務端既能夠在同一臺機器上,也能夠運行在不一樣機器上經過socket或RESTful API來進行通信。程序員

1.服務端

Docker Daemon通常在宿主主機後臺運行,做爲服務端接收來自客戶的請求,並處理這些請求(建立、運行、分發容器)。
在設計上,Docker Daemon是一個模塊化的架構,經過專門的Engine模塊來分發管理各個來自客戶端的任務。
Docker服務端默認監聽本地的unix:///var/run/docker.sock套接字,只容許本地的root用戶或docker用戶組成員訪問。
能夠經過-H選項來修改監聽的方式。例如,讓服務端監聽本地的TCP鏈接1234端口:docker daemon -H 0.0.0.0:1234
此外Docker還支持HTTPS認證方式來驗證訪問。

docker

2.服務端

Docker客戶端爲用戶提供了一系列可執行命令,用戶用這些命令與Docker Daemon交互。
用戶使用的Docker可執行命令即爲客戶端程序。
與Docker Daemon不一樣的是,客戶端發送命令後等待服務端返回,一旦受到返回後,客戶端當即執行結束並退出。
用戶執行新的命令,須要再次調用客戶端命令。

一樣,客戶端默認經過本地的unix:///var/run/docker/sock套接字向服務端發送命令。
若是服務端沒有監聽默認地址,則須要客戶端在執行命令的時候顯式指定服務端地址。

使用docker -H tcp://127.0.0.1:1234指定接收地址。

咱們通常使用的時候,docker的客戶端和服務端通常都在一臺機器上,即便要要遠程操控也是經過SSH鏈接宿主機。後端

 

3.新的架構設計

在使用Docker的時候,必需要保證Docker Daemon的正常運行,它既要管理容器的運行,又要負責對外部API進行響應。
而一旦Docker Daemon服務異常,則運行在Docker主機上的容器每每都沒法使用。

在較新的版本中,開始將維護容器運行的任務放到一個單獨的組件containerd中來管理,並支持OCI的runc規範。
原先對客戶端的API支持仍然放在Docker Daemon,經過解耦,大大減小了對Docker Daemon的依賴

新的架構提升了容器的啓動速度,測試代表,能夠達到每秒啓動100個容器。緩存

 

 

2、命名空間

命名空間(namespace)是Linux內核的一個強大特徵,爲容器虛擬化的實現帶來了極大的便利。
利用這一特徵,每一個容器均可以擁有本身單獨的命名空間,運行在其中的應用都像是在獨立的操做系統環境中同樣。
命令空間機制保證了容器彼此之間互不影響。

在操做系統中,包括內核、文件系統、網絡、PID、UID、IPC、內存、硬盤、CPU等資源,全部的資源都是應用進程共享的。
要想實現虛擬化,除了要實現對內存、CPU、網絡IO、硬盤IO、存儲空間等的限制外,還要實現文件系統、網絡、PID、UID、IPC等的相互隔離。
前者相對容易實現一些,後者則須要宿主機系統的深刻支持。安全

1.進程的命名空間

Linux經過命名空間管理進程號,對於同一個進程(即同一個task_struct),在不一樣的命名空間中,
看到的進程號不相同,每一個進程命名空間有一套本身的進程號管理方法。
進程命名空間是一個父子關係的結構,子空間中的進程對父空間是可見的。
新fork出的進程在父命名空間和子命名空間將分別有一個進程號來對應。網絡

 

2.網絡命名空間

若是有了PID命名空間,那麼每一個命名空間中的進程就能夠相互隔離,可是網絡端口仍是共享本地系統的端口。
經過網絡命名空間,能夠實現網絡隔離。網絡命名空間爲進程提供了一個徹底獨立的網絡協議棧視圖,
包括網絡設備接口、IPv4和IPv6協議棧、IP路由表、防火牆規則、sockets等,這樣每一個容器的網絡就能隔離開來。
Docker採用虛擬網絡設備的方式,將不一樣命名空間的網絡設備鏈接到一塊兒。
默認狀況下,容器中的虛擬網卡將同本地主機上的docker0網橋鏈接到一塊兒。架構

 

3.IPC命名空間

容器中進程交互仍是採用了Linux常見的進程間交互方法(Interprocess Communication)IPC,包括信號量、消息隊列和共享內存等。
PID Namespace和IPC Namespace能夠組合起來一塊兒使用,同一個IPC命名空間內的進程能夠彼此可見,容許進行交互,不一樣空間的進程則沒法交互。app

 

4.掛載命名空間

相似於chroot,將一個進程放到一個特定的目錄執行。
掛載命名空間容許不一樣命名空間的進程看到的文件結構不一樣,這樣每一個命名空間中進程所看到的文件目錄彼此被隔離。socket

 

5.UTS命令空間

UST(UNIX Time-sharing System)命名空間容許獨立的主機名和域名,
從而能夠虛擬出一個獨立主機名和網絡空間環境,就跟網絡上一臺獨立的主機同樣。

 

6.用戶命名空間

每一個容器能夠有不一樣的用戶和組ID,也就是說能夠在容器內使用特定的內部用戶執行程序,而非本地系統上存在的用戶。
每一個容器內部均可以有root帳號,但跟宿主機不在一個命名空間。
經過使用隔離的用戶命名空間能夠提升安全性,避免容器內進程獲取到額外的權限。

 

 

3、控制組

控制組(CGroups)是Linux的一個特徵,主要用來對共享資源進行隔離、限制、審計等。
只有能控制分配到容器的資源,才能避免多個容器同時運行時對宿主機系統的資源競爭。

控制組技術最先是由Google的程序員2006年提出的,Linux內核自2.6.2開始原生支持。
控制組能夠提供對容器的內存、CPU、磁盤IO等資源進行限制和計費模式
控制組的應用目標時爲不一樣的應用狀況提供統一的接口,從控制單一進程(好比nice工具)到系統級(OpenVZ、LXC)的虛擬化。
能夠在建立或啓動容器的時候爲每一個容器指定資源限制。

具體來看,控制組提供:
  資源限制(Redource limiting):能夠將組設置爲不超過設定的內存限制。
  優先級(Prioritization):經過優先級讓一些組優先獲得更多的CPU等資源。
  資源審計(Accounting):用來統計系統實際上把多少資源用到適合的目的上,可使用cpuacct子系統記錄某個進程組使用的CPU時間。
  隔離(isolation):爲組隔離命名空間,這樣一個組不會看到另外一個組的進程、網絡鏈接和文件系統。
  控制(Control):掛起、恢復和重啓等操做。

 

 

4、聯合文件系統

聯合文件系統(UnionFS)是一種輕量級的高性能分層文件系統,它支持將文件系統中的修改信息做爲一次提交,
並層層疊加,同時能夠將不一樣目錄掛載到同一個虛擬文件系統下,應用看到的是掛載的最終結果。

聯合文件系統是實現Docker鏡像的技術基礎。Docker鏡像能夠經過分層來進行繼承。
例如,用戶基於基礎鏡像來製做各類不一樣的應用鏡像。這些鏡像共享一個基礎鏡像層,提升了存儲效率。
此外,當用戶改變了一個Docker鏡像(好比升級程序到新的版本),則會建立一個新的層(layer)。
所以,用戶不用替換整個原鏡像或從新創建,只須要添加新層便可。
用戶分發鏡像的時候,也只須要分發被改動的新層內容(增量部分)。
這讓docker的鏡像管理變得十分輕量級和快速。

1.docker存儲

Docker目前經過插件化方式支持多種文件系統後端。
Debian/Ubuntu上成熟的AUFS就是一種聯合文件系統實現。
AUFS支持爲每個成員目錄(相似git分支)設定只讀(readonly)、讀寫(readwrite)或寫出(whiteout-able)權限,
同時AUFS裏有一個相似分層的概念,對只讀權限的分支能夠在邏輯上進行增量的修改(不影響只讀部分)。

Docker鏡像自身就是由多個文件層組成,每一層有惟一的編號(層ID)。
能夠經過docker history查看一個鏡像有那些層組成。

對於docker來講,這些層的內容都是不可修改的、只讀的。
而當docker利用鏡像啓動一個容器時,將在鏡像文件系統的最頂端再掛載一個新的可讀寫層給容器
容器中的內容更新將會發生在可讀寫層。當所操做對象位於較深的某層時,須要先複製到最上層的可讀寫層。
當數據對象較大時,每每意味IO性能較差。所以,通常推薦將容器修改的數據經過volume方式掛載,而不是修改鏡像內的數據。

此外,對於頻繁啓停docker容器的場景下,文件系統的IO性能也將十分關鍵。

docker默認系統/var/lib/docker
docker默認配置文件/etc/docker

2.多種文件系統比較

Docker目前支持的聯合文件系統種類包括:AUFS、OverlayFS、btrfs、vfs、zfs和Device Mapper等。
各類文件系統狀況以下:
  AUFS:最先支持的文件系統,對Debian/Ubuntu支持好,雖然沒有合併到Linux內核中,但成熟度很高。
  OverlayFS:相似於AUFS,性能更好一些,已經合併到內核,將來會取代AUFS,但成熟度有待提升。
  Device Mapper:Redhat公司和Docker團隊一塊兒開發用於支持RHEL的文件系統,內核支持,性能略慢,成熟度高。
  btrfs:參考zfs等特性設計的文件系統,由Linux社區開發,視圖將來取代Device Mapper,成熟度有待提升。
  vfs:基於普通文件系統(ext、nfs)的中間層抽象,性能較差,比較佔用空間,成熟度也通常。
  zfs:最初設計爲Solaris 10上寫的文件系統,擁有很多好的特性,但對於Linux支持還不夠成熟。

AUFS和Device Mapper的應用最爲普遍,推薦使用。

 

 5、Linux網絡虛擬化

Docker的本地網絡實現其實就是利用了Linux上的網絡命令空間虛擬網絡設備(特別是veth)。

1.基本原理

直觀上看,要實現網絡通訊,機器須要至少一個網絡接口,(物理接口或虛擬接口)與外界相同,並能夠收發數據包;
此外,若是不一樣子網之間要進行通信,須要額外的路由機制。

Docker的網絡接口默認都是虛擬的接口。虛擬接口的優點就是轉發效率極高。
這是由於Linux經過在內核中進行數據複製來實現虛擬接口之間的數據轉發,
即發送接口發送緩存中的數據包將被直接複製到接收接口的接受緩存中,
而無需經過外部網絡設備進行交換。對於本地系統和容器內系統來看,
虛擬接口跟一個正常的以太網卡相比並沒有區別,只是它速度要快得多。

Docker容器網絡就很好的利用了Linux虛擬網絡技術,在本地主機和容器內分別建立一個虛擬接口
並讓他們彼此連通(這樣一對接口叫作veth pair)。

2.網絡建立過程

通常狀況下,docker建立一個容器的時候,會執行以下操做:
  1)建立一對虛擬接口,分別放到本地主機和新容器的命名空間中;
  2)本地主機一端的虛擬接口鏈接到默認的docker()網橋並具備一個以veth開頭的惟一名字。
  3)容器一端的虛擬接口將放到新建立的容器中,並修更名字做爲eth0.這個名字值在容器的命名空間中可見。
  4)從網橋可用地址段中獲取一個空閒地址分配給容器eth0,並配置默認路由網關爲docker()網卡的內部接口docker()的IP地址。
完成這些後,容器就可使用他所能看到的eht0虛擬網卡來鏈接其餘容器和訪問外部網絡。

經過docker network命令來手動管理網絡。
使用docker run或docker create命令啓動容器的時候,能夠經過--net參數來指定容器的網絡配置。
有5個可選值:bridge、none、container、host和用戶定義網絡。
  --net=bridge:默認值,在Docker網橋docker()上爲容器建立新的網絡棧

  --net=none:讓Docker將新容器放到隔離的網絡棧中,可是不進行網絡配置。以後用戶能夠自定義進行配置。

  --net=container:NAME_or_ID:讓Docker將新建立容器的進程放到一個已存在容器的網絡棧中
    新容器進程有本身的文件系統、進程列表和資源限制,但會已存在的容器共享IP地址和端口等資源,
    二者進程能夠直接經過lo環回接口通訊。

  --net=host:告訴Docker不要將容器網絡放到隔離的命名空間中,即不要容器化容器中的網絡。
    此時容器使用本地主機的網絡,它擁有徹底的本地主機接口使用權限。
    容器進程能夠跟主機其它root進程同樣打開低範圍的端口,能夠訪問本地網絡服務。
    好比D-bus,還可讓容器作一些影響整個主機系統的事情,好比重啓主機。
    若是進一步使用--privileged=true參數,容器會被直接容許配置主機的網絡棧。

  --net=user_defined_network:用戶自行用network相關命令建立一個網絡,經過這種方式將容器鏈接到指定的已建立的網絡上去。

 

 

6、docker、Containerd、runc

從Docker 1.11開始,Docker容器運行已經不是簡單的經過Docker Daemon來啓動,而是集成了containerd、runc等多個組件。
Docker服務啓動以後,咱們能夠看到系統啓動了dockerd、docker-containerd、docker-runc等進程。

1.Docker Daemon

做爲Docker容器管理的守護進程,Docker Daemon從最初集成在docker命令中,
到後來的獨立成單獨的二進制程序,其功能正在被逐漸拆分細化,被分配到各個單獨的模塊中去。
從Docker服務的啓動腳本,也能看到守護進程的逐漸剝離。

在Docker1.8以前,Docker守護進程啓動的命令爲:docker -d,這個階段,守護進程看上去只是Docker client的一個選項。
Docker1.8開始,啓動命令就變成了:docker daemon,這個階段,守護進程看上去是docker命令的一個模塊。
Docker1.11開始,守護進程啓動命令變成了:dockerd,此時已經和Docker client分離獨立成一個二進制程序了。

固然,守護進程不停的在重構,其基本功能和定位沒有變化。
和通常的CS架構系統同樣,守護進程負責和Docker client交互,並管理Docker鏡像、容器。

下面會介紹獨立拆分出來的幾個模塊。

 

2.Containerd

Containerd是容器技術標準化以後的產物,爲了可以兼容OCI標準,將容器運行時及其管理功能從Docker Daemon剝離。
理論上是,即便不運行Dockerd也可以直接經過Containerd來管理容器。
固然,Containerd自己只是一個守護進程,容器的實際運行時由後面介紹的RunC控制。

Containerd主要職責是鏡像管理(鏡像、元信息等)、容器執行(調用組件執行)。

Containerd向上爲Docker Daemon提供了RPC接口,使得Dokcer Daemon屏蔽下面的結構變化,確保原有接口向下兼容。
向下經過containerd-shim結合runC,使得引擎能夠獨立升級,避免以前Docker Daemon升級會致使全部容器不可用的問題。

Docker、containerd和containerd-shim之間的關係,能夠經過啓動一個Docker容器,觀察進程之間的管理。

啓動一個Docker容器:

docker run -d busybox sleep 1000

經過pstree命令查看進程之間的父子關係:

pstree -l -a -A 2950

咱們能夠看出,當Docker Daemon啓動以後,dockerd和docker-containerd進程一直存在。
當啓動容器以後,docker-containerd進程會建立docker-containerd-shim進程。

 

3.RunC

OCI定義了容器運行時標準,runC是Docker按照開放容器格式標準(OCF,Open containerd Format)制定的一種具體實現。runC是從Docker的libcontainer中遷移而來的,實現了容器啓動中止、資源隔離等功能。Docker默認提供了docker-runc實現,事實上,經過containerd的封裝,能夠在Docker Daemon啓動的時候指定runC的實現。docker daemon --add-runtime "custom=/usr/local/bin/my-runc-replacement"

相關文章
相關標籤/搜索