題外話
最近對Docker
和Kubernetes
進行了一番學習,前兩天做了一次技術分享,回去聽了一遍自己演講的錄音,發現單單PPT做好還是遠遠不夠的,沒有提前準備好邏輯嚴謹的講稿,在講的時候出現了卡殼、漏掉技術點、邏輯矛盾的問題。為了解決這個問題,我打算以後在做技術分享前,都按著PPT的內容先寫成部落格,理順表達邏輯。另外,我覺得每次技術分享使用的PPT都應該儘可能的做好,因為你不知道未來會不會還要拿來再講幾遍。本文以PPT+講稿的方式編寫,權當對自己這次技術分享做個記錄,歡迎大家拍磚。
1. Docker出現的背景
在平常的研發和專案場景中,以下情況普遍存在:
-
個人開發環境
為了做大資料相關專案,需要安裝一套CDH叢集,常見的做法是在自己電腦裡搭建3臺與CDH版本對應的虛擬機器,把CDH叢集裝起來後,考慮到以後很有可能還要使用一個乾淨的CDH叢集,為了避免以後重複安裝環境,通常會對整套CDH叢集做一個備份,這樣電腦裡就有6個虛擬機器映象了。另外,後面在學習其他技術時,比如學習Ambari大資料叢集,那麼為了不破壞已有的虛擬機器環境,又要重新搭建3臺虛擬機器,本機磁碟很快被一大堆的虛擬機器映象佔滿。 -
公司內部開發環境
公司裡往往會以小團隊的方式來做專案,一般由運維部門從他們管理的伺服器資源中分配出虛擬機器供團隊內部開發測試使用。比如做一個與機器學習相關的專案:- 小明在運維部門分配的虛擬機器上搭建了一套Ambari叢集,拿來跑大資料相關業務
- 小剛用python3寫了一個機器學習演算法,放到虛擬機器上執行發現虛擬機器裡是python2,演算法不相容,於是把虛擬機器裡的python版本升級了,演算法跑通了,但Ambari用到python的部分功能可能就報錯了
- 小李開發了應用,放到虛擬機器上啟動tomcat,發現虛擬機器裡的是OpenJDK,導致tomcat起不來,於是又安裝了一個JDK,這時候可能Ambari裡的Java程式碼可能就報錯了
- 小趙想利用伺服器資源做效能測試,發現虛擬機器嚴重削減了效能,最終還是要直接找物理機來跑測試,破壞了物理機原來的環境
- 做完專案後,這些虛擬機器上安裝的東西往往變得沒用了,下個專案組來還是得新申請虛擬機器重新部署軟體
-
開發/測試/現場環境
研發人員在開發環境裡寫好了程式碼做好測試後,提交給測試部門,測試人員在測試環境跑起來發現有BUG,研發人員說在開發環境沒這個BUG,和測試人員多次扯皮解決BUG後釋出版本,發到現場在生產環境部署後,又發現有BUG,這下輪到工程人員和測試人員扯皮。有時候為了相容特殊的現場環境,還需要對程式碼進行定製化修改,拉出分支,這樣導致了每次到現場升級都是一場噩夢 -
升級或遷移專案
在每次發版本要升級到現場時,如果現場起了多個tomcat應用,那麼需要對每個tomcat都先停掉,替換war包,然後再起起來,輪流著做,不僅繁瑣而且很容易出錯,如果遇到升級後出現嚴重BUG,還要手工做回退。另外,如果專案想上雲,那麼在雲上部署後要重新進行一輪測試,如果後面考慮還雲廠商,可能相同的測試還要再進行一次(比如更換了資料儲存元件),費時費力。
總結以上列舉的所有場景,他們存在的一個共同的問題是:沒有一種既能夠遮蔽作業系統差異,又能夠以不降低效能的方式來執行應用的技術,來解決環境依賴的問題。Docker應運而生。
2. 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以上的核心版本。
3. 與傳統虛擬化技術的區別
傳統的虛擬化技術在虛擬機器(VM)和硬體之間加了一個軟體層Hypervisor,或者叫做虛擬機器管理程式。Hypervisor的執行方式分為兩類:
- 直接執行在物理硬體之上。如基於核心的KVM虛擬機器,這種虛擬化需要CPU支援虛擬化技術;
- 執行在另一個作業系統。如VMWare和VitrualBox等虛擬機器。
因為執行在虛擬機器上的作業系統是通過Hypervisor來最終分享硬體,所以虛擬機器Guest OS發出的指令都需要被Hypervisor捕獲,然後翻譯為物理硬體或宿主機作業系統能夠識別的指令。VMWare和VirtualBox等虛擬機器在效能方面遠不如裸機,但基於硬體虛擬機器的KVM約能發揮裸機80%的效能。這種虛擬化的優點是不同虛擬機器之間實現了完全隔離,安全性很高,並且能夠在一臺物理機上執行多種核心的作業系統(如Linux和Window),但每個虛擬機器都很笨重,佔用資源多而且啟動很慢。
Docker引擎執行在作業系統上,是基於核心的LXC、Chroot等技術實現容器的環境隔離和資源控制,在容器啟動後,容器裡的程式直接與核心互動,無需經過Docker引擎中轉,因此幾乎沒有效能損耗,能發揮出裸機的全部效能。但由於Docker是基於Linux核心技術實現容器化的,因此使得容器內執行的應用只能執行在Linux核心的作業系統上。目前在Window上安裝的docker引擎其實是利用了Window自帶的Hyper-V虛擬化工具自動建立了一個Linux系統,容器內的操作實際上是間接使用這個虛擬系統實現的。
4. Docker基本概念
Docker主要有如下幾個概念:
- 引擎:建立和管理容器的工具,通過讀取映象來生成容器,並負責從倉庫拉取映象或提交映象到倉庫中;
- 映象:類似於虛擬機器映象,一般由一個基本作業系統環境和多個應用程式打包而成,是建立容器的模板;
- 容器:可看作一個簡易版的Linxu系統環境(包括root使用者許可權、程式空間、使用者空間和網路空間等)以及執行在其中的應用程式打包而成的盒子;
- 倉庫:集中存放映象檔案的場所,分為公共倉庫和私有倉庫,目前最大的公共倉庫是官方提供的Docker Hub,此外國內的阿里雲、騰訊雲等也提供了公共倉庫;
- 宿主機:執行引擎的作業系統所在伺服器。
5. 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版本。
6. 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是一個精簡版的作業系統映象):
這時候就會有一個問題,應用基於作業系統映象來構建,那如果作業系統映象本身就很佔空間,豈不是映象的分發不方便,而且映象倉庫佔用的空間也會很大。有人已經考慮到這一點,針對不同的場景分別構造了不同的作業系統映象,下面介紹幾種最常用的系統映象。
7. Docker基礎作業系統
以上系統映象分別適用於不同的場景:
- BusyBox:一個極簡版的Linux系統,整合了100多種常用Linux命令,大小不到2MB,被稱為“Linux系統的瑞士軍刀”,適用於簡單測試場景;
- Alpine:一個面向安全的輕型Linux發行版系統,比BusyBox功能更完善,大小不到5MB,是官網推薦的基礎映象,由於其包含了足夠的基礎功能和體積較小,在生產環境中最常用;
- Debian/Ubuntu: Debian系列作業系統,功能完善,大小約170MB,適合研發環境;
- CentOS/Fedora:都是基於Redhat的Linux發行版,企業級伺服器常用作業系統,穩定性高,大小約200MB,適合生產環境使用。
8. Docker持久化儲存
根據前面介紹的容器UnionFS寫實複製的特點,可知在容器裡增加、刪除或修改檔案,其實都是對可寫層裡的檔案副本進行了操作。在容器關閉後,該可寫層也會被刪除,對容器的所有修改都會失效,因此需要解決容器內檔案持久化的問題。Docker提供了兩種方案來實現:
- 把宿主機檔案系統裡的目錄對映到容器內的目錄,
如下圖所示
。如此一來,容器內在該目錄裡建立的所有檔案,都儲存到宿主機的對應目錄中,在關閉容器後,宿主機的目錄依然存在,再次啟動容器時還能讀取到之前建立的檔案,因此實現了容器的檔案持久化。當然同時要明白,如果是對映象自帶檔案進行了修改,由於映象是隻讀的,該修改操作無法在關閉容器時儲存下來,除非在修改了檔案後構建一個新的映象。
- 把多臺宿主機的磁碟目錄通過網路聯合為共享儲存,然後把共享儲存中的特定目錄對映給特定的容器,
如下圖所示
。這樣容器在重啟時,還是能讀取到關閉前建立的檔案。生產環境中常用NFS作為共享儲存方案。
9. 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映象。
10. 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的環境隔離能力,可以為不同的租戶提供獨佔的容器,實現簡單而且成本較低。
11. 總結
Docker的技術並不神祕,只是整合了前人積累的各種成果實現的應用級的容器化技術,它利用各種Linux發行版中使用了版本相容的核心容器化技術,來實現映象一次構建到處執行的效果,並且利用了容器內的基礎作業系統映象層,遮蔽了實際執行環境的作業系統差異,使使用者在開發應用程式時,只需確保在選定的作業系統和核心版本上能正確執行即可,幾乎不需要關心實際的執行環境的系統差異,大大提高效率和相容性。但隨著容器執行得越來越多,容器管理將會稱為另一個運維的難題,這時候就需要引入Kubernetes、Mesos或Swarm這些容器管理系統,後面有機會再介紹這些技術。