技術選型之 Docker 容器引擎

mp.weixin.qq.com/s/OKb6jbOFM…

做者:huashioupython

segmentfault.com/a/1190000019462392算法


題外話docker


最近對Docker和Kubernetes進行了一番學習,前兩天作了一次技術分享,回去聽了一遍本身演講的錄音,發現單單PPT作好仍是遠遠不夠的,沒有提早準備好邏輯嚴謹的講稿,在講的時候出現了卡殼、漏掉技術點、邏輯矛盾的問題。爲了解決這個問題,我打算之後在作技術分享前,都按着PPT的內容先寫成博客,理順表達邏輯。另外,我以爲每次技術分享使用的PPT都應該儘量的作好,由於你不知道將來會不會還要拿來再講幾遍。本文以PPT+講稿的方式編寫,權當對本身此次技術分享作個記錄,歡迎你們拍磚。ubuntu


1. Docker出現的背景segmentfault


在日常的研發和項目場景中,如下狀況廣泛存在:
後端


  • 我的開發環境瀏覽器

    爲了作大數據相關項目,須要安裝一套CDH集羣,常見的作法是在本身電腦裏搭建3臺與CDH版本對應的虛擬機,把CDH集羣裝起來後,考慮到之後頗有可能還要使用一個乾淨的CDH集羣,爲了不之後重複安裝環境,一般會對整套CDH集羣作一個備份,這樣電腦裏就有6個虛擬機鏡像了。另外,後面在學習其餘技術時,好比學習Ambari大數據集羣,那麼爲了避免破壞已有的虛擬機環境,又要從新搭建3臺虛擬機,本機磁盤很快被一大堆的虛擬機鏡像佔滿。tomcat

  • 公司內部開發環境安全

    公司裏每每會以小團隊的方式來作項目,通常由運維部門從他們管理的服務器資源中分配出虛擬機供團隊內部開發測試使用。好比作一個與機器學習相關的項目:bash


  • 小明在運維部門分配的虛擬機上搭建了一套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能夠把多個物理位置獨立的目錄(也叫分支)內容聯合掛載到同一個目錄下,UnionFS容許控制這些目錄的讀寫權限,此外對於只讀的文件和目錄,它具備「Copy on Write(寫實複製)」的特色,即若是對一個只讀的文件進行修改,在修改前會先把文件複製一份到可寫層(多是磁盤裏的一個目錄),全部的修改操做其實都是對這個文件副本進行修改,原來的只讀文件並不會變化。UnionFS有不少種,其中Docker中經常使用的是AUFS,這是UnionFS的升級版,除此以外還有DeviceMapper、Overlay二、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這些容器管理系統,後面有機會再介紹這些技術。

相關文章
相關標籤/搜索