初步認識 docker
為什么要學習 docker
有同學說他開發工作中有兩大神器,一個是 vim 編輯器,另一個就是 Docker。
什么是 docker
Docker 是一個開源的應用容器引擎。
容器的發展之路
業務是基於應用運轉,而應用通常運行在服務器中,以前每個服務器只能運行單一應用(見 Tip),若業務部門需要增加一個新應用,則需要IT部門去采購一台新的服務器,而由於不知道新增應用所需的服務器性能需要怎樣,這時只能憑經驗去購買。倘若服務器性能不足,可能會讓交易失敗而導致公司收益下降,所以通常會采購更好的服務器。這種做法,導致大部分服務器的使用率都處在較低的水平,對公司的資源是一種極大的浪費。
Tip:單一應用有兩種含義:
- 單一應用可能指同一中應用程序只能在一台服務器上安裝一個。比如 apache(默認80端口或443端口)、dns等,如果再安裝一個 apache 可能就不行,有人說我換一個端口,那假如這個應用是單例的呢?就是只能有一個!
- 單一應用還可能指在一台服務器中只能按照一個操作系統。比如一台服務器只能安裝一個操作系統,即已安裝 win10,就不能再安裝 xp
為了解決上面問題,VMware 公司給我們帶來了 虛擬機(VM),於是我們就有了一種可以將多個應用同時運行在一個服務器中的技術。每當業務部門需要新增應用時,IT 部門無需去采購新的服務器,而會嘗試在現有空閑服務器上部署新應用。從而為公司節省大量資金。
但是,虛擬機也也有缺陷,而像谷歌這樣大規模 web 服務器玩家一直采用容器技術解決虛擬機模型的缺點。
容器模型和虛擬機模型相似,主要區別:容器運行不會獨占操作系統。
- 運行在相同宿主機上的容器共享一個操作系統,這樣就能節省大量系統資源,例如 cpu,內存。
- vm 獨占操作系統,每個 os 都會占用額外的 cpu、內存,而這些資源本可以運行更多的應用。
- 某些情況下,os 需要許可證才能運行,os 也可能需要打補丁,由於容器不獨占 os,所以較 vm 能節省維護和資金成本。
Tip:附上虛擬機和容器的圖片 —— 取自 關於 Windows 容器


此外,虛擬機啟動慢,並且在不同的虛擬機管理器(hypervisor)或者雲平台之間遷移比想象中要困難得多。而容器啟動更快,也更容器遷移。
Tip:現代容器技術起源於 linux,得益於許多人得持續努力和貢獻。當今容器生態環境很大程度上受益於基金會,而基金會是由許多獨立開發者以及公司組織共同創建和維護的。
docker 使容器變得簡單
雖然容器技術很好,但對於大部分人(或組織)來說,容器技術的復雜性阻止了其實際應用,直到 docker 的出現,容器才被大眾所接收。
docker 技術使 linux 容器技術得到了廣泛應用,換個角度,是 docker 公司使容器變得簡單。
docker 公司
docker 公司對外宣稱:我們簡化了開發人員(即正在做改變世界的 apps)的生活。
起先是一家名為 dotCloud 的平台即服務(PaaS)的提供商。底層技術, dotCloud 平台利用 Linux 容器技術,為了方便創建和管理容器, dotCloud 開發了一套內部工具,之后被命名為 Docker。
2013年, dotCloud 的 paas 業務不景氣,於是他們聘請了一個新的 ceo,將公司改名為 Docker,同時放棄舊業務,開始一個新的征程:將 Docker 和容器技術推向全世界。
安裝 Docker Desktop
點擊 docker 官網的入門,映入眼簾的是:我們為您提供完整的容器解決方案——無論您是誰,以及您在容器化之旅的哪個階段。
頁面有兩個東西:Desker Desktop(容器化應用程序的最快方式)和 Docker Hub。
- Docker Desktop,數百萬正在構建容器化應用程序的開發人員的首選。
- Docker Hub,基於雲的應用程序注冊和開發團隊協作服務。
Tip:Docker Hub類似 npm,npm 中可以搜索包,docker hub 可以搜索鏡像。
作為開發人員,筆者選擇下載 Desker Desktop 的 windows 版本。
Tip:筆者是 Windows 10 家庭中文版;Docker Desktop 4.5.1。
點擊安裝(一路 next 即可),然后啟動,無需登錄。進入如下界面:

打開 powsershell,查看 docker 版本信息:
exercise> docker version
Client:
Cloud integration: v1.0.22
Version: 20.10.12
API version: 1.41
Go version: go1.16.12
Git commit: e91ed57
Built: Mon Dec 13 11:44:07 2021
OS/Arch: windows/amd64
Context: default
Experimental: true
Server: Docker Desktop 4.5.1 (74721)
Engine:
Version: 20.10.12
API version: 1.41 (minimum version 1.12)
Go version: go1.16.12
Git commit: 459d0df
Built: Mon Dec 13 11:43:56 2021
OS/Arch: linux/amd64
// Experimental 為 false,表明當前運行的 docker 版本是非實驗版本
Experimental: false
containerd:
Version: 1.4.12
GitCommit: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
runc:
Version: 1.0.2
GitCommit: v1.0.2-0-g52b36a2
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Tip:docker version 命令(或其他命令)的詳細請看 這里
鏡像和容器
可以將 docker 鏡像理解成一個包含 os 文件系統和應用的對象。
與虛擬機模板類似,虛擬機模板本質上是處於關機狀態的虛擬機。在 Docker 世界中,鏡像就等於未運行的容器。
亦或把鏡像當作類(Class)
通過 docker image 可以查看鏡像相關命令:
exercise> docker image
Usage: docker image COMMAND
Manage images
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
prune Remove unused images
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rm Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
Run 'docker image COMMAND --help' for more information on a command.
例如 docker image ls 能查看鏡像列表:
aaron> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
目前沒有鏡像。
我們可以通過 docker pull 來下載鏡像。例如 hello-world(Official Image 官方鏡像)
exercise> docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:97a379f4f88575512824f3b352bc03cd75e239179eea0fecc38e597b2209f49a
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
再次查看鏡像列表:
exercise> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 4 months ago 13.3kB
接着我們可以通過 docker run 來運行鏡像:
exercise> docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
通過 docker ps 查看容器列表:
exercise> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
沒有發現容器。添加 -a 參數顯示所有容器(默認只顯示運行中的容器):
exercise> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
615e96c3e70e hello-world "/hello" 4 seconds ago Exited (0) 3 seconds ago interesting_goodall
從中發現有一個容器,狀態時已退出(Exited)。
getting-started 鏡像
安裝完 Docker Desktop 並啟動,界面提示我們運行 docker/getting-started 鏡像:
// 嘗試運行容器:將此命令復制並粘貼到您的終端,然后返回
Try running a container: Copy and paste this command into your terminal and then come back
docker run -d -p 80:80 docker/getting-started
Tip:可以組合單個字符標志來縮短完整命令。等同於 docker run -dp 80:80 docker/getting-started
-d以分離模式運行容器(在后台)-p80:80將主機的 80 端口映射到容器中的 80 端口docker/getting-started要使用的鏡像
根據提示,直接運行命令:
exercise> docker run -d -p 80:80 docker/getting-started
// 無法在本地找到鏡像 'docker/getting-started:latest'
Unable to find image 'docker/getting-started:latest' locally
// 拉取 latest 版本
latest: Pulling from docker/getting-started
59bf1c3509f3: Pull complete
8d6ba530f648: Pull complete
5288d7ad7a7f: Pull complete
39e51c61c033: Pull complete
ee6f71c6f4a8: Pull complete
f2303c6c8865: Pull complete
0645fddcff40: Pull complete
d05ee95f5d2f: Pull complete
Digest: sha256:aa945bdff163395d3293834697fa91fd4c725f47093ec499f27bc032dc1bdd16
Status: Downloaded newer image for docker/getting-started:latest
8b755ccaba10653299afbb15c39c9a9c798c78ac1f7d90803794b77816495a9f
docker run 會創建容器,然后啟動容器。如果本地沒有該鏡像,則會去拉取鏡像:
exercise> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker/getting-started latest bd9a9f733898 9 days ago 28.8MB
hello-world latest feb5d9fea6a5 4 months ago 13.3kB
鏡像為 docker/getting-started 的容器已經啟動:
exercise> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b755ccaba10 docker/getting-started "/docker-entrypoint.…" 11 seconds ago Up 10 seconds 0.0.0.0:80->80/tcp determined_sutherland
現在通過瀏覽器訪問 http://localhost:80(自動跳轉至 tutorial) 顯示如下:

Tip:這個應用是一個教程,比如目錄 Our Application,里面是一個 todo 的項目,教我們如何構建鏡像。還有 Using Docker Compose、Persisting our DB(持久化我們的數據庫)等。
停止、啟動容器
可以通過 docker stop 和 docker start 來停止、啟動容器:
exercise> docker stop 8b755ccaba10
8b755ccaba10
exercise> docker start 8b755ccaba10
8b755ccaba10
docker kill 也會停止容器,或許較 stop 更暴力:
exercise> docker start 8b755ccaba10
8b755ccaba10
exercise> docker ps -a
// 容器還存在
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b755ccaba10 docker/getting-started "/docker-entrypoint.…" 17 minutes ago Exited (137) 11 seconds ago determined_sutherland
構建鏡像
筆者嘗試將一個 node+express 的小項目打包成鏡像。只需要兩步:
- 創建前端項目
- 編寫 Dockerfile 文件 —— 一個基於文本的指令腳本,用於創建容器鏡像
創建前端項目
// 創建項目 node-server
exercise> mkdir node-server
// 進入項目
exercise> cd .\node-server\
// 初始化項目
node-server> npm init -y
// 安裝依賴包
node-server> npm i -D express@4
創建 app.js,內容如下:
// node-server/app.js
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Tip:express 更多介紹請看 快速入門-hello-world
啟動服務,瀏覽器輸出 Hello World!:
node-server> node app
Example app listening at http://localhost:3000
至此,前端項目編寫完畢。
編寫 Dockerfile 文件
新建 Dockerfile 文件:
// node-server/Dockerfile
// 注:文件沒有擴展名
# Dockerfile 必須以 FROM 指令開頭
# 為后續指令設置基本鏡像
FROM node:14
# 定義變量 port、dir。引用則使用 $port
ENV port=3000
ENV dir=/app
# 設置工作目錄
# 為 Dockerfile 中任何 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指令設置工作目錄
WORKDIR $dir
# 將當前的項目(如 app.js、package.json等文件目錄)復制到 WORKDIR 中
COPY . .
# 運行命令 npm i
# `docker build` 時就會執行
RUN npm i
# EXPOSE 指令通知 Docker 容器在運行時偵聽指定的網絡端口。
# 可以指定端口監聽 TCP 還是 UDP,如果不指定協議,則默認為 TCP。
# EXPOSE 指令實際上並不發布端口。要在運行容器時實際發布端口,請使用 docker run 上的 -p 標志
EXPOSE $port
# CMD 在構建時不會執行任何操作
CMD node app
Tip:本地 node 版本是 14,所以基礎鏡像也選擇 14:
node-server> node -v
v14.17.6
新建 .dockerignore 文件:
// node-server/.dockerignore
// 忽略 node_modules 和 dist
node_modules
dist
Tip:.dockerignore 作用類似 git 中的 .gitignore 文件。有助於避免不必要地將大型或敏感文件和目錄發送到守護程序。
通過 docker build 構建鏡像:
-t給鏡像起個名字- 末尾的
.告訴 Docker 應該在當前目錄中查找 Dockerfile
node-server> docker build -t docker-node-server .
[+] Building 65.0s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 756B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 58B 0.0s
=> [internal] load metadata for docker.io/library/node:14 7.0s
=> [1/4] FROM docker.io/library/node:14@sha256:b2c75df8c9706156c38b4f1f678d00e11cb2bfda09fc4ab6e36ec17ac9163865 53.2s
=> => resolve docker.io/library/node:14@sha256:b2c75df8c9706156c38b4f1f678d00e11cb2bfda09fc4ab6e36ec17ac9163865 0.0s
=> => sha256:28874e89b1ebebe12162aed108a8b4b37f9e0326a58830d1fe54418f0167cad0 2.21kB / 2.21kB 0.0s
=> => sha256:9cb3f042a68426bdefb8eba9bc173bdab3121c897397843ffc130b909dd86e4b 7.64kB / 7.64kB 0.0s
=> => sha256:57b3fa6f1b88b95ac6adeafdb618011e672d4c9f5637b92be373276ee7e066dd 11.30MB / 11.30MB 18.3s
=> => sha256:b2c75df8c9706156c38b4f1f678d00e11cb2bfda09fc4ab6e36ec17ac9163865 776B / 776B 0.0s
=> => sha256:a834d7c95167a3e129adb00a5ddbaf5d3c035ad748ff7ee1273373d150457820 45.38MB / 45.38MB 10.8s
=> => sha256:778df3ecaa0fbba90d3a7d88947a4376ebdc7e2fcf8a4b5ce43b3c699faadff6 4.34MB / 4.34MB 3.4s
=> => sha256:d353c340774e155d838e2e0f0952201366cee28591b065b7d328fde7bc72e034 49.76MB / 49.76MB 28.9s
=> => sha256:6370e0bc373dd8f1f4b0f763cdd52ff8efbe34c82030a0a8d2ced521eb68d4f3 214.46MB / 214.46MB 38.7s
=> => extracting sha256:a834d7c95167a3e129adb00a5ddbaf5d3c035ad748ff7ee1273373d150457820 2.8s
=> => sha256:fb61153482cddb011062662e1d09f1b57807ae219ec6688c5051fc2568f68a3d 4.19kB / 4.19kB 21.4s
=> => extracting sha256:57b3fa6f1b88b95ac6adeafdb618011e672d4c9f5637b92be373276ee7e066dd 0.6s
=> => extracting sha256:778df3ecaa0fbba90d3a7d88947a4376ebdc7e2fcf8a4b5ce43b3c699faadff6 0.2s
=> => sha256:78fb5822e501465f53376aad93747f94d3730e708b5e36bb6494161e9c91f21e 35.60MB / 35.60MB 45.2s
=> => sha256:ba3577a691be8618aafc9ae198e876b57871a492c855035db630e5f15e1f5c52 2.33MB / 2.33MB 32.6s
=> => extracting sha256:d353c340774e155d838e2e0f0952201366cee28591b065b7d328fde7bc72e034 3.8s
=> => sha256:bd38fd0dd57b911346484e7fc692f9473d12488b1425b47be379951c0e12c31f 465B / 465B 33.2s
=> => extracting sha256:6370e0bc373dd8f1f4b0f763cdd52ff8efbe34c82030a0a8d2ced521eb68d4f3 11.2s
=> => extracting sha256:fb61153482cddb011062662e1d09f1b57807ae219ec6688c5051fc2568f68a3d 0.1s
=> => extracting sha256:78fb5822e501465f53376aad93747f94d3730e708b5e36bb6494161e9c91f21e 2.3s
=> => extracting sha256:ba3577a691be8618aafc9ae198e876b57871a492c855035db630e5f15e1f5c52 0.1s
=> => extracting sha256:bd38fd0dd57b911346484e7fc692f9473d12488b1425b47be379951c0e12c31f 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 35.28kB 0.0s
=> [2/4] WORKDIR /app 0.5s
=> [3/4] COPY . . 0.0s
=> [4/4] RUN npm i 4.1s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:b22b98e4c926dc10f96bf41f1e93626a8b7449a1a0e5399755a86c20b666f97b 0.0s
=> => naming to docker.io/library/docker-node-server 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
構建一共花費 65 秒。新的鏡像成功生成:
exercise> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-node-server latest b22b98e4c926 About a minute ago 948MB
docker/getting-started latest bd9a9f733898 9 days ago 28.8MB
hello-world latest feb5d9fea6a5 4 months ago 13.3kB
運行新的鏡像:
exercise> docker run -dp 3010:3000 docker-node-server
85b083215882cc74e8def49b2027c9b53a38ab827614701d0e0d38afb534dbbacd
查看正在運行的鏡像:
exercise> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
85b083215882 docker-node-server "docker-entrypoint.s…" 11 seconds ago Up 9 seconds 0.0.0.0:3010->3000/tcp focused_lovelace
8b755ccaba10 docker/getting-started "/docker-entrypoint.…" 5 hours ago Up 4 hours 0.0.0.0:80->80/tcp determined_sutherland
訪問 http://localhost:3010/,瀏覽器顯示 Hello World!。
Tip:運行鏡像倘若改為 3010:3020,瀏覽器訪問則會失敗,因為鏡像中暴露(EXPOSE)的端口為 3000。
開發、運維的視角
開發,更多關注與應用相關的內容
運維,主要包括下載鏡像、運行新的容器、登錄新容器、在容器內運行命令、銷毀容器
