天天看點

Docker 入門教程

2013年釋出至今, Docker 一直廣受矚目,被認為可能會改變軟體行業。

但是,許多人并不清楚 Docker 到底是什麼,要解決什麼問題,好處又在哪裡?本文就來詳細解釋,幫助大家了解它,還帶有簡單易懂的執行個體,教你如何将它用于日常開發。

Docker 入門教程

軟體開發最大的麻煩事之一,就是環境配置。使用者計算機的環境都不相同,你怎麼知道自家的軟體,能在那些機器跑起來?

使用者必須保證兩件事:作業系統的設定,各種庫群組件的安裝。隻有它們都正确,軟體才能運作。舉例來說,安裝一個 Python 應用,計算機必須有 Python 引擎,還必須有各種依賴,可能還要配置環境變量。

如果某些老舊的子產品與目前環境不相容,那就麻煩了。開發者常常會說:"它在我的機器可以跑了"(It works on my machine),言下之意就是,其他機器很可能跑不了。

環境配置如此麻煩,換一台機器,就要重來一次,曠日費時。很多人想到,能不能從根本上解決問題,軟體可以帶環境安裝?也就是說,安裝的時候,把原始環境一模一樣地複制過來。

虛拟機(virtual machine)就是帶環境安裝的一種解決方案。它可以在一種作業系統裡面運作另一種作業系統,比如在 Windows 系統裡面運作 Linux 系統。應用程式對此毫無感覺,因為虛拟機看上去跟真實系統一模一樣,而對于底層系統來說,虛拟機就是一個普通檔案,不需要了就删掉,對其他部分毫無影響。

雖然使用者可以通過虛拟機還原軟體的原始環境。但是,這個方案有幾個缺點。

(1)資源占用多

虛拟機會獨占一部分記憶體和硬碟空間。它運作的時候,其他程式就不能使用這些資源了。哪怕虛拟機裡面的應用程式,真正使用的記憶體隻有 1MB,虛拟機依然需要幾百 MB 的記憶體才能運作。

(2)備援步驟多

虛拟機是完整的作業系統,一些系統級别的操作步驟,往往無法跳過,比如使用者登入。

(3)啟動慢

啟動作業系統需要多久,啟動虛拟機就需要多久。可能要等幾分鐘,應用程式才能真正運作。

由于虛拟機存在這些缺點,Linux 發展出了另一種虛拟化技術:Linux 容器(Linux Containers,縮寫為 LXC)。

Linux 容器不是模拟一個完整的作業系統,而是對程序進行隔離。或者說,在正常程序的外面套了一個保護層。對于容器裡面的程序來說,它接觸到的各種資源都是虛拟的,進而實作與底層系統的隔離。

由于容器是程序級别的,相比虛拟機有很多優勢。

(1)啟動快

容器裡面的應用,直接就是底層系統的一個程序,而不是虛拟機内部的程序。是以,啟動容器相當于啟動本機的一個程序,而不是啟動一個作業系統,速度就快很多。

(2)資源占用少

容器隻占用需要的資源,不占用那些沒有用到的資源;虛拟機由于是完整的作業系統,不可避免要占用所有資源。另外,多個容器可以共享資源,虛拟機都是獨享資源。

(3)體積小

容器隻要包含用到的元件即可,而虛拟機是整個作業系統的打包,是以容器檔案比虛拟機檔案要小很多。

總之,容器有點像輕量級的虛拟機,能夠提供虛拟化的環境,但是成本開銷小得多。

Docker 屬于 Linux 容器的一種封裝,提供簡單易用的容器使用接口。它是目前最流行的 Linux 容器解決方案。

Docker 将應用程式與該程式的依賴,打包在一個檔案裡面。運作這個檔案,就會生成一個虛拟容器。程式在這個虛拟容器裡運作,就好像在真實的實體機上運作一樣。有了 Docker,就不用擔心環境問題。

總體來說,Docker 的接口相當簡單,使用者可以友善地建立和使用容器,把自己的應用放入容器。容器還可以進行版本管理、複制、分享、修改,就像管理普通的代碼一樣。

Docker 的主要用途,目前有三大類。

(1)提供一次性的環境。比如,本地測試他人的軟體、持續內建的時候提供單元測試和建構的環境。

(2)提供彈性的雲服務。因為 Docker 容器可以随開随關,很适合動态擴容和縮容。

(3)組建微服務架構。通過多個容器,一台機器可以跑多個服務,是以在本機就可以模拟出微服務架構。

Docker 是一個開源的商業産品,有兩個版本:社群版(Community Edition,縮寫為 CE)和企業版(Enterprise Edition,縮寫為 EE)。企業版包含了一些收費服務,個人開發者一般用不到。下面的介紹都針對社群版。

Docker CE 的安裝請參考官方文檔。

Mac Windows Ubuntu Debian CentOS Fedora 其他 Linux 發行版

安裝完成後,運作下面的指令,驗證是否安裝成功。

Docker 需要使用者具有 sudo 權限,為了避免每次指令都輸入<code>sudo</code>,可以把使用者加入 Docker 使用者組(官方文檔)。

Docker 是伺服器----用戶端架構。指令行運作<code>docker</code>指令的時候,需要本機有 Docker 服務。如果這項服務沒有啟動,可以用下面的指令啟動(官方文檔)。

Docker 把應用程式及其依賴,打包在 image 檔案裡面。隻有通過這個檔案,才能生成 Docker 容器。image 檔案可以看作是容器的模闆。Docker 根據 image 檔案生成容器的執行個體。同一個 image 檔案,可以生成多個同時運作的容器執行個體。

image 是二進制檔案。實際開發中,一個 image 檔案往往通過繼承另一個 image 檔案,加上一些個性化設定而生成。舉例來說,你可以在 Ubuntu 的 image 基礎上,往裡面加入 Apache 伺服器,形成你的 image。

image 檔案是通用的,一台機器的 image 檔案拷貝到另一台機器,照樣可以使用。一般來說,為了節省時間,我們應該盡量使用别人制作好的 image 檔案,而不是自己制作。即使要定制,也應該基于别人的 image 檔案進行加工,而不是從零開始制作。

為了友善共享,image 檔案制作完成後,可以上傳到網上的倉庫。Docker 的官方倉庫 Docker Hub 是最重要、最常用的 image 倉庫。此外,出售自己制作的 image 檔案也是可以的。

下面,我們通過最簡單的 image 檔案"hello world",感受一下 Docker。

需要說明的是,國内連接配接 Docker 的官方倉庫很慢,還會斷線,需要将預設倉庫改成國内的鏡像網站,具體的修改方法在下一篇文章的第一節。有需要的朋友,可以先看一下。

首先,運作下面的指令,将 image 檔案從倉庫抓取到本地。

上面代碼中,<code>docker image pull</code>是抓取 image 檔案的指令。<code>library/hello-world</code>是 image 檔案在倉庫裡面的位置,其中<code>library</code>是 image 檔案所在的組,<code>hello-world</code>是 image 檔案的名字。

由于 Docker 官方提供的 image 檔案,都放在<code>library</code>組裡面,是以它的是預設組,可以省略。是以,上面的指令可以寫成下面這樣。

抓取成功以後,就可以在本機看到這個 image 檔案了。

現在,運作這個 image 檔案。

<code>docker container run</code>指令會從 image 檔案,生成一個正在運作的容器執行個體。

注意,<code>docker container run</code>指令具有自動抓取 image 檔案的功能。如果發現本地沒有指定的 image 檔案,就會從倉庫自動抓取。是以,前面的<code>docker image pull</code>指令并不是必需的步驟。

如果運作成功,你會在螢幕上讀到下面的輸出。

輸出這段提示以後,<code>hello world</code>就會停止運作,容器自動終止。

有些容器不會自動終止,因為提供的是服務。比如,安裝運作 Ubuntu 的 image,就可以在指令行體驗 Ubuntu 系統。

對于那些不會自動終止的容器,必須使用<code>docker container kill</code> 指令手動終止。

image 檔案生成的容器執行個體,本身也是一個檔案,稱為容器檔案。也就是說,一旦容器生成,就會同時存在兩個檔案: image 檔案和容器檔案。而且關閉容器并不會删除容器檔案,隻是容器停止運作而已。

上面指令的輸出結果之中,包括容器的 ID。很多地方都需要提供這個 ID,比如上一節終止容器運作的<code>docker container kill</code>指令。

終止運作的容器檔案,依然會占據硬碟空間,可以使用<code>docker container rm</code>指令删除。

運作上面的指令之後,再使用<code>docker container ls --all</code>指令,就會發現被删除的容器檔案已經消失了。

學會使用 image 檔案以後,接下來的問題就是,如何可以生成 image 檔案?如果你要推廣自己的軟體,勢必要自己制作 image 檔案。

這就需要用到 Dockerfile 檔案。它是一個文本檔案,用來配置 image。Docker 根據 該檔案生成二進制的 image 檔案。

下面通過一個執行個體,示範如何編寫 Dockerfile 檔案。

下面我以 koa-demos 項目為例,介紹怎麼寫 Dockerfile 檔案,實作讓使用者在 Docker 容器裡面運作 Koa 架構。

作為準備工作,請先下載下傳源碼。

首先,在項目的根目錄下,建立一個文本檔案<code>.dockerignore</code>,寫入下面的内容。

上面代碼表示,這三個路徑要排除,不要打包進入 image 檔案。如果你沒有路徑要排除,這個檔案可以不建立。

然後,在項目的根目錄下,建立一個文本檔案 Dockerfile,寫入下面的内容。

上面代碼一共五行,含義如下。

<code>FROM node:8.4</code>:該 image 檔案繼承官方的 node image,冒号表示标簽,這裡标簽是<code>8.4</code>,即8.4版本的 node。 <code>COPY . /app</code>:将目前目錄下的所有檔案(除了<code>.dockerignore</code>排除的路徑),都拷貝進入 image 檔案的<code>/app</code>目錄。 <code>WORKDIR /app</code>:指定接下來的工作路徑為<code>/app</code>。 <code>RUN npm install</code>:在<code>/app</code>目錄下,運作<code>npm install</code>指令安裝依賴。注意,安裝後所有的依賴,都将打包進入 image 檔案。 <code>EXPOSE 3000</code>:将容器 3000 端口暴露出來, 允許外部連接配接這個端口。

有了 Dockerfile 檔案以後,就可以使用<code>docker image build</code>指令建立 image 檔案了。

上面代碼中,<code>-t</code>參數用來指定 image 檔案的名字,後面還可以用冒号指定标簽。如果不指定,預設的标簽就是<code>latest</code>。最後的那個點表示 Dockerfile 檔案所在的路徑,上例是目前路徑,是以是一個點。

如果運作成功,就可以看到新生成的 image 檔案<code>koa-demo</code>了。

<code>docker container run</code>指令會從 image 檔案生成容器。

上面指令的各個參數含義如下:

<code>-p</code>參數:容器的 3000 端口映射到本機的 8000 端口。 <code>-it</code>參數:容器的 Shell 映射到目前的 Shell,然後你在本機視窗輸入的指令,就會傳入容器。 <code>koa-demo:0.0.1</code>:image 檔案的名字(如果有标簽,還需要提供标簽,預設是 latest 标簽)。 <code>/bin/bash</code>:容器啟動以後,内部第一個執行的指令。這裡是啟動 Bash,保證使用者可以使用 Shell。

如果一切正常,運作上面的指令以後,就會傳回一個指令行提示符。

這表示你已經在容器裡面了,傳回的提示符就是容器内部的 Shell 提示符。執行下面的指令。

這時,Koa 架構已經運作起來了。打開本機的浏覽器,通路 http://127.0.0.1:8000,網頁顯示"Not Found",這是因為這個 demo 沒有寫路由。

這個例子中,Node 程序運作在 Docker 容器的虛拟環境裡面,程序接觸到的檔案系統和網絡接口都是虛拟的,與本機的檔案系統和網絡接口是隔離的,是以需要定義容器與實體機的端口映射(map)。

現在,在容器的指令行,按下 Ctrl + c 停止 Node 程序,然後按下 Ctrl + d (或者輸入 exit)退出容器。此外,也可以用<code>docker container kill</code>終止容器運作。

容器停止運作之後,并不會消失,用下面的指令删除容器檔案。

也可以使用<code>docker container run</code>指令的<code>--rm</code>參數,在容器終止運作後自動删除容器檔案。

上一節的例子裡面,容器啟動以後,需要手動輸入指令<code>node demos/01.js</code>。我們可以把這個指令寫在 Dockerfile 裡面,這樣容器啟動以後,這個指令就已經執行了,不用再手動輸入了。

上面的 Dockerfile 裡面,多了最後一行<code>CMD node demos/01.js</code>,它表示容器啟動後自動執行<code>node demos/01.js</code>。

你可能會問,<code>RUN</code>指令與<code>CMD</code>指令的差別在哪裡?簡單說,<code>RUN</code>指令在 image 檔案的建構階段執行,執行結果都會打包進入 image 檔案;<code>CMD</code>指令則是在容器啟動後執行。另外,一個 Dockerfile 可以包含多個<code>RUN</code>指令,但是隻能有一個<code>CMD</code>指令。

注意,指定了<code>CMD</code>指令以後,<code>docker container run</code>指令就不能附加指令了(比如前面的<code>/bin/bash</code>),否則它會覆寫<code>CMD</code>指令。現在,啟動容器可以使用下面的指令。

容器運作成功後,就确認了 image 檔案的有效性。這時,我們就可以考慮把 image 檔案分享到網上,讓其他人使用。

首先,去 hub.docker.com 或 cloud.docker.com 注冊一個賬戶。然後,用下面的指令登入。

接着,為本地的 image 标注使用者名和版本。

也可以不标注使用者名,重新建構一下 image 檔案。

最後,釋出 image 檔案。

釋出成功以後,登入 hub.docker.com,就可以看到已經釋出的 image 檔案。

docker 的主要用法就是上面這些,此外還有幾個指令,也非常有用。

(1)docker container start

前面的<code>docker container run</code>指令是建立容器,每運作一次,就會建立一個容器。同樣的指令運作兩次,就會生成兩個一模一樣的容器檔案。如果希望重複使用容器,就要使用<code>docker container start</code>指令,它用來啟動已經生成、已經停止運作的容器檔案。

(2)docker container stop

前面的<code>docker container kill</code>指令終止容器運作,相當于向容器裡面的主程序發出 SIGKILL 信号。而<code>docker container stop</code>指令也是用來終止容器運作,相當于向容器裡面的主程序發出 SIGTERM 信号,然後過一段時間再發出 SIGKILL 信号。

這兩個信号的差别是,應用程式收到 SIGTERM 信号以後,可以自行進行收尾清理工作,但也可以不理會這個信号。如果收到 SIGKILL 信号,就會強行立即終止,那些正在進行中的操作會全部丢失。

(3)docker container logs

<code>docker container logs</code>指令用來檢視 docker 容器的輸出,即容器裡面 Shell 的标準輸出。如果<code>docker run</code>指令運作容器的時候,沒有使用<code>-it</code>參數,就要用這個指令檢視輸出。

(4)docker container exec

<code>docker container exec</code>指令用于進入一個正在運作的 docker 容器。如果<code>docker run</code>指令運作容器的時候,沒有使用<code>-it</code>參數,就要用這個指令進入容器。一旦進入了容器,就可以在容器的 Shell 執行指令了。

(5)docker container cp

<code>docker container cp</code>指令用于從正在運作的 Docker 容器裡面,将檔案拷貝到本機。下面是拷貝到目前目錄的寫法。

繼續閱讀