初識 Docker 容器技術和相關概念

01Docker是什麼

  Docker是一個開源的軟件,它允許開發人員將應用程序和程序所依賴的庫等運行環境一起打包,構建出容器鏡像後開發人員可以放心的將整個鏡像包移植到任意服務器上運行。由於Docker是連同依賴環境一同打包的,這種打包機制保證本地和雲端環境的高度一致性,同時也避免了之前由於環境不一致導致的各種差異性問題。Docker的出現也是給PaaS世界帶來了“降維打擊”,它的殺手鐗之一就是解決了應用打包這個根本問題,更貼近開發者。而Docker通過這種Build once Run anywhere的能力,對於開發人員可以幫助他們更專注編寫代碼不用擔心運行的系統環境問題,而對於運維人員通過容器的隔離機制和靈活性來完成業務的彈性擴縮和降低資源消耗。

  如果一個新技術得到了廣泛的推廣,那麼技術的推進過程往往是它解決了上一個時代所無法解決的痛點問題,補足了之前的不足。我們來看一下在容器部署時代之前的時代對比就一目瞭然了。

  可以看到容器部署時代主要解決的問題一個是怎麼樣可以頻繁構建,另一個是跨環境的一致性問題。這個與DevOps的理念不謀而合,DevOps所解決的核心問題也是如何使得企業在構建、測試、發佈應用或軟件能夠更加快捷、頻繁、可靠,所以很多時候Docker已經成爲DevOps推動的重要工具之一。

  Docker像虛擬機但不是虛擬機

  剛開始接觸Docker的同學往往把它比作虛擬機,不可否認它和虛擬機確實很像,同樣可以隔離資源,同樣可以有單獨的“操作系統”,我們來看一下虛擬機和Docker的對比圖,如下所示:

  可以看到右圖中畫出了虛擬機的基本結構圖,它通過Hypervisoer這個組件完成虛擬化工作,在它之上模擬出一個個虛擬的操作系統,每個虛擬的操作系統也有各自的虛擬硬件設備,如CPU、內存、I/O設備。而再來看左邊Docker容器的結構圖,Docker不會真正的給你模擬出一個虛擬的操作系統,它是通過Docker引擎這個“黑魔法”來實現上述的功能。服務器硬件還是需要一個Host Operating System操作系統,而Docker起得是輔助作用,在應用啓動的過程中添加一些隔離和限制的方法。也正是由於虛擬機和Docker的本質區別,使得Docker比虛擬化技術佔用的資源更小,啓動的更快。

  那麼Docker應該怎麼理解呢?上文我們提到了應用在啓動過程中Docker給應用添加了一些“黑魔法”,這些“黑魔法”指的就是Namespace和Cgroups,它們分別起到隔離和限制的作用,這些特性也很早就被引入到Linux內核中。

  咱們先來看Namespace,它的主要實現是讓進程有獨立的運行空間,並且對外界是無感知的,通過每個進程有獨立的系統環境達到隔離的效果。在Linux內核中提供了六種Namespace隔離的系統調用,如下所示:

  以其中的PID爲例來驗證一下,首先我們來運行一個Docker容器,可以看到在容器中看到的PID是1,但是在操作系統中真實的PID是5366,通過Namespace這種隔離術讓容器在內部有了一套獨立的運行環境,包括自己的進程、主機名、網絡棧、文件系統等。

  我們來做一個實驗,首先創建一個test.c文件,文件內容如下:

  在代碼中當我們調用clone()中我們指定了CLONE_NEWPID參數,這樣新創建的容器進程就會有一個全新的PID,在它的Namespace中,PID的值是1,我們可以運行這段代碼驗證一下。

  在每個Namespace中的應用進程,都會認爲自己的PID值是1,同時它也看不到物理服務器真正的進程空間,每個進程通過這種方式相互隔離開來。

  接下來我們看一下Docker的另一個特性Cgroup,它的主要作用是將應用進程使用的資源進行限制。Cgroups是control groups的縮寫,它是linux內核的特性,主要的作用是限制、計算和隔離進程的資源使用,包括cpu、內存、磁盤io、網絡等方面。

  Cgroups提供虛擬文件系統作爲進行分組管理和各個子系統設置的用戶接口,首先先了解一些cgourp的概念。

  Task任務,在Cgroups中task任務代表一個系統進程

  Control group控制族羣,control group控制組主要是通過標準對進程的劃分,達到對進程的限定。

  Hierarchy層級,control group可以通過hierarchy組成一個層級樹,下層cgourp繼承父節點的屬性。

  Subsystem子系統,指定資源,比如cpu、內存等的資源調度控制器,負責cgourp下的資源控制功能。

  如下圖所示,cpu和內存兩個子系統都有各自的層級結構,同時又通過task任務調用取得相互的聯繫。

  具體我們來看一下Docker是如何使用Cgroup進行資源限制的,先運行一個容器同時限制它的內存爲100M,執行下面這個命令:

  我們運行了一個nginx的容器,並且使用-memory參數限制了應用的內存爲100M,通過容器的ID反向在/sys/fs/cgroup/中查到系統中cgroup的資源限制。在這個目錄中可以看到所有應用的CPU、內存、網絡I/O等限制情況。

  我們已經講解了Docker的兩個重要武器Namespace和Cgroups,Docker技術使用它們實現應用進程的分隔,以便各自進程獨立運行。同時Docker在打包上使用分層鏡像的模式,使得它能夠輕鬆跨多種環境,解決在部署過程中的程序依賴。爲多環境自動部署應用提供最佳實踐方案。

02

Docker的主要特性

 

  環境一致性

  Docker可以有很好的移植性,由於在容器打包成鏡像的過程中已經解決環境依賴問題,所以可以保證在多種環境下運行的一致性。你的應用可以比作成貨物,由Docker進行打包封裝到集裝箱裏,遷移到任何環境貨物的本質是不會改變的。無論是本地環境還是多種雲端環境,Docker都可以融入其中並有很好的可一致性。現在微軟也越來越擁抱Docker,無論是Azure的支持還是對dotnet應用編譯打包等。

  基於鏡像的版本管理

  在每個Docker鏡像文件中都包含多個層,這些鏡像層組合在一起構成整個容器鏡像,在構建B鏡像時如果發現B鏡像已經包含A鏡像,那麼B鏡像在構建時可以直接在A鏡像之上進行疊加。我們可以使用公有的容器鏡像倉庫或者私有化部署的鏡像倉庫比如harbor,這樣就可以像使用代碼倉庫已經管理我們的容器。

  快速迭代與發佈

  Docker在開發和發佈的生命週期中,不但能保證各個環境的一致性,而且可以基於鏡像倉庫進行管理,在與CI/CD工具進行集成後可以方便我們進行容器鏡像的發佈和回滾。這樣你的應用整體部署時間可以縮短至幾秒,你可以輕鬆高效的創建和銷燬應用容器。

  快速擴容縮容

  在企業運行場景中,比如一家電商平臺在促銷時間段業務訪問量突增的情況下,通過Docker容器的快速啓動特性,並且配合容器的編排平臺的感知調度,讓業務應用可以彈性擴充滿足高併發下的訪問壓力。

03

學會編寫Dockerfile

 

  在學習Dockerfile語法前,先需要一個Docker的運行環境,可以通過如下方法安裝Docker:

curl -fsSL "https://get.docker.com/" | sh && systemctl enable --now docker

  Dockerfile可以看做是一個Docker鏡像的表述文件,它裏面包含一條條的指令,每一條指令將構建出一個Docker鏡像層,指令的疊加也是鏡像層的疊加最終形成一個完整的Docker鏡像。一個標準的Dockerfile格式如下:

  話不多說,我們先寫一段代碼用golang寫一個web服務端,命名爲main.go

  根據上面的代碼我們來編寫一個Dockerfile將代碼封裝到容器鏡像裏。具體的Dockerfile可以是如下內容:

  我們來看這段Dockerfile,第一行使用FROM來聲明這個容器鏡像使用的基礎鏡像,比如我們在這裏引用了centos7這個基礎鏡像。第二行中MAINTAINER是來聲明我們這個容器鏡像的作者信息。而後面的RUN命令是在這個基礎鏡像中執行的linux命令,每RUN一行都會在當前鏡像中創建一個新的鏡像層並依次疊加。最後的CMD命令是聲明在容器啓動的默認命令。可以看到Dockerfile文件其實就是申明瞭一個應用在運行時需要的所有依賴環境和信息。你可以把這個Dockerfile比作一個你想要的汽車的裝配清單,你把這個清單放在任何一個汽車工廠像Docker引擎上都可以裝配出你期望的那個汽車來。我們可以根據這個Dockerfile來編譯出我們想要的容器應用鏡像,如下:

docker build -t golang-example:1.1 .

  可以看到我們通過上述的Dockerfile編譯出golang-example這個容器鏡像,在編譯過程中正如我們之前所說的每個命令都會在容器鏡像中新建一個層級,最終的鏡像ID是378f604a2012

05

來,運行一個的容器

 

  在上一個章節中我們創建了一個名稱爲golang-example版本1.1的容器鏡像,接下來我們嘗試在宿主機上運行這個容器鏡像,命令如下:

docker run -it --rm -p 8080:8080  golang-example:1.1

  這裏的-p參數指的是映射端口,讓你的容器裏應用運行端口和宿主機上端口做映射,使得用戶可以通過宿主機的8080端口進行訪問容器。我們可以通過curl命令測試一下應用是否正常啓動。

  我們可以非常便捷的啓動一個容器,同時可以使用docker run -help去查詢裏面的運行參數,讓我們制定容器啓動的限制和網絡環境等。

06

在工作中我們使用容器的姿勢

 

  在我們實際工作中,怎麼設計一個Dockerfile呢?下面有一些建議以供大家參考:

  1)一個容器一個應用,雖然你可以將多個應用封裝到一個容器裏,但是不建議這麼操作,這樣的話會導致應用鏡像的體積越來越大,構建時間也會變長,應用進程管理和殭屍進程等問題。

  2)將多條RUN命令合併,前面有講到每條RUN命令都會新建一層,如果一個Dockerfile中包含過多的RUN命令,可以通過&&將多個RUN合併成一條,這樣會大大減小容器鏡像的體積。

  3)使用alpine作爲基礎鏡像,隨着容器技術的火熱,alpine這個linux操作系統也映入大家的視線,它比centos、Ubuntu等容器鏡像要小很多,使用alpine作爲基礎鏡像編譯出來的容器鏡像要小很多。

  4)使用exec模式避免使用shell模式,在CMD和ENTRYPOINT中推薦使用exec模式(CMD [“command”,“param1”,“param2”])避免使用shell模式(CMD command param1 param2)。shell模式會調用shell進程去執行命令,它會成爲/bin/sh -c的子命令。而exec模式不需要shell進程,可以接收Unix信號,比如執行docker stop時可以接收SIGTERM信號,可以讓進程安全退出。

  5)使用多段FROM進行構建,在Docker版本17以後可以使用多段FROM進行構建,這樣就可以在運行環境中不摻雜編譯環境,從而減小容器鏡像的體積,如下:

閉環。

  本章節我們就介紹到這裏,下一章節我們將帶來《kubernetes in 5 mins》精彩內容,歡迎繼續關注!

 

 

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