Docker 爲什麼這麼牛?

作者:huashiou

來源:segmentfault.com/a/1190000019462392

  1. Docker 出現的背景

在平常的研發和項目場景中,以下情況普遍存在:

2)小剛用 python3 寫了一個機器學習算法,放到虛擬機上運行發現虛擬機裏是 python2,算法不兼容,於是把虛擬機裏的 python 版本升級了,算法跑通了,但 Ambari 用到 python 的部分功能可能就報錯了

3)小李開發了應用,放到虛擬機上啓動 tomcat,發現虛擬機裏的是 OpenJDK,導致 tomcat 起不來,於是又安裝了一個 JDK,這時候可能 Ambari 裏的 Java 代碼可能就報錯了

4)小趙想利用服務器資源做性能測試,發現虛擬機嚴重削減了性能,最終還是要直接找物理機來跑測試,破壞了物理機原來的環境

做完項目後,這些虛擬機上安裝的東西往往變得沒用了,下個項目組來還是得新申請虛擬機重新部署軟件

總結以上列舉的所有場景,他們存在的一個共同的問題是:沒有一種既能夠屏蔽操作系統差異,又能夠以不降低性能的方式來運行應用的技術,來解決環境依賴的問題。Docker 應運而生。

  1. Docker 是什麼

圖片

Docker 是一種應用容器引擎。首先說一下何爲容器,Linux 系統提供了NamespaceCGroup技術實現環境隔離和資源控制,其中 Namespace 是 Linux 提供的一種內核級別環境隔離的方法,能使一個進程和該進程創建的子進程的運行空間都與 Linux 的超級父進程相隔離,注意 Namespace 只能實現運行空間的隔離,物理資源還是所有進程共用的,爲了實現資源隔離,Linux 系統提供了 CGroup 技術來控制一個進程組羣可使用的資源(如 CPU、內存、磁盤 IO 等),把這兩種技術結合起來,就能構造一個用戶空間獨立且限定了資源的對象,這樣的對象稱爲容器。

Linux Container是 Linux 系統提供的容器化技術,簡稱LXC,它結合 Namespace 和 CGroup 技術爲用戶提供了更易用的接口來實現容器化。LXC 僅爲一種輕量級的容器化技術,它僅能對部分資源進行限制,無法做到諸如網絡限制、磁盤空間佔用限制等。dotCloud 公司結合 LXC 和以下列出的技術實現了 Docker 容器引擎,相比於 LXC,Docker 具備更加全面的資源控制能力,是一種應用級別的容器引擎。

也正是因爲 Docker 依賴 Linux 內核的這些技術,至少使用 3.8 或更高版本的內核才能運行 Docker 容器,官方建議使用 3.10 以上的內核版本。

  1. 與傳統虛擬化技術的區別

圖片

傳統的虛擬化技術在虛擬機(VM)和硬件之間加了一個軟件層 Hypervisor,或者叫做虛擬機管理程序。Hypervisor 的運行方式分爲兩類:

因爲運行在虛擬機上的操作系統是通過 Hypervisor 來最終分享硬件,所以虛擬機 Guest OS 發出的指令都需要被 Hypervisor 捕獲,然後翻譯爲物理硬件或宿主機操作系統能夠識別的指令。VMWare 和 VirtualBox 等虛擬機在性能方面遠不如裸機,但基於硬件虛擬機的 KVM 約能發揮裸機 80% 的性能。這種虛擬化的優點是不同虛擬機之間實現了完全隔離,安全性很高,並且能夠在一臺物理機上運行多種內核的操作系統(如 Linux 和 Window),但每個虛擬機都很笨重,佔用資源多而且啓動很慢。

Docker 引擎運行在操作系統上,是基於內核的 LXC、Chroot 等技術實現容器的環境隔離和資源控制,在容器啓動後,容器裏的進程直接與內核交互,無需經過 Docker 引擎中轉,因此幾乎沒有性能損耗,能發揮出裸機的全部性能。但由於 Docker 是基於 Linux 內核技術實現容器化的,因此使得容器內運行的應用只能運行在 Linux 內核的操作系統上。目前在 Window 上安裝的 docker 引擎其實是利用了 Window 自帶的 Hyper-V 虛擬化工具自動創建了一個 Linux 系統,容器內的操作實際上是間接使用這個虛擬系統實現的。

  1. Docker 基本概念

圖片

Docker 主要有如下幾個概念:

  1. Docker 與虛擬機、Git、JVM 的類比

爲了讓大家對 Docker 有更直觀的認識,下面分別進行三組類比:

圖片

上圖中 Docker 的鏡像倉庫類似於傳統虛擬機的鏡像倉庫或存放鏡像的本地文件系統,Docker 引擎啓動容器來運行 Spark 集羣(容器內包含基礎的 Linux 操作系統環境),類比於虛擬機軟件啓動多個虛擬機,在虛擬機內分別運行 Spark 進程,兩者區別在於 Docker 容器內的應用在使用物理資源時,直接與內核打交道,無需經過 Docker 引擎。
圖片

Docker 的倉庫思想與 Git 是相同的。

圖片

Docker 的口號是 “Build,Ship,and Run Any App,Anywhere”,也就是可以基於 Docker 構建、裝載和運行應用程序,一次構建到處運行。Java 的口號是 “Write Once,Run Anywhere”,即一次編寫到處運行。

Java 是基於 JVM 適配操作系統的特點來屏蔽系統的差異,Docker 則是利用內核版本兼容性的特點來實現一次構建導出運行,只要 Linux 系統的內核是 3.8 或更高的版本,就都能把容器跑起來。

當然,正如 Java 中如果應用代碼使用了 JDK10 的新特性,基於 JDK8 就無法運行一樣,如果容器內的應用使用了 4.18 版本的內核特性,那麼在 CentOS7(內核版本爲 3.10)啓動容器時,雖然容器能夠啓動,但裏面應用的功能是無法正常運行的,除非把宿主機的操作系統內核升級到 4.18 版本。

  1. Docker 鏡像文件系統

圖片

Docker 鏡像採用分層存儲格式,每個鏡像可依賴其他鏡像進行構建,每一層的鏡像可被多個鏡像引用,上圖的鏡像依賴關係,K8S 鏡像其實是 CentOS+GCC+GO+K8S 這四個軟件結合的鏡像。

這種分層結構能充分共享鏡像層,能大大減少鏡像倉庫佔用的空間,而對用戶而言,他們所看到的容器,其實是 Docker 利用 UnionFS(聯合文件系統)把相關鏡像層的目錄 “聯合” 到同一個掛載點呈現出來的一個整體,這裏需要簡單介紹一個 UnionFS 是什麼:

UnionFS 可以把多個物理位置獨立的目錄(也叫分支)內容聯合掛載到同一個目錄下,UnionFS 允許控制這些目錄的讀寫權限,此外對於只讀的文件和目錄,它具有 “Copy on Write(寫實複製)” 的特點,即如果對一個只讀的文件進行修改,在修改前會先把文件複製一份到可寫層(可能是磁盤裏的一個目錄),所有的修改操作其實都是對這個文件副本進行修改,原來的只讀文件並不會變化。其中一個使用 UnionFS 的例子是:Knoppix,一個用於 Linux 演示、光盤教學和商業產品演示的 Linux 發行版,它就是把一個 CD/DVD 和一個存在在可讀寫設備(例如 U 盤)聯合掛載,這樣在演示過程中任何對 CD/DVD 上文件的改動都會在被應用在 U 盤上,不改變原來的 CD/DVD 上的內容。

UnionFS 有很多種,其中 Docker 中常用的是 AUFS,這是 UnionFS 的升級版,除此之外還有 DeviceMapper、Overlay2、ZFS 和 VFS 等。Docker 鏡像的每一層默認存放在/var/lib/docker/aufs/diff目錄中,當用戶啓動一個容器時,Docker 引擎首先在/var/lib/docker/aufs/diff中新建一個可讀寫層目錄,然後使用 UnionFS 把該可讀寫層目錄和指定鏡像的各層目錄聯合掛載到/var/lib/docker/aufs/mnt裏的一個目錄中(其中指定鏡像的各層目錄都以只讀方式掛載),通過 LXC 等技術進行環境隔離和資源控制,使容器裏的應用僅依賴 mnt 目錄中對應的掛載目錄和文件運行起來。

利用 UnionFS 寫實複製的特點,在啓動一個容器時, Docker 引擎實際上只是增加了一個可寫層和構造了一個 Linux 容器,這兩者都幾乎不消耗系統資源,因此 Docker 容器能夠做到秒級啓動,一臺服務器上能夠啓動上千個 Docker 容器,而傳統虛擬機在一臺服務器上啓動幾十個就已經非常喫力了,而且虛擬機啓動很慢,這是 Docker 相比於傳統虛擬機的兩個巨大的優勢。

當應用只是直接調用了內核功能來運作的情況下,應用本身就能直接作爲最底層的層來構建鏡像,但因爲容器本身會隔絕環境,因此容器內部是無法訪問宿主機裏文件的(除非指定了某些目錄或文件映射到容器內),這種情況下應用代碼就只能使用內核的功能。但是 Linux 內核僅提供了進程管理、內存管理、文件系統管理等一些基礎且底層的管理功能,在實際的場景中,幾乎所有軟件都是基於操作系統來開發的,因此往往都需要依賴操作系統的軟件和運行庫等,如果這些應用的下一層直接是內核,那麼應用將無法運行。所以實際上應用鏡像往往底層都是基於一個操作系統鏡像來補足運行依賴的。

Docker 中的操作系統鏡像,與平常安裝系統時用的 ISO 鏡像不同。ISO 鏡像裏包含了操作系統內核及該發行版系統包含的所有目錄和軟件,而 Docker 中的操作系統鏡像,不包含系統內核,僅包含系統必備的一些目錄(如 / etc /proc 等)和常用的軟件和運行庫等,可把操作系統鏡像看作內核之上的一個應用,一個封裝了內核功能,併爲用戶編寫的應用提供運行環境的工具。應用基於這樣的鏡像構建,就能夠利用上相應操作系統的各種軟件的功能和運行庫,此外,由於應用是基於操作系統鏡像來構建的,就算換到另外的服務器,只要操作系統鏡像中被應用使用到的功能能適配宿主機的內核,應用就能正常運行,這就是一次構建到處運行的原因。

下圖形象的表現出了鏡像和容器的關係:

圖片

上圖中 Apache 應用基於 emacs 鏡像構建,emacs 基於 Debian 系統鏡像構建,在啓動爲容器時,在 Apache 鏡像層之上構造了一個可寫層,對容器本身的修改操作都在可寫層中進行。Debian 是該鏡像的基礎鏡像(Base Image),它提供了內核 Kernel 的更高級的封裝。同時其他的鏡像也是基於同一個內核來構建的(以下的 BusyBox 是一個精簡版的操作系統鏡像):
圖片

這時候就會有一個問題,應用基於操作系統鏡像來構建,那如果操作系統鏡像本身就很佔空間,豈不是鏡像的分發不方便,而且鏡像倉庫佔用的空間也會很大。有人已經考慮到這一點,針對不同的場景分別構造了不同的操作系統鏡像,下面介紹幾種最常用的系統鏡像。

  1. Docker 基礎操作系統

圖片

以上系統鏡像分別適用於不同的場景:

  1. Docker 持久化存儲

根據前面介紹的容器 UnionFS 寫實複製的特點,可知在容器裏增加、刪除或修改文件,其實都是對可寫層裏的文件副本進行了操作。在容器關閉後,該可寫層也會被刪除,對容器的所有修改都會失效,因此需要解決容器內文件持久化的問題。Docker 提供了兩種方案來實現:

圖片

圖片

  1. Docker 鏡像製作方法

鏡像製作方法有兩種:

圖片

當一個容器在運行時,在裏面所有的修改都會體現在容器的可寫層,Docker 提供了 commit 命令,可以把正在運行的容器,疊加上可寫層的修改內容,生成一個新鏡像。如上圖所示,在容器裏新安裝 Spark 組件的,如果關閉容器,Spark 組件會隨着可寫層的消失而消失,如果在關閉容器之前使用 commit 命令生成新鏡像,那麼使用新鏡像啓動爲容器時,容器裏就會包含 Spark 組件。

這種方式比較簡單,但無法直觀的設置環境變量、監聽端口等內容,適合在簡單使用的場景運用。

圖片

Dockerfile 是一個定義了鏡像創建步驟的文件,Docker 引擎通過 build 命令讀取 Dockerfile,按定義的步驟來一步步構造鏡像。在研發和實施環境中,通過 Dockerfile 創建容器是主流做法。下面是一個 Dockerfile 的例子:

FROM ubuntu/14.04                                # 基礎鏡像
MAINTAINER guest                                 # 製作者簽名
RUN apt-get install openssh-server -y            # 安裝ssh服務
RUN mkdir /var/run/sshd                          # 創建目錄
RUN useradd -s /bin/bash -m -d /home/guest guest # 創建用戶
RUN echo ‘guest:123456’| chpasswd                # 修改用戶密碼
ENV RUNNABLE_USER_DIR /home/guest                # 設置環境變量
EXPOSE 22                                        # 容器內默認開啓的端口
CMD ["/usr/sbin/sshd -D"]                        # 啓動容器時自動啓動ssh服務

Docker 引擎可以根據以上 Dockerfile 定義的步驟,構造出一個帶有 ssh 服務的 Ubuntu 鏡像。

  1. Docker 的使用場景

Docker 作爲一種輕量級的虛擬化方案,應用場景十分豐富,下面收集了一些常見的場景:

PaaS(平臺即服務):把開發平臺作爲服務提供給用戶。用戶可以在一個包括 SDK,文檔和測試環境等在內的開發平臺上非常方便地編寫應用,而且不論是在部署,或者在運行的時候,用戶都無需爲服務器、操作系統、網絡和存儲等資源的管理操心,這些繁瑣的工作都由 PaaS 供應商負責處理。其主要的用戶是企業開發人員。

SaaS(軟件即服務):將應用作爲服務提供給客戶。用戶只要接上網絡,並通過瀏覽器,就能直接使用在雲端上運行的應用,而不需要顧慮類似安裝等瑣事,並且免去初期高昂的軟硬件投入。SaaS 主要面對的是普通的用戶。

CaaS(容器即服務):完成 IaaS 和 PaaS 兩個層級的功能。相對於傳統的 IaaS 和 PaaS 服務,CaaS 對底層的支持比 PaaS 更靈活,而對上層應用的操控又比 IaaS 更容易。同時因爲 Docker 是比 VM 更細粒度的虛擬化服務,所以能夠對計算資源做到更高效的利用。CaaS 可以部署在任何物理機,虛擬機或 IaaS 雲之上。

  1. 總結

Docker 的技術並不神祕,只是整合了前人積累的各種成果實現的應用級的容器化技術,它利用各種 Linux 發行版中使用了版本兼容的內核容器化技術,來實現鏡像一次構建到處運行的效果,並且利用了容器內的基礎操作系統鏡像層,屏蔽了實際運行環境的操作系統差異,使用戶在開發應用程序時,只需確保在選定的操作系統和內核版本上能正確運行即可,幾乎不需要關心實際的運行環境的系統差異,大大提高效率和兼容性。

但隨着容器運行得越來越多,容器管理將會稱爲另一個運維的難題,這時候就需要引入 Kubernetes、Mesos 或 Swarm 這些容器管理系統,後面有機會再介紹這些技術。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Gye-_lR4CGYwMS2Ofaml6Q