Docker 爲什麼這麼牛?
作者:huashiou
來源:segmentfault.com/a/1190000019462392
- Docker 出現的背景
在平常的研發和項目場景中,以下情況普遍存在:
-
個人開發環境 爲了做大數據相關項目,需要安裝一套 CDH 集羣,常見的做法是在自己電腦裏搭建 3 臺與 CDH 版本對應的虛擬機,把 CDH 集羣裝起來後,考慮到以後很有可能還要使用一個乾淨的 CDH 集羣,爲了避免以後重複安裝環境,通常會對整套 CDH 集羣做一個備份,這樣電腦裏就有 6 個虛擬機鏡像了。另外,後面在學習其他技術時,比如學習 Ambari 大數據集羣,那麼爲了不破壞已有的虛擬機環境,又要重新搭建 3 臺虛擬機,本機磁盤很快被一大堆的虛擬機鏡像佔滿。
-
公司內部開發環境 公司裏往往會以小團隊的方式來做項目,一般由運維部門從他們管理的服務器資源中分配出虛擬機供團隊內部開發測試使用。
比如做一個與機器學習相關的項目:
1)小明在運維部門分配的虛擬機上搭建了一套 Ambari 集羣,拿來跑大數據相關業務
2)小剛用 python3 寫了一個機器學習算法,放到虛擬機上運行發現虛擬機裏是 python2,算法不兼容,於是把虛擬機裏的 python 版本升級了,算法跑通了,但 Ambari 用到 python 的部分功能可能就報錯了
3)小李開發了應用,放到虛擬機上啓動 tomcat,發現虛擬機裏的是 OpenJDK,導致 tomcat 起不來,於是又安裝了一個 JDK,這時候可能 Ambari 裏的 Java 代碼可能就報錯了
4)小趙想利用服務器資源做性能測試,發現虛擬機嚴重削減了性能,最終還是要直接找物理機來跑測試,破壞了物理機原來的環境
做完項目後,這些虛擬機上安裝的東西往往變得沒用了,下個項目組來還是得新申請虛擬機重新部署軟件
-
開發 / 測試 / 現場環境 研發人員在開發環境裏寫好了代碼做好測試後,提交給測試部門,測試人員在測試環境跑起來發現有 BUG,研發人員說在開發環境沒這個 BUG,和測試人員多次扯皮解決 BUG 後發佈版本,發到現場在生產環境部署後,又發現有 BUG,這下輪到工程人員和測試人員扯皮。有時候爲了兼容特殊的現場環境,還需要對代碼進行定製化修改,拉出分支,這樣導致了每次到現場升級都是一場噩夢
-
升級或遷移項目 在每次發版本要升級到現場時,如果現場起了多個 tomcat 應用,那麼需要對每個 tomcat 都先停掉,替換 war 包,然後再起起來,輪流着做,不僅繁瑣而且很容易出錯,如果遇到升級後出現嚴重 BUG,還要手工做回退。另外,如果項目想上雲,那麼在雲上部署後要重新進行一輪測試,如果後面考慮還雲廠商,可能相同的測試還要再進行一次(比如更換了數據存儲組件),費時費力。
總結以上列舉的所有場景,他們存在的一個共同的問題是:沒有一種既能夠屏蔽操作系統差異,又能夠以不降低性能的方式來運行應用的技術,來解決環境依賴的問題。Docker 應運而生。
- Docker 是什麼
Docker 是一種應用容器引擎。首先說一下何爲容器,Linux 系統提供了Namespace
和CGroup
技術實現環境隔離和資源控制,其中 Namespace 是 Linux 提供的一種內核級別環境隔離的方法,能使一個進程和該進程創建的子進程的運行空間都與 Linux 的超級父進程相隔離,注意 Namespace 只能實現運行空間的隔離,物理資源還是所有進程共用的,爲了實現資源隔離,Linux 系統提供了 CGroup 技術來控制一個進程組羣可使用的資源(如 CPU、內存、磁盤 IO 等),把這兩種技術結合起來,就能構造一個用戶空間獨立且限定了資源的對象,這樣的對象稱爲容器。
Linux Container
是 Linux 系統提供的容器化技術,簡稱LXC
,它結合 Namespace 和 CGroup 技術爲用戶提供了更易用的接口來實現容器化。LXC 僅爲一種輕量級的容器化技術,它僅能對部分資源進行限制,無法做到諸如網絡限制、磁盤空間佔用限制等。dotCloud 公司結合 LXC 和以下列出的技術
實現了 Docker 容器引擎,相比於 LXC,Docker 具備更加全面的資源控制能力,是一種應用級別的容器引擎。
-
Chroot:該技術能在 container 裏構造完整的 Linux 文件系統;
-
Veth:該技術能夠在主機上虛擬出一張網卡與 container 裏的 eth0 網卡進行橋接,實現容器與主機、容器之間的網絡通信;
-
UnionFS:聯合文件系統,Docker 利用該技術 “Copy on Write” 的特點實現容器的快速啓動和極少的資源佔用,後面會專門介紹該文件系統;
-
Iptables/netfilter:通過這兩個技術實現控制 container 網絡訪問策略;
-
TC:該技術主要用來做流量隔離,限制帶寬;
-
Quota:該技術用來限制磁盤讀寫空間的大小;
-
Setrlimit:該技術用來限制 container 中打開的進程數,限制打開的文件個數等
也正是因爲 Docker 依賴 Linux 內核的這些技術,至少使用 3.8 或更高版本的內核才能運行 Docker 容器,官方建議使用 3.10 以上的內核版本。
- 與傳統虛擬化技術的區別
傳統的虛擬化技術在虛擬機(VM)和硬件之間加了一個軟件層 Hypervisor,或者叫做虛擬機管理程序。Hypervisor 的運行方式分爲兩類:
- Docker 基本概念
Docker 主要有如下幾個概念:
-
引擎:創建和管理容器的工具,通過讀取鏡像來生成容器,並負責從倉庫拉取鏡像或提交鏡像到倉庫中;
-
鏡像:類似於虛擬機鏡像,一般由一個基本操作系統環境和多個應用程序打包而成,是創建容器的模板;
-
容器:可看作一個簡易版的 Linxu 系統環境(包括 root 用戶權限、進程空間、用戶空間和網絡空間等)以及運行在其中的應用程序打包而成的盒子;
-
倉庫:集中存放鏡像文件的場所,分爲公共倉庫和私有倉庫,目前最大的公共倉庫是官方提供的 Docker Hub,此外國內的阿里雲、騰訊雲等也提供了公共倉庫;
-
宿主機:運行引擎的操作系統所在服務器。
- 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 中如果應用代碼使用了 JDK10 的新特性,基於 JDK8 就無法運行一樣,如果容器內的應用使用了 4.18 版本的內核特性,那麼在 CentOS7(內核版本爲 3.10)啓動容器時,雖然容器能夠啓動,但裏面應用的功能是無法正常運行的,除非把宿主機的操作系統內核升級到 4.18 版本。
- 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 是一個精簡版的操作系統鏡像):
這時候就會有一個問題,應用基於操作系統鏡像來構建,那如果操作系統鏡像本身就很佔空間,豈不是鏡像的分發不方便,而且鏡像倉庫佔用的空間也會很大。有人已經考慮到這一點,針對不同的場景分別構造了不同的操作系統鏡像,下面介紹幾種最常用的系統鏡像。
- Docker 基礎操作系統
以上系統鏡像分別適用於不同的場景:
-
BusyBox:一個極簡版的 Linux 系統,集成了 100 多種常用 Linux 命令,大小不到 2MB,被稱爲 “Linux 系統的瑞士軍刀”,適用於簡單測試場景;
-
Alpine:一個面向安全的輕型 Linux 發行版系統,比 BusyBox 功能更完善,大小不到 5MB,是官網推薦的基礎鏡像,由於其包含了足夠的基礎功能和體積較小,在生產環境中最常用;
-
Debian/Ubuntu:Debian 系列操作系統,功能完善,大小約 170MB,適合研發環境;
-
CentOS/Fedora:都是基於 Redhat 的 Linux 發行版,企業級服務器常用操作系統,穩定性高,大小約 200MB,適合生產環境使用。
- Docker 持久化存儲
根據前面介紹的容器 UnionFS 寫實複製的特點,可知在容器裏增加、刪除或修改文件,其實都是對可寫層裏的文件副本進行了操作。在容器關閉後,該可寫層也會被刪除,對容器的所有修改都會失效,因此需要解決容器內文件持久化的問題。Docker 提供了兩種方案來實現:
- 把宿主機文件系統裏的目錄映射到容器內的目錄,
如下圖所示
。如此一來,容器內在該目錄裏創建的所有文件,都存儲到宿主機的對應目錄中,在關閉容器後,宿主機的目錄依然存在,再次啓動容器時還能讀取到之前創建的文件,因此實現了容器的文件持久化。當然同時要明白,如果是對鏡像自帶文件進行了修改,由於鏡像是隻讀的,該修改操作無法在關閉容器時保存下來,除非在修改了文件後構建一個新的鏡像。
- 把多臺宿主機的磁盤目錄通過網絡聯合爲共享存儲,然後把共享存儲中的特定目錄映射給特定的容器,
如下圖所示
。這樣容器在重啓時,還是能讀取到關閉前創建的文件。生產環境中常用 NFS 作爲共享存儲方案。
- Docker 鏡像製作方法
鏡像製作方法有兩種:
- 通過正在運行的容器生成新鏡像
當一個容器在運行時,在裏面所有的修改都會體現在容器的可寫層,Docker 提供了 commit 命令,可以把正在運行的容器,疊加上可寫層的修改內容,生成一個新鏡像。如上圖所示,在容器裏新安裝 Spark 組件的,如果關閉容器,Spark 組件會隨着可寫層的消失而消失,如果在關閉容器之前使用 commit 命令生成新鏡像,那麼使用新鏡像啓動爲容器時,容器裏就會包含 Spark 組件。
這種方式比較簡單,但無法直觀的設置環境變量、監聽端口等內容,適合在簡單使用的場景運用。
- 通過 Dockerfile 文件來生成新鏡像
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 鏡像。
- Docker 的使用場景
Docker 作爲一種輕量級的虛擬化方案,應用場景十分豐富,下面收集了一些常見的場景:
-
作爲輕量級虛擬機使用 可以使用 Ubuntu 等系統鏡像創建容器,當作虛擬機來使用,相比於傳統虛擬機,啓動速度更快,資源佔用更少,單機可以啓動大量的操作系統容器,方便進行各種測試;
-
作爲雲主機使用 結合 Kubernetes 這樣的容器管理系統,可以在大量服務器上動態分配和管理容器,在公司內部,甚至可以取代 VMWare 這樣的虛擬機管理平臺,使用 Docker 容器作爲雲主機使用;
-
應用服務打包 在 Web 應用服務開發場景,可以把 Java 運行環境、Tomcat 服務器打包爲一個基礎鏡像,在修改了代碼包後加入到基礎鏡像來構建一個新的鏡像,能很方便的升級服務和控制版本;
-
容器雲平臺 CaaS Docker 的出現,使得很多雲平臺供應商開始提供容器雲的服務,簡稱容器即服務 CaaS,以下對比一下 IaaS、PaaS 和 SaaS:
IaaS(基礎設施即服務):提供虛擬機或者其他基礎資源作爲服務提供給用戶。用戶可以從供應商那裏獲得虛擬機或者存儲等資源來裝載相關的應用,同時這些基礎設施的繁瑣的管理工作將由 IaaS 供應商來處理。其主要的用戶是企業的系統管理員和運維人員;
PaaS(平臺即服務):把開發平臺作爲服務提供給用戶。用戶可以在一個包括 SDK,文檔和測試環境等在內的開發平臺上非常方便地編寫應用,而且不論是在部署,或者在運行的時候,用戶都無需爲服務器、操作系統、網絡和存儲等資源的管理操心,這些繁瑣的工作都由 PaaS 供應商負責處理。其主要的用戶是企業開發人員。
SaaS(軟件即服務):將應用作爲服務提供給客戶。用戶只要接上網絡,並通過瀏覽器,就能直接使用在雲端上運行的應用,而不需要顧慮類似安裝等瑣事,並且免去初期高昂的軟硬件投入。SaaS 主要面對的是普通的用戶。
CaaS(容器即服務):完成 IaaS 和 PaaS 兩個層級的功能。相對於傳統的 IaaS 和 PaaS 服務,CaaS 對底層的支持比 PaaS 更靈活,而對上層應用的操控又比 IaaS 更容易。同時因爲 Docker 是比 VM 更細粒度的虛擬化服務,所以能夠對計算資源做到更高效的利用。CaaS 可以部署在任何物理機,虛擬機或 IaaS 雲之上。
-
持續集成和持續部署 互聯網行業提倡敏捷開發,持續集成部署 CI/CD 便是最典型的開發模式。使用 Docker 容器雲平臺,就能實現從代碼編寫完成推送到 Git/SVN 後,自動觸發後端 CaaS 平臺將代碼下載、編譯並構建成測試 Docker 鏡像,再替換測試環境容器服務,自動在 Jenkins 或者 Hudson 中運行單元 / 集成測試,測試通過後,馬上就能自動將新版本鏡像更新到線上,完成服務升級。整個過程全自動化,一氣呵成,最大程度地簡化了運維,而且保證線上、線下環境完全一致,而且線上服務版本與 Git/SVN 發佈分支也實現統一。
-
解決微服務架構的實施難題 基於 Spring Cloud 這樣的微服務框架,能夠實現微服務的管理,但微服務本身還是需要運行在操作系統上。一個採用微服務架構開發的應用中,微服務的個數往往很多,這就導致了一臺服務器上往往需要啓動多個微服務來提高資源的利用率,而微服務本身可能就只能兼容部分操作系統,這就導致了就算有大量的服務器資源(操作系統可能不一樣),但由於微服務本身與操作系統可能相關,就不能做到讓微服務在任意服務器上運行,這就帶來了資源的浪費和運維的困難。利用 Docker 容器的環境隔離能力,讓微服務運行在容器內,就能夠解決以上所說的問題。
-
執行臨時任務 有時候用戶只是想執行一次性的任務,但如果用傳統虛擬機的方式就要搭建環境,執行完任務後還要釋放資源,比較麻煩。使用 Docker 容器就可以構建臨時的運行環境,執行完任務後關閉容器即可,方便快捷。
-
多租戶環境 利用 Docker 的環境隔離能力,可以爲不同的租戶提供獨佔的容器,實現簡單而且成本較低。
- 總結
Docker 的技術並不神祕,只是整合了前人積累的各種成果實現的應用級的容器化技術,它利用各種 Linux 發行版中使用了版本兼容的內核容器化技術,來實現鏡像一次構建到處運行的效果,並且利用了容器內的基礎操作系統鏡像層,屏蔽了實際運行環境的操作系統差異,使用戶在開發應用程序時,只需確保在選定的操作系統和內核版本上能正確運行即可,幾乎不需要關心實際的運行環境的系統差異,大大提高效率和兼容性。
但隨着容器運行得越來越多,容器管理將會稱爲另一個運維的難題,這時候就需要引入 Kubernetes、Mesos 或 Swarm 這些容器管理系統,後面有機會再介紹這些技術。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Gye-_lR4CGYwMS2Ofaml6Q