Docker操作實踐(1):容器的本質是什麼?容器從何而來?

本文旨在用三篇文章讓讀者能夠清晰的認識與使用docker。本文結合理論與實戰,先是從進程隔離、文件隔離、namespace、cgroups、libcontainer展開介紹容器的本質與概念,然後分析docker的技術架構,最後演示docker的常用操作。而今天,我們就先從docker的原理開始。



容器本質之進程隔離


1. 容器本質

容器本質上是一種進程隔離的技術。容器爲進程提供了一個隔離的環境,容器內的進程無法訪問容器外的進程。


2. 容器及容器中的進程在主機上的呈現

啓動一個ubuntu的容器:

docker run -it ubuntu


2b1079e71a848cd287008d1c18b63dd0


在主機上可以看到啓動了三個進程:


82340cdc97079d59037727164358c865

第一個是剛剛執行的命令

第二個是啓動的容器,容器在系統上就是一個進程

第三個是在該容器父進程下的一個子進程:/bin/bash


在容器中運行top命令生成一個進程:


db506a7ba3aa0fa7bf708764664deb15

在主機上繼續查看進程:


453e38baed551874c9d9a16d4b1fbc9d

可以查看到主機上多了一個top命令的進程,該進程的父進程是上面啓動容器時運行的/bin/bash


由此,我們可以得知,容器在主機上是一個進程,容器中的進程在主機上,是容器進程樹下的子進程或子子進程。


容器中的進程有什麼不同呢,直觀上看,就是進程編號不一樣了,我們使用docker run -it busybox /bin/sh再啓動一個busybox的容器,並查看它的進程號:


01654a80dc9dfa5e30377aa7c626f2ff

/bin/sh的進程號爲1

在主機上查看:


3293e6b62133670618beb3a4d340a141

/bin/sh的進程號是容器父進程下的37508


小結:容器是一個進程,在容器中啓動進程,其實就是在容器這個父進程下啓動一個子進程。並且使用“障眼法”對這個子進程的進程編號進行了重新編號,使得用戶在容器中查看進程時,如同身處於一個OS環境中。



容器本質之文件隔離


1. 使用chroot來實現文件隔離

容器的本質是進程隔離,那麼容器與外部之間也會存在着文件的隔離。文件系統隔離,這也是容器概念的起始。


容器的概念始於 1979 年的 UNIX  chroot,它是一個 UNIX 操作系統上的系統調用,用於將一個進程及其子進程的根目錄改變到文件系統中的一個新位置,讓這些進程只能訪問到該目錄。這個功能的想法是爲每個進程提供獨立的磁盤空間。


接下來我們使用chroot來設置一個隔離的文件系統,也就是將某個目錄設置爲根目錄。


創建根目錄 /root/container ,然後運行 chroot container進行設置:

abb5a623ceeffd7d46a4e32980a7506f

報錯,是因爲chroot設置該目錄時,會啓動一個僅在該目錄範圍內操作的bash,而我們沒有將bash命令文件拷貝進行來,拷貝相關的命令文件、動態鏈接庫文件:

6858dcbd3850b7cf7508356ff6c8a8e0

然後再運行chroot container設置其爲根目錄:


309d09674050af342a95db1a77eb1e75

在上圖中,我們運行了pwd,發現該目錄已經是根目錄了。我們在此命令行窗口下的所有操作都是在此目錄範圍內進行了。


基於chroot,我們還可以繼續將一些常用命令添加進來、設置該目錄的訪問權限,讓用戶登錄時就切到此目錄下等等。


chroot爲進程隔離打開了一扇大門,後面就有更多的進程隔離技術加進來,並逐步建立規範,最終形成了容器技術,誕生了家喻戶曉的docker。



容器技術演進歷史


1. 容器技術演進歷史

1979年,chroot技術的引進開啓了進程隔離大門。


171e1cc18b79fc003a17da243f34a83e

2000年,FreeBSD Jails 作爲最早的容器技術之一,它由 R&D Associates 公司的 Derrick T. Woolworth 在 2000 年爲 FreeBSD 引入。這是一個類似 chroot 的操作系統級的系統調用,但是爲文件系統、用戶、網絡等的隔離增加了進程沙盒功能。因此,它可以爲每個 jail 指定 IP 地址、可以對軟件的安裝和配置進行定製,等等


2006 年,Process Containers 由 Google 實現,用於對一組進程進行限制、記賬、隔離資源使用(CPU、內存、磁盤 I/O、網絡等)。後來爲了避免和 Linux 內核上下文中的“容器”一詞混淆而改名爲 Control Groups。Cgroups至今仍然作爲容器技術的關鍵組成之一。


2008 年,LXC作爲第一個最完善的 Linux 容器管理器的實現方案,是通過 cgroups 和 Linux 名字空間(namespace)實現的。LXC提供了各種編程語言的 API 實現,包括 Python3、Python2、Lua、Go、Ruby 和 Haskell。與其它容器技術不同的是, LXC 可以工作在普通的 Linux 內核上,而不需要增加補丁。


2013年,LMCTFY出現,lmctfy 的意思是“讓我爲你容器化(Let Me Contain That For You)”。這是一個 Google 容器技術的開源版本,提供 Linux 應用容器。Google 啓動這個項目旨在提供能可保證的、高資源利用率的、資源共享的、可超售的、接近零消耗的容器。現在爲 Kubernetes 所用的 cAdvisor 工具就是從 lmctfy 項目的成果開始的。


lmctfy 首次發佈於 2013 年10月,在 2015 年 Google 決定貢獻核心的 lmctfy 概念,並抽象成 libcontainer。libcontainer 項目最初由  Docker 發起,現在已經被移交給了開放容器基金會(Open Container Foundation)。Libcontainer目前仍然是docker的重要組成之一。Docker最初使用LXC,後面替換爲libcontainer。


2013年,Docker面世。Docker實際上是一家公司,在2013年這家公司還叫做DotCloud,Docker是他們公司的一個容器管理產品,2013年初,DotCloud決定將Docker開源,Docker在短短幾個月間風靡全球,DotCloud公司也更名爲Docker。


2. Docker的發展狀況

從goole熱度上可以獲取到docker的熱度如下:

d5ba51630023cfc7fe0aa7fdb6e75b51



容器本質之Namespace


1. Namespace的類別

在後面的容器技術中,實現上都離不開Linux的Namespace技術。Namespace 是 Linux 提供的一種內核級別環境隔離的方法。Namespace的隔離有6大分類:

08de718b063c855d9f8121e1ad32efbb


每個進程都有自己的namespace的id。可以通過ls -l 來查看。

93cb0b5a8fe0e8771f2f2646783381c3

關於6種Namespace的簡介如下:

  • Mount namespace 讓容器看上去擁有整個文件系統,它與chroot相比,具有更好的安全性和擴展性。

  • IPC 全稱 Inter-Process Communication,是 Unix/Linux 下進程間通信的一種方式,IPC 有共享內存、信號量、消息隊列等方法。所以,爲了隔離,我們也需要把IPC給隔離開來,這樣,只有在同一個 Namespace 下的進程才能相互通信。

  • Network namespace 讓容器擁有自己獨立的網卡、IP、路由等資源。

  • UTS namespace 讓容器有自己的 hostname。默認情況下,容器的 hostname 是它的短ID,可以通過 -h 或 --hostname 參數設置

  • 容器擁有自己獨立的一套 PID,同一個主機上,不同容器的進程的編號可以是相同的。容器中的進程的編號也可以與主機的進程編號相同。這就是 PID namespace 提供的功能。

  • User namespace 讓容器能夠管理自己的用戶,host 不能看到容器中創建的用戶。


2. 主機中與容器中的進程在Namespace上的呈現

在主機上運行top和ping命令,可以查看到這兩個進程的namespace是一致的:

8126c709b022994230ad6e146d326e2a

查看top進程的namespace

ea5631923096af81e2b621e75ee72737

對比top進程和ping進程的namespace


4ab9d5ad7941f32697ccf2ff1894d2e3

可以發現,主機上的這兩個進程的namespace是一致的。


運行一個容器”centos1”,並在容器中運行top命令,查看容器中的top進程與主機上的top進程的namespace,是不同的:


運行容器,並在容器中運行top命令

d9ea05890449db37e2b759d201a953f8

在主機上查看容器中的top命令的進程id

c9ea2d5e9590e1abb2d0293d9f05d6e7

查看容器中的top進程的namespace


2919264ae1cdc3b0d29992c90491660b

對比主機上的top進程的namespace


ea5631923096af81e2b621e75ee72737

可以發現,主機上的top進程與容器上的top進程的namespace是不同的。

運行第二個centos容器”centos2”,並在容器中運行top進程,與第一個容器”centos1”中的top進程的namespace進行對比:

b9ba097e337c666bba466615a96e8e38

查看該容器中的top進程的id

2378cb5526cadb4960e871d2885f2a02

查看該容器中top進程的namespace:

862538b54d541d2028b4afdd66dc44e0

與第一個容器”centos1”中的top進程的namespace對比:

2919264ae1cdc3b0d29992c90491660b

可以發現是不一致的。


結論:docker容器,利用namespace實現了文件系統、網絡、主機名、進程方面的隔離。



容器核心技術之cgroups


1. cgroups定義及作用

cgroups是Linux內核提供的一種機制,這種機制可以根據需求把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而爲系統資源管理提供一個統一的框架。


本質上來說,cgroups是內核附加在程序上的一系列鉤子(hook),通過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。


cgroups主要有以下幾個功能:

資源限制:cgroups可以對任務使用的資源總額進行限制,比如內存大小限制;

優先級分配:通過分配的CPU時間片數量及磁盤IO帶寬大小,實際上就相當於控制了任務運行的優先級。

資源統計:cgroups可以統計系統的資源使用量,如CPUwetjfta\mwdhetjgtffu,p個功能非常適用計費。

任務控制:cgroups可以對任務執行掛起、恢復等操作。


2. Docker中如何使用cgroups

當我們運行:docker run -it --rm --cpu-period=100000 --cpu-quota=200000 centos /bin/bash

fb2c08cae6fcde05f062a29af9462b2e

--cpu-period和 –cpu-quota 表示在每100毫秒的時間裏,運行進程使用的cpu時間最多爲200毫秒(也就是要佔用兩個cpu)


進入容器的cgroups目錄,查看啓動容器時的cpu配置是否已經生效:

f7a05a0a6d9776c5658e4b9086dcf766

可以看到該配置已經生效,那麼接下來容器就在這個cpu限額範圍內運行了。



容器核心技術之Libcontainer


1. 其他的進程隔離技術

前面的內容中,詳細介紹了進程隔離的兩個最主要技術,namespace和cgroups,除了他們之外,比較重要的進程隔離技術還有selinux、apparmor、capability、netlink。


Selinx與apparmor可以增加對容器的訪問控制,capability實現了將超級用戶root的權限分割爲多種不同的capability權限,從而實現對容器的更細顆粒度的權限控制,netlink技術則可實現對docker容器網絡環境的配置與創建。


2. LXC、Libcontainer與docker

基於各種進程隔離技術,我們就可以創建一個個滿足應用運行要求的容器。但這個過程非常複雜。需要有一個簡易的容器管理工具。Docker就是最流行的容器管理工具。但docker並沒有直接操作namespace等這些linux內核技術,而是基於lxc或libcontainer之上再進行一次封裝。通過lxc\libcontainer來操作這些linux內核技術。


3ebad5ee644a6bb0f3a2791c07887373

docker1.8之後廢棄了LXC(Linux Container,即linux虛擬容器技術),引入了基於Go構建的libcontainer的execution driver. 有了libcontainer這個項目, Docker不再需要依賴於Linux部件( LXC, libvirt, systemd-nspawn... )就可以處理namespaces, control groups, capabilities, apparmor profiles, network interfaces。


LXC是第一個真正意義的容器管理工具,主要是提供了方便的命令行接口供用戶操作以調用底層的namespace等進程隔離進行。而libcontainer與lxc相比,實際上是反向定義容器了實現標準,將底層實現都抽象化到Libcontainer的接口。這就意味着,底層容器的實現方式變成了一種可變的方案,無論是使用namespace、cgroups技術抑或是使用systemd等其他方案,只要可以與上層的 Libcontainer定義的接口對接,那麼libcontainer上層的Docker也就可以運行。這也爲Docker實現全面的跨平臺帶來了可能。


從上面的架構圖中可以看到,docker daemon通過docker的execdriver和networkriver完成對容器及容器網絡的創建。



3. Libcontainer的工作機制

Open Container Initiative(OCI)組織成立以後,libcontainer進化爲runC,因此從技術上說,未來licontainer/runC創建的將是符合Open Container Format(OCF)標準的容器。


當使用docker run命令時,將會在主機上創建一個容器,Docker daemon調用libcontainer創建容器的過程如下:


fdda1b2bbafec9799fb0471a3a31e3e9


創建邏輯容器LinuxContainer和邏輯進程Process

  • 在執行docker run的時候,docker中的execdriver調用libcontainer創建一個邏輯容器LinuxContainer和邏輯進程Process,邏輯容器位於/var/lib/docker/containers之下。


  • Execdriver在調用libcontainer時,會將從docker run命令接收到的參數(如cpu-quota等)進行處理之後以libcontainer所能接收的格式傳輸給libcontainer。而邏輯進程Process,則是容器中所要運行的指令及其參數和環境變量等配置信息。


  • 啓動邏輯容器LinuxContainer


  • 使用Container.start(Process)方法來啓動邏輯容器和創建物理容器。


  • 啓動邏輯容器,實際上是在libcontainer中創建一個initProcess的對象,包含了邏輯容器配置、邏輯進程配置,以及進程與外部的交互管道(pipes)等。這個initProcess對象用於創建真正的物理容器。


  • 創建物理容器Container


  • 使用initProcess.start()方法創建物理容器Container。


  • 這個過程中會配置容器的cgroup,創建容器內部的網絡設備,配置容器內部的信息,如hostname、ip地址等


  • 最後啓動容器中需要運行的進程Endtrypoint



作者:沈曉龍



好文推薦

使用sqlplus進行Oracle數據庫批量自動發佈

業務複雜、數據龐大、應用廣怎辦?瞭解下分佈式事務的解決思路!

SaaS設計:自動化服務啓停設計示例 

這裏有份選擇雲服務商的攻略,請查收…


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章