[譯] 容器,虛擬機以及 Docker 的初學者入門介紹

[譯] 容器,虛擬機以及 Docker 的初學者入門介紹

來自專欄掘金翻譯計劃12 人贊了文章

  • 原文地址:A Beginner-Friendly Introduction to Containers, VMs and Docker
  • 原文作者:Preethi Kasireddy
  • 譯文出自:掘金翻譯計劃
  • 本文永久鏈接:github.com/xitu/gold-mi
  • 譯者:steinliber
  • 校對者:7Ethan, jianboy

如果你是一個開發者或者技術人員,那你肯定或多或少聽說過 Docker:它是一個用於打包,傳輸並且在「容器」中運行應用的工具。它是如此不容忽視,以至於現在無論開發還是運維相關人員都在關注這個工具。甚至像谷歌,VMware 和亞馬遜這樣的大型公司也正在構建支持它的服務。

無論對你來說 Docker 是否能馬上用得到,我都認為理解 「容器」的一些基礎概念以及它和虛擬機(VM)之間的差異是很重要的。雖然互聯網上已經有了大量優秀的 Docker 使用指南,但我沒看到很多適合初學者的概念指南,特別是和容器的構成相關的。所以希望這篇文章可以幫助解決這個問題 :)

讓我們先來理解虛擬機和容器到底是什麼。

什麼是「容器」和「虛擬機」

容器和虛擬機它們的目的很相似:即將應用程序和它的依賴放到一個可以在任何環境運行的自足單元中。

此外,容器和虛擬機消除了對物理硬體的需求,從而在能源消耗和成本效益方面能讓我們更有效地使用計算資源,

容器和虛擬機的主要區別在於它們的架構方式。讓我們繼續深入了解。

虛擬機

虛擬機在本質上是對現實中計算機的模擬,它會像真實的計算機一樣執行程序。使用 「hypervisor」 可以將虛擬機運行於物理機上。hypervisor 可以在主機運行,也可以在「裸機」上運行。

讓我們來揭開這些術語的面紗:

hypervisor(之後都以虛擬機管理程序稱呼)是能讓虛擬機在其上運行的軟體,固件或者硬體。虛擬機管理程序本身會在物理計算機上運行,稱為**「主機」**。主機為虛擬機提供資源,包括 RAM 和 CPU。這些資源在虛擬機之間被劃分並且可以根據需要進行分配。所以如果一個虛擬機上運行了資源佔用更大的應用程序,相較於其它運行在同一個主機的虛擬機你可以給其分配更多的資源。

運行在主機上的虛擬機(再次說明,通過使用虛擬機管理程序)通常也被叫做「訪客機」。訪客機包含了應用以及運行這個應用所需要的全部依賴(比如:系統二進位文件和庫)。它還帶有一個自己的完整虛擬化硬體棧,包括虛擬化的網路適配器,儲存和 CPU-這意味著它還擁有自己成熟的整個訪客操作系統。從虛擬機內部來看,訪客機的操作都認為其使用的都是自己的專用資源。從外部來看,我們知道它是一個虛擬機-和其它虛擬機一起共享主機提供的資源。

就像前面所提到的,訪客機既可以運行在託管的虛擬機管理程序上,也可以運行在裸機虛擬機管理程序上。它們之間存在一些重要的差別。

首先,託管的虛擬化管理程序是在主機的操作系統上運行。比如說,可以在一台運行 OSX 操作系統的計算機的系統上安裝虛擬機(例如:VirtualBox 或者 VMware Workstation 8)。虛擬機無法直接訪問硬體,因此必須通過主機上運行的操作系統訪問(在我們的例子中,也就是 Mac 的 OSX 操作系統)。

託管虛擬機管理程序的好處是底層硬體並不那麼重要。主機的操作系統會負責硬體的驅動而不需要管理程序參與。因此這種方式被認為具備更好的「硬體兼容性」。在另一方面,在硬體和管理程序之間這個額外的附加層會產生更多的資源開銷,這會降低虛擬機的性能。

裸機虛擬機管理程序通過直接在主機硬體上安裝和運行來解決這個性能問題。因為它直接面對底層的硬體,所以並不需要運行在主機的操作系統之上。在這種情況下,安裝在主機上第一個作為操作系統運行的就是這個裸機虛擬機管理程序。與託管虛擬機管理程序不同,它有自己的設備驅動直接與每個組件交互,以執行任何 I/O,處理或特定於操作系統的任務。這樣可以獲得更好的性能,可伸縮性和穩定性。這裡的權衡在於其對硬體的兼容性有限,因為裸機虛擬機管理程序內置的設備驅動只有那麼多。

在討論了虛擬機管理程序之後,你可能想知道為什麼我們需要在虛擬機和主機之間這個額外的「虛擬機管理程序」層。

好吧,虛擬機管理程序在其中確實發揮了重要的作用,由於虛擬機擁有自己的虛擬操作系統,管理程序為虛擬機管理和執行訪客操作系統提供了一個平台。它允許主機與作為客戶端運行的虛擬機之間共享其資源。

虛擬機圖示

正如你可以在圖示中所看到的,VMS 會為每個新的虛擬機打包虛擬硬體,一個內核(即操作系統)和用戶空間。

容器

與提供硬體虛擬化的虛擬機不同,容器通過抽象「用戶空間」來提供操作系統級別的虛擬化。當我們詳解容器這個術語的時候你就會明白我的意思。

從所有的意圖和目的來看,容器看起來就像一個虛擬機。比如說,它們有執行進程的私有空間,可以使用 root 許可權執行命令,具有專有的網路介面和 IP 地址,允許自定義路由和 iptable 規則,可以掛載文件系統等。

容器和虛擬機之間的一個重要區別在於容器和其它容器共享主機系統的內核。

容器圖示

這圖表明容器只會打包用戶空間,而不是像虛擬機那樣打包內核或虛擬硬體。每個容器都有自己獨立的用戶空間從而可以讓多個容器在單個主機上運行。我們可以看到所有操作系統級別的體系架構是所有容器共享的。要從頭開始創建的部分只有 bins 和 libs 目錄。這就是容器如此輕巧的原因。

Docker 是從哪來的?

Docker 是基於 Linux 容器技術的開源項目。它使用 Luinux 的內核功能(如命名空間和控制組)在操作系統上創建容器。

容器已經遠遠不是一個新技術:Google 已經使用他們自己的容器技術好多年了。其它的容器技術包括 Solaris Zones、BSD jails 和 LXC 也已經存在好多年。

那麼為啥 Docker 會突然取得成功呢?

  1. 使用簡單:Docker 使得任何人(開發人員,運維,架構師和其他人)都可以更輕鬆的利用容器的優勢來快速構建和測試可移植的應用程序。它可以讓任何人在他們的筆記本電腦上打包應用程序,不需要任何修改就可以讓應用運行在公有雲,私有雲甚至裸機上。Docker 的口頭禪是:「一次構建,處處運行」。
  2. 速度:Docker 容器非常輕量級和快速。因為容器只是運行在內核上的沙盒環境,因此它們佔用的資源更少。與可能需要更多時間來創建的虛擬機相比,你可以在幾秒鐘內創建一個 Docker 容器,因為虛擬機每次都必須啟動一個完整的操作系統。
  3. Docker Hub:Docker 用戶也可以從日益豐富的 Docker Hub 生態中受益,你可以把 Docker Hub 看作是 「Docker 鏡像的應用商店」。Docker Hub 擁有數萬個由社區構建的公共鏡像,這些鏡像都是隨時可用的。在其中搜索符合你需求的鏡像非常容易,你只需要準備拉取鏡像而且幾乎不需要任何修改。
  4. 模塊化和可擴展性:Docker 可以讓你輕鬆地把應用程序按功能拆分為單個獨立的容器。比如說,你的 Postgre 資料庫可以運行在一個容器中,Redis 服務運行在另一個容器中,而 Node.js 應用運行在另一個容器中。使用 Docker,將這個容器鏈接在一起以創建你的應用程序將會變得更簡單,同時在將來可以很輕鬆地擴展和更新單獨的組件。

最後但並不重要的是,有誰不喜歡 Docker 的鯨魚(Docker 的標誌)呢?:)

來自: docker.com/docker-birth

基礎的 Docker 概念

現在我們已經大致了解了 Docker,讓我們依次講下 Docker 的基礎部分:

Docker Engine

Docker Engine 是 Docker 運行的底層。它是一個輕量級的運行時和工具,可以用於管理容器,鏡像,構建等等。它在 Linux 本機上運行,由以下部分組成:

  1. 在主機上運行的 Docker 守護進程。
  2. Docker 客戶端,用於和 Docker 守護進程通信來執行命令。
  3. 用於遠程和 Docker 守護進程交互的 REST API。

Docker 客戶端

Docker 客戶端是用來和你( Docker 的終端用戶)交互的。可以把它想像成 Docker 的 UI。例如:

你是在和 Docker 客戶端進行交互,然後 Docker 客戶端會把你的指令傳遞給 Docker 守護進程。

docker build iampeekay/someImage .

Docker 守護進程

實際上發送到 Docker 客戶端的命令是由 Docker 守護進程執行(比如像構建,運行和分發容器)。Docker 守護進程在主機上運行,但作為用戶,你並不能直接和守護進程交互。Docker 客戶端也可以在主機上運行,但這並不是必需的。它可以運行在不同的機器上並且與運行在主機上的 Docker 守護進程通信。

Dockerfile

你可以在 Dockerfile 中編寫構建 Docker 鏡像的指令。這些指令可以是:

  • RUN apt-get y install some-package:安裝軟體包
  • EXPOSE 8000:暴露一個埠
  • ENV ANT_HOME /usr/local/apache-ant:傳遞環境變數

更進一步。一旦配置好的你的 Dockfile,就可以使用 docker build 命令從中構建鏡像。這裡是 Dockerfile 的一個例子:

簡單的 Dockerfile:

# 構建基於 ubuntu 14.04 鏡像FROM ubuntu:14.04MAINTAINER preethi kasireddy iam.preethi.k@gmail.com# 用於 SSH 登陸和埠重定向ENV ROOTPASSWORD sample# 在安裝包的過程中關閉提示ENV DEBIAN_FRONTEND noninteractiveRUN echo "debconf shared/accepted-oracle-license-v1-1 select true" | debconf-set-selectionsRUN echo "debconf shared/accepted-oracle-license-v1-1 seen true" | debconf-set-selections# 更新包RUN apt-get -y update# 安裝系統工具/庫RUN apt-get -y install python3-software-properties software-properties-common bzip2 ssh net-tools vim curl expect git nano wget build-essential dialog make build-essential checkinstall bridge-utils virt-viewer python-pip python-setuptools python-dev# 安裝 Node,npmRUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -RUN apt-get install -y nodejs# 把 oracle-jdk7 添加到 Ubuntu 包倉庫RUN add-apt-repository ppa:webupd8team/java# 確保包倉庫是最新的RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list# 更新 aptRUN apt-get -y update# 安裝 oracle-jdk7RUN apt-get -y install oracle-java7-installer# 導出 JAVA_HOME 環境變數ENV JAVA_HOME /usr/lib/jvm/java-7-oracle# 執行 sshdRUN apt-get install -y openssh-serverRUN mkdir /var/run/sshdRUN echo "root:$ROOTPASSWORD" | chpasswdRUN sed -i s/PermitRootLogin without-password/PermitRootLogin yes/ /etc/ssh/sshd_config# SSH 登陸修復。否則用戶將在登陸後被踢出RUN sed s@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g -i /etc/pam.d/sshd# 暴露 Node.js 應用埠EXPOSE 8000# 創建 tap-to-android 應用目錄RUN mkdir -p /usr/src/my-appWORKDIR /usr/src/my-app# 安裝應用依賴COPY . /usr/src/my-appRUN npm install# 添加 entrypoint 執行入口點ADD entrypoint.sh /entrypoint.shRUN chmod +x /entrypoint.shENTRYPOINT ["/entrypoint.sh"]CMD ["npm", "start"]

Docker 鏡像

通過你 Dockerfile 中指令構建的鏡像是一個只讀的模版。鏡像不僅定義了你希望打包的應用程序和其依賴,還有啟動時要運行的進程。

Docker 鏡像是使用 Dockerfile 構建的。Dockerfile 中的每個指令都會為鏡像添加一個新的「鏡像層」,鏡像層表示的是鏡像文件系統中的一部分,可以添加或者替換位於它下面的鏡像層內容。鏡像層是 Docker 輕巧且強大結構的關鍵。Docker 使用 Union 文件系統來實現它:

Union 文件系統

Docker 使用 Union 文件系統來構建一個鏡像。你可以把 Union 文件系統看作是可堆疊文件系統,這意味著不同文件系統(也被認為是分支)中的文件和目錄可以透明的構成一個文件系統。

在重疊分支內擁有相同路徑目錄的內容會被視為單個合併的目錄,這避免了需要為每一層創建單獨副本。相反,它們都被賦予了指向同一個資源的指針;當某些鏡像層需要被更改時,它就會創建一個副本並且修改本地的副本,而原來的鏡像層保持不變。這種方式使得在外部看起來文件系統是可寫的而實際上內部卻並不可寫。(換句話說,就是「寫時複製」系統。)

層級系統主要提供了兩個優點:

  1. 無複製:鏡像層有助於避免每次你使用鏡像創建或者運行容器時複製整套文件,這使 docker 容器實例化非常快速和廉價。
  2. 鏡像層隔離:當你更改一個鏡像時會更快,Docker 更新只會傳播到已改變的鏡像層。

卷是容器的「數據部分」,它會在容器創建的時候初始化。卷允許你持久化並且共享容器中的數據。數據卷與鏡像中默認的 Union 文件系統是分離的,並作為主機文件系統上的普通目錄和文件存在。所以,即使你銷毀,更新或者重新構建你的容器,數據卷也將保持不變。如果想更新數據卷,你也可以直接對其進行更改。(這功能額外的好處在於,數據卷可以在多個容器之間共享和重用,如此簡潔優雅。)

Docker 容器

如上所述,Docker 容器將應用程序的軟體及其運行所需的全部東西打包到了不可見的沙箱中。這包括操作系統,應用代碼,運行時,系統庫等等。Docker 容器是基於 Docker 鏡像構建的。因為鏡像是只讀的,所以 Docker 在鏡像的只讀文件系統上添加了一個讀寫文件系統來創建容器。

來自:Docker

此外,Docker 創建容器還有很多步,它會創建一個網路介面以便容器和本地主機可以通信,再把可用的 IP 地址附加到容器上,並運行定義鏡像時你所指定運行應用程序的進程。

成功創建了容器之後,你可以在任何環境中運行它而無需任何更改。

雙擊「容器」

唷!已經講了好多部分了。有一件事總是讓我感到好奇,那就是實際上容器是如何實現的,特別是容器相關並沒有任何的抽象基礎設施邊界可以參照。經過大量地閱讀之後,這一切都是值得的,所以下面是我嘗試向你們解釋它!:)

「容器」其實只是一個抽象的概念,用於描述不同的功能如何協同從而得到一個可視化的「容器」。讓我們快速瀏覽這些功能:

1) 命名空間

命名空間為容器提供了它們自己的底層 Linux 視圖,限制了容器可以查看和訪問的內容。當你運行一個容器的時候,Docker 會創建這個特定容器將會使用的命名空間。

Docker 使用了內核中提供的幾種不同類型的命名空間,比如說:

a. NET:為容器提供了只有其自己可見的系統網路堆棧(例如,其自己的網路設備、IP 地址、IP 路由表、/proc/net 目錄和埠號等)。 b. PID:PID 表示進程 ID。如果你曾在命令行中運行 ps aux 來檢測系統上正在運行的進程,你將會看到有一列名叫 「PID」。PID 命名空間為容器提供了只有它們自己範圍內可見和交互的進程視圖。包括獨立的 init 進程(PID 1),這個進程是容器內所有進程的「祖先」。 c. MNT:給容器一個自己的系統「掛載」視圖。因此,在不同掛載命名空間的進程具有文件層級結構的不同視圖。 d. UTS:UTS 代表 UNIX 分時系統。它允許進程識別系統標識符(即主機名,域名等)。UTS 讓容器可以有自己的主機名和 NIS 域名,獨立於其它容器和主機系統。 e. IPC:IPC 表示進程間通信。IPC 命名空間負責隔離每個容器中運行進程之間的 IPC 資源。 f. USER:這個命名空間用於隔離每個容器中的用戶。相較於主機系統,它的功能是讓容器具有 uid(用戶 ID)和 gid(組 ID)範圍的不同視圖。因此,進程在用戶命名空間內部的 uid 和 gid 可以和外部主機不同,這就允許在進程在容器外部的 uid 是非特權用戶,而不會犧牲在容器內部進程 uid 的 root 許可權。

Docker 將這些命名空間一起使用來隔離並開始創建容器。下面的功能叫做控制組。

2) 控制組

控制組(也叫做 cgroups)是一種 Linux 內核功能,用於隔離,確定優先順序和統計一組進程的資源使用情況(CPU、內存、磁碟 I/O 和網路等 )。從這個意義上來說,控制組確保 Docker 容器只使用它們需要的資源-如果需要,還可以設置容器可以使用的資源限制。控制組還確保單個容器不會耗盡其中的資源從而導致系統奔潰。

最後,Union 文件系統是 Docker 使用的另一個功能:

3) 隔離的 Union 文件系統:

這個已經在上面 Docker 鏡像部分描述過了:)

這就是 Docker 容器的全部內容(當然,魔鬼在實現細節中-比如如何管理不同組件之間的交互)。

Docker 的未來:Docker 將於虛擬機共存

雖然 Docker 確實獲得了很多支持,但我並不認為它會成為虛擬機真正的威脅。容器將繼續發揮作用,但有很多情況下更適合使用虛擬機。

比如說,如果你需要在多個伺服器上運行多個應用,則使用虛擬機可能是有意義的。另一方面,如果你需要運行單個應用的多個副本,Docker 則能提供一些引人注目的優點。

此外,雖然容器允許你將應用拆分為更多功能獨立的部分從而創建關注點分離,它也意味著需要管理的部件會越來越多,這可能會變得難以處理。

安全性也是 Docker 容器所關注的一個領域-由於容器之間共享內核,容器之間的隔層會更薄。一個完整的虛擬機只能向主機的虛擬機管理程序發出超級調用,但是 Docker 容器卻可以向主機內核發起系統調用,這導致其被攻擊的範圍相比之下會更大。當安全性特別重要時,開發人員可能會選擇由抽象硬體隔離的的虛擬機-這可以使不同虛擬機之間進程的互相干擾變得更加困難。

當然,隨著容器在生產環境中更多使用和用戶的進一步審核,安全和管理等問題肯定會不斷發展。就目前而言,關於容器與虛擬機之間的爭論對於那些每天都接觸它們的人來說真的是最好的。

結論

我希望你現在已經掌握了了解 Docker 所需要的知識,甚至有一天會在項目中使用它。

像往常一樣,無論我在文中有任何錯誤或者您有任何幫助的建議,請在評論下留言!:)

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久鏈接 即為本文在 GitHub 上的 MarkDown 鏈接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。

推薦閱讀:

TAG:Docker | 虛擬化 | 虛擬機 |