天天看點

我的碎碎念:Docker入門指南

我的碎碎念:Docker入門指南

相比很多人的解釋,我相信說Docker是一個輕量級的虛拟機更容易了解。另外一種解釋是:Docker就是作業系統中的<code>chroot</code>。如果你不知道<code>chroot</code>是什麼的話,後一種解釋可能無法幫助你了解什麼是Docker。

-- Arch Linux 的 wiki 中對 chroot 的解釋

下面這張圖描述了虛拟機和Docker之間的差異。 在VM中,宿主OS上是hypervisor(虛拟機螢幕), 最上層是客戶機作業系統,而Docker則使用Docker引擎和容器。 這樣解釋你能了解嗎? Docker引擎和hypervisor之間的差別又是什麼呢?你可以列出運作在宿主OS上的程序來了解它們的差別。

我的碎碎念:Docker入門指南

下面這個簡單的程序樹可以看出它們的差異。雖然虛拟機中運作了很多程序,但是運作虛拟機的主控端上卻隻有一個程序。

而運作Docker引擎的主機上則可以看到所有的程序。 容器程序是運作在宿主OS上的!,他們可以通過普通的<code>ps</code>,<code>kill</code>等指令進行檢查和維護。

所有的東西都是透明的, 意味着什麼呢?意味着Docker容器比虛拟機更小,更快,更容易與其它東西內建。如下圖所示。

我的碎碎念:Docker入門指南

安裝CoreOS的小型虛拟機居然有1.2GB, 而裝上busybox的小型容器隻有2.5MB。最快的虛拟機啟動時間也是分鐘級的,而容器的啟動時間通常不到一秒。在同一主控端上安裝虛拟機需要正确的設定網絡, 而安裝Docker非常簡單。

這麼來看,容器是輕量、快速并且易內建,但這并不是它的全部!

Docker還是開發者和運維之間的“合約”。 開發和運維在選擇工具和環境時的姿态通常差别很大。開發者想要使用一些閃亮的新東西,比如Node.js、Rust、Go、微服務、Cassandra、Hadoop、blablabla.........而運維則傾向于使用以往用過的工具,因為事實證明那些舊的工具很有效。

但這恰恰是Docker的亮點, 運維喜歡它,因為Docker讓他們隻要關心一件事: 部署容器, 而開發者也一樣很開心,隻要寫好代碼,然後往容器裡一扔,剩下的交給運維就完事了。

我的碎碎念:Docker入門指南

不過别急,這還沒完。運維還能幫助開發者建構優化好的容器以便用于本地開發。

很多年前,那時候還沒有虛拟化,當我們需要建立一個新服務時,我們必須申請實際的實體機硬體。 這可能要花上數月,依賴于公司的流程。一旦伺服器到位,我們建立好服務,很多時候它并沒有像我們希望的那樣成功,因為伺服器的CPU使用率隻有5%。 太奢侈了。 

接着,虛拟化來了。它可以在幾分鐘之内把一台機器運轉起來,還可以在同一硬體上運作多個虛拟機,資源使用率就不隻5%了。但是,我們還需要給每個服務配置設定一個虛拟機,是以我們還是不能如願的使用這台機器。

容器化是演化程序的下一步。容器可以在幾秒之内建立起來,而且還能以比虛拟機更小的粒度進行部署。

我的碎碎念:Docker入門指南

Docker啟動速度真的很酷。 但是,我們為什麼不把所有的都服務部署到同一台機器上呢? 原因很簡單:依賴的問題。在同一台機器上安裝多個獨立的服務,不管是真是機器還是虛拟機都是一場災難。用Docker公司的說法是:地獄一樣的矩陣依賴。

而Docker通過在容器中保留依賴關系解決了矩陣依賴的問題。

我的碎碎念:Docker入門指南

快當然不錯,但是能快100倍就太不可思議了。速度讓很多事情成為可能,增加了更多新的可能性。比如,現在可以快速建立新的環境,如果需要從Clojure開發環境完整的切換到Go語言嗎?啟動一個容器吧。需要為內建和性能測試提供生産環境DB ?啟動一個容器吧! 需要從Apache切換整個生産環境到Nginx?啟動容器吧!

Docker是一個Client-Server結構的系統,Docker守護程序運作在主機上, 然後通過Socket連接配接從用戶端通路, 用戶端和守護程序也可以運作再同一主機上,但這不是必須的。Docker指令行用戶端也是類似的工作方式,但它通常通過Unix域套接字而不是TCP套接字連接配接。

守護程序從用戶端接受指令并管理運作在主機上的容器。

我的碎碎念:Docker入門指南

主機, 運作容器的機器。

鏡像,檔案的層次結構,以及包含如何運作容器的中繼資料

容器,一個從鏡像中啟動,包含正在運作的程式的程序

Registry, 鏡像倉庫

卷,容器外的存儲

Dockerfile, 用于建立鏡像的腳本

我的碎碎念:Docker入門指南

我們可以通過<code>Dockerfile</code>來建構鏡像, 還可以通過<code>commit</code>一個運作的容器來建立一個鏡像,這個鏡像可以會被标記,可以推到Registry或者從Registry上拉下來,可以通過建立或者運作鏡像的方式來啟動容器,可以被<code>stop</code>,也可以通過<code>rm</code>來移除它。

鏡像是一種檔案結構,包含如何運作容器的中繼資料。Dockerfile中的每條指令都會在檔案系統中建立一個新的層次結構,檔案系統在這些層次上建構起來,鏡像就建構于這些聯合的檔案系統之上。

我的碎碎念:Docker入門指南

當容器啟動後,所有鏡像都會統一合并到一個程序中。 聯合檔案系統中的檔案被删除時, 它們隻是被标記為已删除,但實際上仍然存在。

這是一些經常使用的鏡像相關的資料:

scratch - 基礎鏡像, 0個檔案,大小為0

busybox - 最小Unix系統,2.5MB,10000個檔案

debian:jessie - Debian最新版, 122MB, 18000 個檔案

ubuntu:14.04 - 188MB,23000 個檔案

可以通過<code>docker commit container-id</code>、<code>docker import url-to-tar</code>或者<code>docker build -f Dockerfile .</code>來建立鏡像。

先看commit的方式:

從上面可以看出,我們可以通過<code>docker commit</code>來建立鏡像,但是這種方式有點淩亂而且很難複制, 更好的方式是通過Dockerfile來建構鏡像,因為它步驟清晰并且容易複制:

然後用下面的指令來建構:

Dockerfile中的每一個指令都建立了新版的layer,通常把類似的指令放在一起,通過&amp;&amp;和續行符号把指令組合起來:

這些行中指令的順序很重要,因為Docker為了加速鏡像的建構,會緩存中間的鏡像。 組織Dockerfile的順序時,注意把經常變化的行放在檔案的底部,當緩存中相關的檔案改變時,鏡像會重新運作,即使Dockerfile中的行沒有發生變化也是如此。

Dockerfile 支援13個指令, 其中一些指令用于建構鏡像,另外一些用于從鏡像中運作容器,這是一個關于指令什麼時候被用到的表格:

我的碎碎念:Docker入門指南

FROM - 新鏡像是基于哪個鏡像的

MAINTAINER - 鏡像維護者的姓名和郵箱位址

COPY - 拷貝檔案和目錄到鏡像中

ADD - 同COPY一樣,但會自動處理URL和解壓tarball壓縮包

RUN - 在容器中運作一個指令, 比如:<code>apt-get install</code>

ONBUILD - 當建構一個被繼承的Dockerfile時運作指令

.dockerignore - 不是一個指令, 但它能控制什麼檔案被加入到建構的上下文中,建構鏡像時應該包含.git以及其它的不需要的檔案。

CMD - 運作容器時的預設指令,可以被指令行參數覆寫

ENV - 設定容器内的環境變量

EXPOSE - 從容器中暴露出端口, 必須顯式的通過在主機上的RUN指令帶上-p或者-P來暴露端口

VOLUME - 指定一個在檔案系統之後的存儲目錄。如果不是通過<code>docker run -v</code>設定的, 那麼将被建立為<code>/var/lib/docker/volumes</code>

ENTRYPOINT - 指定一個指令不會被<code>docker run image cmd</code>指令覆寫。常用于提供一個預設的可執行程式并使用指令作為參數。

USER - 為RUN、CMD、ENTRYPOINT指令設定使用者

WORKDIR - 為RUN、CMD、ENTRYPOINT、ADD、COPY指令設定工作目錄

我的碎碎念:Docker入門指南

容器啟動後,程序在它可以運作的聯合檔案系統中獲得了新的可寫層。

從1.5版本起,它還可以讓最頂層的layer設定為隻讀,強制我們為所有檔案輸出(如日志、臨時檔案)使用卷。

如上所述, <code>docker run</code>是使用者啟動新容器的指令, 這裡是一些通用的運作容器的方法:

我的碎碎念:Docker入門指南

這是一個可以讓你像普通的終端程式一樣互動式的運作容器的方法, 如果你想把管道輸出到容器中,可以使用-t選項。

--interactive (-i) - 将标準輸入發送給程序

-tty (-t) - 告訴程序有終端連接配接。 這個功能會影響程式的輸出和它如何處理Ctrx-C等信号。

--rm - 退出時删除鏡像。

--name - 給容器命名, 否則它是一個随機容器

--env (-e)- 設定容器中的環境變量

--env-file - 從env-file中引入所有環境變量(同Linux下的source env-file 功能)

mysql - 指定鏡像名為 mysql:lastest

nginx 鏡像,比如暴露出80和443端口。

連接配接容器需要設定容器到被連接配接的容器之間的網絡,有兩件事要做:

通過容器的連接配接名,更新 /etc/hosts 。 在上面的例子中,連接配接名是db, 可以友善的通過名字db來通路容器。

為暴露的端口設定環境變量。這個好像沒啥實際用處,你也可以通過 <code>主機名:端口</code>的形式通路對應的端口。

還可以通過run limits來限制容器可以使用的主機資源

設定CPU份數為1024中的512份并不意味着可以使用一半的CPU資源,這意味着在一個無任何限制的容器中,它最多可以使用一半的份數。比如我們有兩個有1024份的容器,和一個512份的容器(1024:1024:512) ,那麼512份的那個容器,就隻能得到1/5的總CPU份數

<code>docker exec</code> 允許我們在已經運作的容器内部執行指令,這點在debug的時候很有用。

我的碎碎念:Docker入門指南

卷提供容器外的持久存儲。 這意味着如果你送出了新的鏡像,資料将不會被儲存。

如果目錄不存在,則會被自動建立為:/var/lib/docker/valumes/ec3c543bc..535

實際的目錄名可以通過指令:<code>docker inspect container-id</code> 找到。

還可以使用<code>--valumes-from</code>選項從别的容器中挂載卷。

Docker Hub是Docker的官方鏡像倉庫,支援私有庫和共有庫,倉庫可以被标記為官方倉庫,意味着它由該項目的維護者(或跟它有關的人)策劃。 

Docker Hub 還支援自動化建構來自Github和Bitbucket的項目,如果啟用自動建構功能,那麼每次你送出代碼到代碼庫都會自動建構鏡像。

即使你不想用自動建構,你還是可以直接<code>docker push</code>到Docker Hub,Docker pull則會拉取鏡像下來。<code>docker run</code> 一個本地不存在的鏡像,則會自動開始<code>docker pull</code>操作。 

此外,Quay、Tutum和Google 還提供私有鏡像托管服務。

檢查容器的指令有一大把:

下面詳細講一下<code>docker ps</code> 和<code>docker inspect</code>,這兩個指令最常用了。

擷取容器id。寫腳本時很有用。

<code>docker inspect</code>可以帶格式化的字元串----Go語言模闆作為參數,較長的描述所需的資料。寫腳本時同時有用。

使用<code>docker exec</code>來跟運作中的容器進行互動。

通過卷來避免每次運作時都重建鏡像, 下面是一個Dockerfile,每次建構時,會拷貝目前目錄到容器中。

建構并運作鏡像:

為避免重建,建立一次性鏡像并在運作時挂載本地目錄。

我的碎碎念:Docker入門指南

大家可能聽說過使用Docker不那麼安全。這不是假話,但這不成問題。 

目前Docker存在以下安全問題:

鏡像簽名未被正确的核準。

如果你在容器中擁有root權限,那你潛在的擁有對真個主機的root權限。

安全解決辦法:

從你的私有倉庫中使用受信任的鏡像

盡量不要以root運作容器

把容器中的root當作是主機上的root? 還是把容器的根目錄設定為容器内的根目錄 ?

如果伺服器上所有的容器都是你的,那你不需要擔心他們之間會有危險的互動。

我給選擇兩字加了引号, 因為目前根本沒有任何别的選擇, 但是很多容器愛好者想玩玩,比如Ubuntu的LXD、微軟的Drawbridge,還有Rocket。

Rocket由CoreOS開發,CoreOS是一個很大的容器平台。 他們開發Rocket的理由是覺得Docker公司讓Docker變得臃腫,并且還和CoreOS有業務沖突。

我的碎碎念:Docker入門指南

當我們把應用程式拆開到多個不同的容器中時,會産生一些新的問題。怎麼讓不同的部分進行通信呢? 這些容器在單個主機上怎麼辦? 多個主機上又是怎麼處理? 

單個主機上,Docker通過連接配接來解決編排的問題。 

為簡化容器的連結操作,Docker提供了一個叫<code>docker-compose</code>的工具。(以前它叫<code>fig</code>, 由另一家公司開發,然後最近Docker收購了他們)

我的碎碎念:Docker入門指南

<code>docker-compose</code>在單個<code>docker-compose.yml</code>檔案中聲明多個容器的資訊。來看一個例子,管理web和redis兩個容器的配置檔案:

啟動上述容器,可以使用<code>docker-compose up</code>指令

也可以通過detached模式(detached mode)啟動: <code>docker-compose up -d</code>,然後可以通過<code>docker-compose ps</code>檢視容器中跑了啥東西:

還可以同時讓指令在一個容器或者多個容器中同時工作。

從以上指令可以看出,擴充很容易,不過應用程式必須寫成支援處理多個容器的方式。在容器外,不支援負載均衡。

很多公司想做在雲中托管Docker的生意,如下圖。

我的碎碎念:Docker入門指南

這些提供商嘗試解決不同的問題, 從簡單的托管到做"雲作業系統"。其中有兩家比較有前景:

如上圖所示,CoreOS是可以在CoreOS叢集中托管多個容器的一系列服務的集合:

我的碎碎念:Docker入門指南

CoreOS Linux發行版是裁剪的Linux,在初次啟動時使用114MB的RAM,沒有包管理器, 使用Docker或者它自己的Rocket運作一切程式。

CoreOS 使用Docker(或Rocket)在主機上安裝應用。

使用<code>systemd</code>作為init服務,它的性能超級好,還能很好的處理啟動依賴關系, 強大的日志系統,還支援socket-activation。

<code>etcd</code> 是分布式的,一緻性 K-V 存儲用于配置共享和服務發現。

<code>fleet</code>,叢集管理器,是<code>systemd</code>的擴充,能與多台機器工作,采用<code>etcd</code>來管理配置并運作在每一個台CoreOS伺服器上。

Docker容器托管在Amazon有兩種途徑:

Elastic Beanstalk部署Docker容器,它工作的很好,但就是太慢了, 一次全新的部署需要好幾分鐘,感覺跟一般的容器秒級啟動不大對勁。

ECS、Elastic Container Server是Amazon上遊容器叢集解決方案, 目前還在預覽版3,看起來很有前途,跟Amazon其它服務一樣,通過簡單的web service調用與它互動。

Docker is here to stay

解決了依賴問題

容器各方面都很快

有叢集解決方案,但不能無縫對接

=============================

譯者介紹

何林沖, 目前就職于騰訊計算機系統有限公司, 負責遊戲自動化運維體系架構設計及開發工作, 熱愛開源技術。希望通過翻譯技術文章為社群貢獻微薄之力。

原文釋出時間為:2015-03-29

本文作者:何林沖 

本文來自雲栖社群合作夥伴DockerOne,了解相關資訊可以關注DockerOne。

原文标題:我的碎碎念:Docker入門指南

繼續閱讀