前端程序員初步認識 docker


初步認識 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,基於雲的應用程序注冊和開發團隊協作服務。

TipDocker 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

Tipdocker 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 以分離模式運行容器(在后台)
  • -p 80: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) 顯示如下:

getting-started.png

Tip:這個應用是一個教程,比如目錄 Our Application,里面是一個 todo 的項目,教我們如何構建鏡像。還有 Using Docker ComposePersisting our DB(持久化我們的數據庫)等。

停止、啟動容器

可以通過 docker stopdocker 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。

開發、運維的視角

開發,更多關注與應用相關的內容

運維,主要包括下載鏡像、運行新的容器、登錄新容器、在容器內運行命令、銷毀容器


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM