構建微服務的開源技術之 Docker
一、前言
Docker 是最近在云計算領域出現的新技術。目前,Docker 和以其為代表的容器技術的熱度已經改過了之前的 OpenStack。Docker 以及其所代表的容器技術的流行,即使因為軟件技術的進步,更是由于其符合云計算對軟件領域所帶來新思想。在如今的互聯網和企業應用開發領域,微服務和 DevOps 是兩個思想頗為深入人心。而 Docker 技術的出現和其對整個容器技術及其生態圈發展的促進,解決了這個微服務和 DevOps 這兩個思想實踐中的很多難題,使得前面兩種思想大規模地實現成為了可能。所以,我們有必要地深入了解一下 Docker 這個技術,看看它會對云計算時代的軟件開發產生什么樣的影響。
本文將介紹如下內容:
- 什么是容器技術
- 什么是 Docker
- Docker 是如何實現的
- 為什么要使用 Docker
至于 Docker 的用法,不在這里做介紹。Docker 的入門使用,可以前往 Docker 官網。各種高級用法、技巧、經驗,可以前往技術網站 CSDN、InfoQ 和 CoreOS、Centurylink Labs 等這些使用 Docker 的云計算廠商的網站。
二、容器技術簡介
容器技術有時會被稱為輕量化虛擬技術。但不同于基于 Hypervisor 的傳統虛擬化技術,容器技術并不會虛擬硬件。容器本身和容器內的進程都是運行在宿主 Linux 系統的內核之上。但與直接運行的進程不同,運行在容器內的進程會被隔離和約束。從而以直接運行的高效實現了虛擬技術的大部分效果。
容器技術的歷史
容器技術并不是一個新鮮事物,早在1979年出現的在 Unix 系統中的 chroot 便是容器技術的雛形。而隨后出現的 BSD Jail、Solaris Containers 和 OpenVZ 都算是容器技術的先驅。但容器技術開始普及卻是在2007年,這一年 Google 貢獻出 cgroups,并且從 2.6.4 開始,Linux 內核包含了這一組件,隨后容器技術開始逐漸普及。但容器技術真正大放異彩則要等到2013年 Docker 0.10 版本發布。
三、Docker 簡介
在 Docker 出現之前,不僅 Google 大量使用容器技術,國內的如淘寶也使用容器技術搭建了自己的應用平臺。影響力最大的開源 PaaS 解決方案 CloudFoundry,也在使用自己的容器解決方案 Warden。而 Docker 發布之后,因其極有可能成為未來企業應用、互聯網應用和云計算應用的開發、部署的中心角色,所以得到了幾乎所有的業界大佬的追捧,Google、VMware、微軟、RedHat 等等都已全力推動 Docker 技術的發展。同時,圍繞 Docker,出現了一系列以 CoreOS 為代表的新技術。
Docker 的出現并非創造了一個新的容器技術,而是在 LXC (LinuX Container)注1、cgroups、namespaces 技術之上所構建的一種技術:
- Docker 簡化了容器的運行:它通過一個簡單的命令就能夠運行起一個容器docker run [params] [image] [command (optional)]
- Docker 簡化了容器鏡像的構建和分發:Docker 提供了Dockerfile和docker commit兩種方式構建鏡像,并且提供了 Docker image registry 機制以保存和分發鏡像
形象地解釋
打一個比方,集裝箱(容器)對于遠洋運輸(應用運行)來說十分重要。集裝箱(容器)能保護貨物(應用),讓其不會相互碰撞(應用沖突)而損壞,也能保障當一些危險貨物發生規模不大的爆炸(應用崩潰)時不會波及其它貨物(應用)但是把貨物(應用)裝載在集裝箱(容器)中并不是一件簡單的事情。而出色的碼頭工人(Docker)的出現解決了這一問題。它(Docker)使得貨物裝載到集裝箱(容器)這一過程變得輕而易舉。對于遠洋運輸(應用運行)而言,用多艘小貨輪(虛擬機)代替原來的大貨輪(實體機)也能保證貨物(應用)彼此之間的安全,但是和集裝箱(容器)比,成本過高,但適合運輸某些重要貨物(應用)。
四、Docker 的組成
Docker 主要有 Docker Hub 和 Docker 引擎組成。前者是Docker 官方提供的容器鏡像倉庫;后者運行在宿主機上,可分為服務器端和客戶端兩部分。服務器端負責構建、運行和分發 Docker 容器等重要工作,客戶端負責接收用戶的命令和服務程序進行通信。
除了這兩部分,Dockerfile 也是不得不提的,它雖然不能算作一個獨立的組件,但是卻是 Docker 中很重要的部分。通過 Dockerfile,技術人員可以創建自己的 Docker 容器鏡像。Dockerfile 起到了連接開發與運維的橋梁的作用,非常符合現在 DevOps 的潮流。
Docker Hub
Docker Hub 是 Docker 官方所提供的一個鏡像倉庫。在運行 Docker 容器或構建自己的容器鏡像時,都會直接或間接地使用到 Docker Hub 中的鏡像。
Docker Engine
Docker Engine 承載了 Docker 容器在宿主機上運行啟停、Docker 鏡像的構建等功能等功能。是我們接觸最多的組件。接下來簡單介紹一下 Docker 常見的命令:
- run運行一個容器,如果鏡像不存在則先下載。常用參數有-d、-t、-i等
- pull下載容器鏡像
- start/stop啟動/停止一個 Docker 容器
- rm刪除一個容器
- rmi刪除一個容器鏡像
- commit將容器中的修改提交至鏡像中
- logs顯示容器運行的控制臺輸出
- build從 Dockerfile 構建一個鏡像
- inspect顯示容器運行參數,通過輸入一個 JSON 格式的值來顯示相應的結果
- images顯示當前宿主機上的所有鏡像
Dockerfile
通過編寫 Dockerfile,我們可以構建自己的鏡像。看一個 Dockerfile 的例子:
FROM dockerfile/java:oracle-java8 MAINTAINER Lifan Yang <yanglifan@gmail.com> ADD device.jar /device.jar EXPOSE 8080 ENTRYPOINT java -jar /device.jar
FROM指令的意思是說你的鏡像是基于一個什么鏡像。dockerfile/java:oracle-java8是一個鏡像的名字,它也是有一個基礎鏡像狗狗見。其實它也是基于例如 Ubuntu、CentOS 這樣的 Base 鏡像。關于 Base 鏡像的制作方法,Docker 官網上有介紹,需要專門的工具,這里不再作介紹。MAINTAINER指令是可選的。ADD指令是用來將一個文件或目錄添加到 Docker 鏡像中,前面是源文件,后面是目標文件。源文件必須使用相對路徑。EXPOSE指令用來容器間暴露端口,其指定的端口也會被-P參數映射給宿主機的一個隨機端口上。ENTRYPOINT可以用來指定運行 Docker 容器時,在容器中執行的命令是什么。如果需要運行多個命令,可以通過 Supervisor 來執行。
除了這些指令,還有一些常用的指令。例如,用于在構建過程中執行命令的CMD指令;用于在容器中設置環境變量的ENV指令。詳細請見 Dockerfile Reference
五、Docker 的實現
接下來要介紹 Docker 所使用的幾個重要技術:namespaces、cgroups、LXC 和 AUFS。
namespaces
Linux 容器通過 Kernel 的 namespaces 技術,為一個或一組進程創建獨立的pid、net等 namespaces,從而與其它進程相互隔離。下面將介紹 namespaces 都會對哪些資源進行分組控制以實現相互隔離:
pidnamespace
不同容器中進程是通過 pid namespace 隔離開的,且不同容器中可以有相同 pid。具有以下特征:
- 每個 namespace 中的 pid 是有自己的 pid=1 的初始進程
- 每個 namespace 中的進程只能影響自己的同一個 namespace 或子 namespace 中的進程
- 因為/proc包含正在運行的進程,因此在容器中的/proc目錄只能看到自己 namespace 中的進程
- 因為 namespace 允許嵌套,父 namespace 可以影響子 namespace 的進程,所以子 namespace 的進程可以在父 namespace 中看到,但是具有不同的 pid
netnamespace
如果僅僅隔離了進程空間,還是會有問題。比如如果你在多個容器中運行 Apache 服務器,那只能有一個服務器使用 80 端口。對此你有兩個選擇,讓每個 Apache 服務器使用不同的端口,或者隔離網絡空間。
netnamespace 使得每個容器都有自己的loloopback 接口。同時,還有一個通常被命名為eth0的網絡接口,通過這個接口,容器可以和 host 或其它容器進行通信。eth0interface 會被分配一個 172.17.0.XXX 的 IP 地址,容器之間可以通過這個 IP 地址相互通信。
圖3:容器內部ifconfig命令查看的結果
同時,容器的這個eth0網卡在 Host 中的名字是一個類似vethdfb7的略顯古怪的名字。這個網卡會和docker0網卡橋接在一起。
ipcnamespace
ipcnamepace 對于不熟悉 Unix 的人(包括之前的我)吸引力不是很大。畢竟,現在進程之間的通信多數是通過網絡實現的。但實際上,Unixipc有著十分廣泛的應用,比如管道就是ipc的一種。對ipc就不做過多介紹了,總之 Linux Kernel namespaces 可以讓不同容器的ipc相互隔離。
mntnamespace
如名所示,mntnamespace 是處理掛載點的。mntnamespace 可以使不同容器擁有不同的掛載的文件系統和 root 目錄。在一個mntnamespace 掛載的文件系統只能被同一個 namespace 里的進程所見。
utsnamespace
utsnamespace 用于控制 hostname 的隔離。
有了以上幾種隔離,一個容器就可以對外展現出一個獨立計算機的能力,并且不同容器內的資源在操作系統層面實現了隔離。 然而不同 namespace 之間資源還是相互競爭的,仍然需要類似 ulimit 來管理每個容器所能使用的資源 - Docker 采用的是 cgroup。
參考文獻
1 http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part
cgroups
namespaces 對進程分組以實現資源隔離,但這隔離還是不夠的。一個進程可以通過占用過度的硬件資源的方式去影響另一個分組中的進程。所以,要想實現完善的資源隔離,不僅要對資源分組,還要能對這一組內的進程所使用的資源進行約束。cgroups 就是用來實現這個目的的。cgroups 的全稱是 Control Groups,在 2003 年由 Google 的工程師實現,在 2007 年加入 Linux Kernel。cgroups 可限制進程對 CPU、內存、塊存儲和網絡的使用。這里不對 cgroups 的使用方法作介紹,感興趣的同學可以參考如下:
雖然 cgroups 提供了對 IO 資源使用的約束的功能,但 Docker 目前(1.3)尚未提供支持。
內存
cgroups 可以控制進程所能使用的內存和 swap 空間的大小。通過-m參數,Docker 可以限制容器所能使用的最大內存數:
docker run -m 128m -d container_img cmd_name
CPU
Docker 可以通過 cgroups 限制容器所能使用的 CPU 資源。在執行docker run命令時可以通過參數--cpu-shares(-c)、--cpuset對 CPU 做出限制。
--cpu-shares(-c)
-c設置的是一個相對值,這個值將影響到此容器內的進程所能使用的 CPU 時間片。新運行的 Docker 容器默認使用 1024,對一個單獨的 Docker 的容器來說,這個值沒有任何意義。當啟動兩個 Docker 容器的時候,這兩個容器將平分 CPU 時間片。如果兩個容器,一個不指定-c,另一個設置為-c 512,那前者將使用大致 2/3 的計算能力,另一個將使用大致 1/3 的計算能力。但是-c對容器所能使用 CPU 的運行頻率等沒有任何影響。
--cpuset
可讓你指定 Docker 容器中的進程運行在第幾塊 CPU 上。后面跟一個數組或用逗號分隔的多個數字0,1,2。比如下列命令將會使你的容器運行在第一個 CPU 核心上。
docker run -i -t --cpuset 0 ubuntu:14.04
注:cgroups 使用的是一種偽文件系統形式的接口。這個偽文件系統實際存在于內存中,但映射在目錄中。用戶通過在這個目錄中寫入文件來對 cgroups 進行操作。
AUFS
注:最新的 Docker 使用 BTRFS 替代 AUFS,但所要實現的功能相同
AUFS 的全稱是 Another Union File System。AUFS(包括其它 UFS)的一個重要能力是能使兩個目錄結構合二為一。這有什么用呢?Docker 的鏡像都是有多個層組成的,最上層是一個可讀寫的,而下面的層則是只讀的。通過 AUFS 的目錄融合的能力,實現了既可隨意讀寫,又保證了下層的內容安全的目的。見下圖可以有一個形象的認識。下圖中的 bootfs 層包含了 Linux Kernel。在其上是某個特定的 Linux 發行版本的不同于 Kernel 的文件層,在下圖中是 Debian。再往上有包含 emacs 和 Apache 的兩個層。這些層在容器運行時都是只讀的。最上面就是容器運行時可讀寫的層了。上面的層可以只讀訪問下面的層里的文件。這樣的層次結構可以通過 Dockerfile 來創建。
那這樣的一個結構有什么樣的好處呢?主要有下面幾點:
節省磁盤和內存的存儲空間
節省磁盤存儲空間是因為不同的容器鏡像之間可以共享相同的層。例如,兩個不同的 Java 應用的容器鏡像,它們都是基于 Ubuntu 14.04 和 JDK7。那在同一臺 Host 中,這兩個鏡像就會使用相同的 Ubuntu 14.04 和 JDK7 的層。
節省內存空間是因為 Linux 為了加快磁盤訪問,會將一些磁盤上的文件加載到內存中。所以,節省磁盤空間的同時也就可以間接地解釋內存的使用。
加快部署速度
同樣,可共享的鏡像層能加快部署速度。因為相同的層不用被重復下載部署。
允許對文件任意改動
上傳可讀寫的層可以對下面的只讀層中的文件做任意修改,但這其實是 copy-on-write,所以,這種修改對下面的層其實是安全的。
六、Docker 的生態圈
Docker 再好,單靠 Docker 自身是無法滿足互聯網和企業應用的各種復雜需求的。好在 Docker 的出現帶動了一些列技術的發展,形成了一個龐大的生態圈。這個生態圈中的產品可大致分為如下幾類:
容器編排管理
以 Google Kubernets 和 Apache Mesos 為代表。主要解決基于容器組成分布式集群應用的管理工作,例如對容器的運行狀態的監控、容器自動化的故障恢復、基于容器的應用的擴容和縮容、服務發現。
基于容器的操作系統
以 CoreOS 和 Redhat Atomic 為代表。它們拋棄了 Linux 上面傳統的包管理機制,而使用 Docker 作為應用的運行平臺。同時精簡系統。CoreOS 還引入了 Ectd、Fleet 等組件以更好地支持分布式系統。
基于容器的平臺
PaaS 平臺不是什么新鮮的概念,但卻一直處于發育不良的狀態。Docker 的出現給 PaaS 的發展帶來了新的機遇,Docker 使得 PaaS 應用的部署有了統一的格式。以 Flynn 和 Deis 為代表的新的 PaaS 技術平臺都是以 Docker 為基礎的。
網絡
如今的一臺服務器可以輕松應付幾百上千的 Docker 容器同時運行在其中。可以想見,在一個服務器集群中的 Docker 容器會有多少。如果對這么多的 Docker 容器所使用的網絡進行組織管理便成為新的挑戰。在這個領域主要有 Pipework、Weave 和 Flannel 等技術
配置管理工具
像 Puppet、Ansible 這樣的配置管理工具早在 Docker 出現之前就已被廣泛使用,但 Docker 的出現給這些技術帶來了新的變化。是否能更好地支持對 Docker 容器集群的配置管理決定了這些技術今后的發展。
七、總結
本文簡單介紹了 Docker 出現的背景、意義,Docker 的組成和背后的技術以及其所帶動的生態圈。但作為一個新出現并在快速發展的基礎性的技術,一兩篇文章顯然只能讓人有一個最基本的認識。同時,任何技術也都有其兩面性,Docker 作為一個新技術在實踐中也存在這個非常多的問題。即便在國外,Docker 的應用也是出于起步階段。所以還有很長的路要走。但是從最近一年多的發展看,Docker 無疑是一個非常有生命力的技術,必定會在今后的一段時間內成為一個熱門、主流的技術。
雖然 Docker 的出現更多地是改變了服務器應用的部署和運維。但作為開發者來說,部署和運維的模型會對開發也產生很重大的影響,而且 Docker 的出現能使開發人員更好地參與到運維中來,這將促使應用更快、更好地迭代和發布。
更重要地是,云計算毫無疑問是未來互聯網和企業應用的發展方向,而 Docker 和其所代表的容器技術將在未來一段時間內成為云計算的基礎性技術。從這個角度來講,Docker 是我們每一個從業人員必須了解甚至熟練掌握的一門技術。