- Docker實踐(第2版)
- (英)伊恩·米爾 艾丹·霍布森·塞耶斯
- 3817字
- 2021-01-08 22:12:13
1.2 構建一個Docker應用程序
現在,我們要動手使用Docker來構建一個簡單的“to-do”應用程序(todoapp)鏡像了。在這個過程中,讀者會看到一些關鍵的Docker功能,如Dockerfile、鏡像復用、端口公開及構建自動化。這是接下來10分鐘讀者將學到的東西:
- 如何使用Dockerfile來創建Docker鏡像;
- 如何為Docker鏡像打標簽以便引用;
- 如何運行新建的Docker鏡像。
to-do應用是協助用戶跟蹤待完成事項的一個應用程序。我們所構建的應用將存儲并顯示可被標記為已完成的信息的簡短字符串,它以一個簡單的網頁界面呈現。圖1-6展示了如此操作將得到的結果。
圖1-6 構建一個Docker應用程序
應用程序的細節不是重點。我們將演示的是,讀者可以從我們所提供的一個簡短的Dockerfile放心地在自己的宿主機上使用與我們相同的方法構建、運行、停止和啟動一個應用程序,而無須考慮應用程序的安裝或依賴。這正是 Docker為我們提供的關鍵部分——可靠地重現并簡便地管理和共享開發環境。這意味著用戶無須再遵循并迷失在那些復雜的或含糊的安裝說明中。
注意
這個to-do應用程序將貫穿本書,多次使用,它非常適合用于實踐和演示,因此值得讀者熟悉一下。
1.2.1 創建新的Docker鏡像的方式
創建Docker鏡像有4種標準的方式。表1-2逐一列出了這些方法。
表1-2 創建Docker鏡像的方式

如果用戶所做的是概念驗證以確認安裝過程是否正常,那么第一種“手工”方式是沒問題的。在這個過程中,用戶應對所采取的步驟做記錄,以便在需要時回到同一點上。
到某個時間點,用戶會想要定義創建鏡像的步驟。這就是Dockerfile方式(也就是我們這里所用的方式)。
對于更復雜的構建,用戶需要使用第三種方式,特別是在Dockerfile功能還不足以滿足鏡像要求的時候。
最后一種方式從一個空鏡像開始,通過疊加一組運行鏡像所需要的文件進行構建。如果用戶想導入一組在其他地方創建好的自包含的文件,這將非常有用,不過這種方法在主流應用中非常罕見。
現在,我們來看一下Dockerfile方法,其他方法將在本書后面再做說明。
1.2.2 編寫一個Dockerfile
Dockerfile是一個包含一系列命令的文本文件。本示例中我們將使用的Dockerfile如代碼清單1-1所示。創建一個新目錄,移動到這個目錄里,然后使用這些內容創建一個名為“Dockerfile”的文件。
代碼清單1-1 todoapp Dockerfile
FROM node ?--- 定義基礎鏡像
LABEL maintainer ian.miell@gmail.com ?--- 聲明維護人員
RUN git clone -q https://github.com/docker-in-practice/todo.git ?--- 克隆todoapp代碼
WORKDIR todo ?--- 移動到新的克隆目錄
RUN npm install > /dev/null ?--- 執行node包管理器的安裝命令(npm)
EXPOSE 8000 ?--- 指定從所構建的鏡像啟動的容器需要監聽這個端口
CMD ["npm","start"] ?--- 指定在啟動時需要執行的命令
Dockerfile的開始部分是使用FROM
命令定義基礎鏡像。本示例使用了一個Node.js鏡像以便訪問Node.js程序。官方的Node.js鏡像名為node
。
接下來,使用LABEL
命令聲明維護人員。在這里,我們使用的是其中一個人的電子郵件地址,讀者也可以替換成自己的,因為現在它是你的Dockerfile了。這一行不是創建可工作的Docker鏡像所必需的,不過將其包含進來是一個很好的做法。到這個時候,構建已經繼承了node容器的狀態,讀者可以在它上面做操作了。
接下來,使用RUN
命令克隆todoapp代碼。這里使用指定的命令獲取應用程序的代碼:在容器內運行git
。在這個示例中,Git是安裝在基礎node鏡像里的,不過讀者不能對這類事情做假定。
現在使用WORKDIR
命令移動到新克隆的目錄中。這不僅會改變構建環境中的目錄,最后一條WORKDIR
命令還決定了從所構建鏡像啟動容器時用戶所處的默認目錄。
接下來,執行node包管理器的安裝命令(npm
)。這將為應用程序設置依賴。我們對輸出的信息不感興趣,所以將其重定向到/dev/null上。
由于應用程序使用了8000端口,使用EXPOSE
命令告訴Docker從所構建鏡像啟動的容器應該監聽這個端口。
最后,使用CMD
命令告訴Docker在容器啟動時將執行哪條命令。
這個簡單的示例演示了Docker及Dockerfile的幾個核心功能。Dockerfile是一組嚴格按順序執行的有限的命令集的簡單序列。它影響了最終鏡像的文件和元數據。這里的RUN
命令通過簽出并安裝應用程序影響了文件系統,而EXPOSE
、CMD
和WORKDIR
命令影響了鏡像的元數據。
1.2.3 構建一個Docker鏡像
讀者已經定義了自己的Dockerfile的構建步驟。現在可以鍵入圖1-7所示的命令,從而構建Docker鏡像了。
圖1-7 docker build子命令
輸出看起來和下面類似。
Sending build context to Docker daemon 2.048kB ?--- Docker會上傳docker build指定目錄下的文件和目錄
Step 1/7 : FROM node ?--- 每個構建步驟從 1 開始按順序編號,并與命令一起輸出
---> 2ca756a6578b ?--- 每個命令會導致一個新鏡像被創建,其鏡像ID在此輸出
Step 2/7 : LABEL maintainer ian.miell@gmail.com
---> Running in bf73f87c88d6
---> 5383857304fc
Removing intermediate container bf73f87c88d6 ?--- 為節省空間,在繼續前每個中間容器會被移除
Step 3/7 : RUN git clone -q https://github.com/docker-in-practice/todo.git
---> Running in 761baf524cc1
---> 4350cb1c977c
Removing intermediate container 761baf524cc1
Step 4/7 : WORKDIR todo
---> a1b24710f458
Removing intermediate container 0f8cd22fbe83
Step 5/7 : RUN npm install > /dev/null
---> Running in 92a8f9ba530a
npm info it worked if it ends with ok ?--- 構建的調試信息在此輸出(限于篇幅,本代碼清單做了刪減)
[...]
npm info ok
---> 6ee4d7bba544
Removing intermediate container 92a8f9ba530a
Step 6/7 : EXPOSE 8000
---> Running in 8e33c1ded161
---> 3ea44544f13c
Removing intermediate container 8e33c1ded161
Step 7/7 : CMD npm start
---> Running in ccc076ee38fe
---> 66c76cea05bb
Removing intermediate container ccc076ee38fe
Successfully built 66c76cea05bb ?--- 此次構建的最終鏡像ID,可用于打標簽
現在,擁有了一個具有鏡像ID(前面示例中的“66c76cea05bb”,不過讀者的ID會不一樣)的Docker鏡像。總是引用這個ID會很麻煩,可以為其打標簽以方便引用,如圖1-8所示。
圖1-8 docker tag
子命令
輸入圖1-8所示的命令,將66c76cea05bb替換成讀者生成的鏡像ID。
現在就能從一個Dockerfile構建自己的Docker鏡像副本,并重現別人定義的環境了!
1.2.4 運行一個Docker容器
讀者已經構建出Docker鏡像并為其打上了標簽。現在可以以容器的形式來運行它了。運行后的輸出結果如代碼清單1-2所示。
代碼清單1-2 todoapp的docker run輸出
$ docker run -i -t -p 8000:8000 --name example1 todoapp ?--- docker run子命令啟動容器,-p將容器的 8000 端口映射到宿主機的8000端口上,--name給容器賦予一個唯一的名字,最后一個參數是鏡像
npm install
npm info it worked if it ends with ok
npm info using npm@2.14.4
npm info using node@v4.1.1
npm info prestart todomvc-swarm@0.0.1
> todomvc-swarm@0.0.1 prestart /todo ?--- 容器的啟動進程的輸出被發送到終端中
> make all
npm install
npm info it worked if it ends with ok
npm info using npm@2.14.4
npm info using node@v4.1.1
npm WARN package.json todomvc-swarm@0.0.1 No repository field.
npm WARN package.json todomvc-swarm@0.0.1 license should be a valid SPDX
? license expression
npm info preinstall todomvc-swarm@0.0.1
npm info package.json statics@0.1.0 license should be a valid SPDX license
? expression
npm info package.json react-tools@0.11.2 No license field.
npm info package.json react@0.11.2 No license field.
npm info package.json node-
jsx@0.11.0 license should be a valid SPDX license expression
npm info package.json ws@0.4.32 No license field.
npm info build /todo
npm info linkStuff todomvc-swarm@0.0.1
npm info install todomvc-swarm@0.0.1
npm info postinstall todomvc-swarm@0.0.1
npm info prepublish todomvc-swarm@0.0.1
npm info ok
if [ ! -e dist/ ]; then mkdir dist; fi
cp node_modules/react/dist/react.min.js dist/react.min.js
LocalTodoApp.js:9: // TODO: default english version
LocalTodoApp.js:84: fwdList = this.host.get('/TodoList#'+listId);
// TODO fn+id sig
TodoApp.js:117: // TODO scroll into view
TodoApp.js:176: if (i>=list.length()) { i=list.length()-1; } // TODO
? .length
local.html:30: <!-- TODO 2-split, 3-split -->
model/TodoList.js:29: // TODO one op - repeated spec? long spec?
view/Footer.jsx:61: // TODO: show the entry's metadata
view/Footer.jsx:80: todoList.addObject(new TodoItem()); // TODO
? create default
view/Header.jsx:25: // TODO list some meaningful header (apart from the
? id)
npm info start todomvc-swarm@0.0.1
> todomvc-swarm@0.0.1 start /todo
> node TodoAppServer.js
Swarm server started port 8000
^Cshutting down http-server... ?--- 在此按組合鍵Ctrl+C終止進程和容器
closing swarm host...
swarm host closed
npm info lifecycle todomvc-swarm@0.0.1~poststart: todomvc-swarm@0.0.1
npm info ok
$ docker ps -a ?--- 執行這個命令查看已經啟動和移除的容器,以及其ID和狀態(就像進程一樣)
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9db5ada0461 todoapp "npm start" 2 minutes ago Exited (0) 2 minutes ago
? example1
$ docker start example1 ?--- 重新啟動容器,這次是在后臺運行
example1
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
? PORTS NAMES
b9db5ada0461 todoapp "npm start" 8 minutes ago Up 10 seconds
? 0.0.0.0:8000->8000/tcp example1 ?--- 再次執行ps命令查看發生變化的狀態
$ docker diff example1 ?--- docker diff子命令顯示了自鏡像被實例化成一個容器以來哪些文件受到了影響
C /root
C /root/.npm
C /root/.npm/_locks
C /root/.npm/anonymous-cli-metrics.json
C /todo ?--- 修改了/todo目錄(C)
A /todo/.swarm ?--- 增加了/todo/.swarm目錄(A)
A /todo/.swarm/_log
A /todo/dist
A /todo/dist/LocalTodoApp.app.js
A /todo/dist/TodoApp.app.js
A /todo/dist/react.min.js
C /todo/node_modules
docker run
子命令啟動容器。-p
標志將容器的8000端口映射到宿主機的8000端口上,讀者現在應該可以使用瀏覽器訪問http://localhost:8000來查看這個應用程序了。--name
標志賦予了容器一個唯一的名稱,以便后面引用。最后的參數是鏡像名稱。
一旦容器啟動,我們就可以按組合鍵Ctrl+C終止進程和容器。讀者可以執行ps
命令查看被啟動且未被移除的容器。注意,每個容器都具有自己的容器 ID 和狀態,與進程類似。它的狀態是Exited
(已退出),不過讀者可以重新啟動它。這么做之后,注意狀態已經改變為Up
(運行中),且容器到宿主機的端口映射現在也顯示出來了。
docker diff
子命令顯示了自鏡像被實例化成一個容器以來哪些文件受到了影響。在這個示例中,todo目錄被修改了(C),而其他列出的文件是新增的(A)。沒有文件被刪除(D),這是另一種可能性。
如讀者所見,Docker“包含”環境的事實意味著用戶可以將其視為一個實體,在其上執行的動作是可預見的。這賦予了Docker寬廣的能力——用戶可以影響從開發到生產再到維護的整個軟件生命周期。這種改變正是本書所要描述的,在實踐中展示Docker所能完成的東西。
接下來讀者將了解Docker的另一個核心概念——分層。
1.2.5 Docker分層
Docker分層協助用戶管理在大規模使用容器時會遇到的一個大問題。想象一下,如果啟動了數百甚至數千個to-do應用,并且每個應用都需要將文件的一份副本存儲在某個地方。
可想而知,磁盤空間會迅速消耗光!默認情況下,Docker在內部使用寫時復制(copy-on-write)機制來減少所需的硬盤空間量(見圖 1-9)。每當一個運行中的容器需要寫入一個文件時,它會通過將該項目復制到磁盤的一個新區域來記錄這一修改。在執行Docker提交時,這塊磁盤新區域將被凍結并記錄為具有自身標識符的一個層。
圖1-9 啟動時復制與寫時復制對比
這一部分解釋了Docker容器為何能如此迅速地啟動——它們不需要復制任何東西,因為所有的數據已經存儲為鏡像。
提示
寫時復制是計算技術中使用的一種標準的優化策略。在從模板創建一個新的(任意類型)對象時,只在數據發生變化時才能將其復制進來,而不是復制整個所需的數據集。依據用例的不同,這能省下相當可觀的資源。
圖1-10展示了構建的to-do應用,它具有我們所感興趣的3個層。因為層是靜態的,所以如果用戶需要更改更高層上的任何東西,都可以在想引用的鏡像之上進行構建。在這個to-do應用中,我們從公開可用的node鏡像構建,并將變更疊加在最上層。
所有這3個層都可以被多個運行中的容器共享,就像一個共享庫可以在內存中被多個運行中的進程共享一樣。對于運維人員來說,這是一項至關重要的功能,可以在宿主機上運行大量基于不同鏡像的容器,而不至于耗盡磁盤空間。
想象一下,將所運行的to-do應用作為在線服務提供給付費用戶。你可以將服務擴散給大量用戶。如果是在開發中,你可以一次在本地機器上啟動多個不同的環境。如果是在進行測試,你可以比之前同時運行更多測試,速度也更快。有了分層,所有這些東西都成為可能。
圖1-10 Docker中to-do應用的文件系統分層
通過使用Docker構建和運行一個應用程序,讀者開始見識到Docker能給工作流帶來的威力。重現并共享特定的環境,并能在不同的地方落地,讓開發過程兼具靈活性和可控性。
- Mastering vRealize Operations Manager(Second Edition)
- Modern Web Testing with TestCafe
- Mobile-first Bootstrap
- 從零開始寫Linux內核:一書學透核心原理與實現
- Arch Linux Environment Setup How-to
- 新手易學:系統安裝與重裝
- Linux網絡內核分析與開發
- 8051軟核處理器設計實戰
- AWS Development Essentials
- AutoCAD 2014中文版從入門到精通
- 完美應用RHEL 8
- Linux命令行大全(第2版)
- Kali Linux高級滲透測試(原書第3版)
- Linux軟件管理平臺設計與實現
- Android應用性能優化最佳實踐