官方示例
官方地址:https://docs.docker.com/get-started/02_our_app/
Docker作為一個容器,可以在容器中安裝各種應用
獲取官方提供的應用示例
官方地址:https://github.com/docker/getting-started/tree/master/app
建立應用容器鏡像
為了創建一個應用,需要使用一個 Dockerfile
一個Dockerfile只是一個基於文本的指令腳本,用於創建容器映像。如果之前已經創建過Dockerfile,可能已經見識過了Dockerfile的暇疵
# syntax=docker/dockerfile:1 FROM node:12-alpine RUN apk add --no-cache python g++ make WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
構建docker鏡像
docker build -t getting-started .
首先會下載相應的鏡像依賴,然后開始構建,然后執行yarn指令,下載應用所需要的依賴
參數解釋 -t 和 .
- -t 會標記我們的鏡像,可以當作起名字,這個鏡像的名字就是 getting-started,也可以理解為這個鏡像的引用
- . 在這條構建docker指令的最后,通知Docker在當前目錄查找Dockerfile
啟動應用容器
構建步驟結束后,我們已經有了一個鏡像,通過 docker run 指令啟動容器
docker run -dp 3000:3000 getting-started
參數解釋 -d 和 -p
- -d,Run container in background and print container ID,后台運行並打印容器的ID
- -p,Publish a container's port(s) to the host,給主機暴露該容器的端口
當前指令的意思是把容器的3000端口映射到主機的3000端口,沒有這個映射我們不能直接管理應用
更新應用
需要查看容器列表然后停下指定容器
docker ps
使用docker stop指令
# Swap out <the-container-id> with the ID from docker ps docker stop <the-container-id>
刪除該容器
docker rm <the-container-id>
也可以使用強制刪除指令
docker rm -f <the-container-id>
把新的應用覆蓋上去,然后重新啟動即可
docker run -dp 3000:3000 getting-started
共享應用
現在我們已經創建好了鏡像,可以共享鏡像了,必須使用一個Docker注冊處才能實現共享,默認的注冊處是Docker Hub,我們所有使用的鏡像都來源於注冊處
一個Docker ID允許訪問Docker Hub,這是世界上最大的容器鏡像的庫和社區
創建Docker ID的鏈接:https://hub.docker.com/signup
創建倉庫
在推送一個鏡像前,我們首先需要在Docker Hub上創建一個倉庫
- 注冊並使用Docker Hub分享鏡像
- 在Docker Hub登錄
- 點擊創建倉庫按鈕
- 使用getting-started給倉庫設置名字,保證可見性為 Public
推送鏡像
$ docker push docker/getting-started The push refers to repository [docker.io/docker/getting-started] An image does not exist locally with the tag: docker/getting-started
在命令行中,嘗試運行在Docker Hub上看到的push命令。請注意,您的命令將使用您的命名空間,而不是“docker”
為什么失敗了?push命令正在查找名為docker/getting started的圖像,但沒有找到。如果運行docker image ls,也不會看到
要解決這個問題,我們需要“標記”我們已經建立的現有鏡像,以給它起另一個名字
使用一下指令在Docker Hub上登錄
docker login -u YOUR-USER-NAME
使用docker tag命令為getting-start賦予一個新名稱,一定要把你的用戶名換成你的Docker ID
docker tag getting-started YOUR-USER-NAME/getting-started
現在再試一次你的推送命令
docker push YOUR-USER-NAME/getting-started
如果要從Docker Hub復制值,可以刪除標記名部分,因為我們沒有向圖像名稱添加標記。如果不指定標記,Docker將使用名為latest的標記
持久化數據庫
容器的文件系統
當一個容器運行時,它使用來自一個映像的不同層作為它的文件系統。每個容器也有自己的“暫存空間”來創建/更新/刪除文件。在另一個容器中看不到任何更改,即使它們使用相同的圖像
開啟一個Ubuntu容器,該容器能夠創建一個被稱為 /data.txt 的文件,該文件攜帶1到10000的隨機數
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
這條命令,啟動一個bashshell並調用兩個命令(所以使用&&)。第一部分選取一個隨機數並將其寫入/data.txt。第二個命令只是監視一個文件以保持容器運行
我們可以通過在容器中執行來查看輸出。為此,打開儀表板並單擊運行ubuntu映像的容器的第一個操作
您將看到在ubuntu容器中運行shell的終端,運行以下命令以查看/data.txt文件的內容,之后再次關閉此終端
cat /data.txt
如果喜歡命令行,也可以使用docker exec命令來執行相同的操作,首先需要獲取容器的ID(使用docker ps獲取它)並使用以下命令獲取內容
docker exec <container-id> cat /data.txt
同時啟動另一個ubuntu容器(相同的鏡像),我們將看到沒有相同的文件
docker run -it ubuntu ls /
通過以下指令刪除容器
docker rm -f
容器卷
在前面的實驗中,我們看到每個容器每次啟動時都從圖像定義開始
雖然容器可以創建、更新和刪除文件,但當容器被刪除並且所有更改都與該容器隔離時,這些更改將丟失,通過卷,我們可以改變這一切
卷提供了將容器的特定文件系統路徑連接回主機的能力,如果裝載了容器中的目錄,則在主機上也會看到該目錄中的更改,如果我們跨容器重新啟動裝載相同的目錄,我們會看到相同的文件
有兩種主要的卷類型。我們最終將兩者都使用,但我們將從命名卷開始
Persist the todo data
默認情況下,todo應用程序將其數據存儲在SQLite數據庫/etc/todos/todo.db中
由於數據庫是一個單獨的文件,如果我們能夠將該文件持久化到主機上並使其可供下一個容器使用,那么它應該能夠從上一個容器停止的地方恢復
通過創建一個卷並將其附加(通常稱為“裝載”)到存儲數據的目錄中,我們可以持久化數據,當容器寫入todo.db文件時,它將被持久化到卷中的主機
將命名卷簡單地看作一個數據桶
Docker維護磁盤上的物理位置,您只需記住卷的名稱,每次使用卷時,Docker都會確保提供正確的數據
通過下面指令創建卷
docker volume create todo-db
在儀表板中再次停止並移除todo應用程序容器(或使用docker rm-f<id>),因為它仍在運行,而不使用持久卷
啟動todo應用程序容器,添加-v標志以指定卷裝載,將使用命名卷並將其裝載到/etc/todos,它將捕獲在該路徑上創建的所有文件
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
一旦容器啟動,打開應用程序並將一些項目添加到您的待辦事項列表中
停止並刪除todo應用程序的容器,使用儀表板或 docker ps 獲取ID,然后使用 docker rm-f<ID> 將其刪除
Dive into the volume(偵測卷)
使用下面這條指令
docker volume inspect
docker volume inspect todo-db [ { "CreatedAt": "2019-09-26T02:18:36Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": {}, "Scope": "local" } ]
掛載點是磁盤上存儲數據的實際位置,在大多數計算機上,您需要具有root訪問權限才能從主機訪問此目錄
在Docker Desktop中運行時,Docker命令實際上是在您機器上的一個小虛擬機中運行的,如果您想查看Mountpoint目錄的實際內容,則需要首先進入VM內部
每次更改重建圖像都需要相當長的時間,使用綁定掛載,這個方法更好!
綁定掛載
通過綁定掛載
- 可以控制主機上的確切掛載點
- 可以使用它來持久化數據,但它通常用於向容器提供額外的數據
- 在處理應用程序時,可以使用綁定掛載將源代碼掛載到容器中,讓它看到代碼更改、響應,並立即看到更改
- 對於基於節點的應用程序,nodemon是一個很好的工具,可以監視文件更改,然后重新啟動應用程序
在大多數其他語言和框架中都有相應的工具
快速卷類型比較
綁定裝載和命名卷是Docker引擎附帶的兩種主要類型的卷。但是,可以使用其他卷驅動程序來支持其他用例(SFTP、Ceph、NetApp、S3等)
Named Volumes | Bind Mounts | |
---|---|---|
Host Location | Docker chooses | You control |
Mount Example (using -v ) |
my-volume:/usr/local/data | /path/to/data:/usr/local/data |
Populates new volume with container contents | Yes | No |
Supports Volume Drivers | Yes | No |
開啟一個調試模式的容器
要運行容器以支持開發工作流,我們將執行以下操作:
- 將源代碼裝入容器
- 安裝所有依賴項,包括“dev”依賴項
- 啟動nodemon以監視文件系統更改
確保沒有運行任何以前的getting-start容器,運行以下命令
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ node:12-alpine \ sh -c "yarn install && yarn run dev"
如果用的是windows的powershell,那么使用以下指令
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` node:12-alpine ` sh -c "yarn install && yarn run dev"
參數解釋
-dp 3000:3000
- Run in detached (background) mode and create a port mapping,后台運行並創建一個端口映射-w /app
- sets the “working directory” or the current directory that the command will run from,設置工作目錄或者指令運行的當前目錄-v "$(pwd):/app"
- bind mount the current directory from the host in the container into the/app
directory,綁定掛載從/app目錄下的容器下的主機中的當前目錄node:12-alpine
- the image to use. Note that this is the base image for our app from the Dockerfilesh -c "yarn install && yarn run dev"
- the command. We’re starting a shell usingsh
(alpine doesn’t havebash
) and runningyarn install
to install all dependencies and then runningyarn run dev
. If we look in thepackage.json
, we’ll see that thedev
script is startingnodemon
.
您可以使用docker logs-f<container id>查看日志
docker logs -f <container-id> $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
停止容器並構建新鏡像
docker build -t getting-started .
對於本地開發設置,使用綁定裝載非常常見,其優點是開發人員機器不需要安裝所有的構建工具和環境
使用一個docker run命令,將拉動開發環境並准備就緒
多容器應用
我們現在想將MySQL添加到應用程序堆棧中,下面的問題經常出現
- MySQL將在哪里運行?
- 在同一個容器中安裝或單獨運行?
一般來說,每個容器都應該做一件事並做好它
- 很有可能目前必須以不同於數據庫的方式擴展API和前端
- 單獨的容器允許獨立地設置版本和更新版本
- 雖然可以在本地為數據庫使用容器,但更希望在生產環境中為數據庫使用托管服務,沒人想把數據庫引擎和自己的應用一起發布
- 運行多個進程將需要一個進程管理器(容器只啟動一個進程),這增加了容器啟動/關閉的復雜性
因此,我們將更新我們的應用程序以如下方式工作:
容器網絡系統
在默認情況下,容器是獨立運行的,不知道同一台計算機上的其他進程或容器的任何信息
如果兩個容器在同一個網絡上,它們可以相互通信,如果他們不是,他們就不能
啟動MySQL
將容器放到網絡上有兩種方法
- 在開始時分配它
- 連接現有容器
現在,我們將首先創建網絡,並在啟動時附加MySQL容器
創建網絡
docker network create todo-app
啟動一個MySQL容器並將其連接到網絡,定義一些數據庫用來初始化數據庫的環境變量
參閱MySQL Docker Hub清單中的“環境變量”部分--https://hub.docker.com/_/mysql/
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7
如果使用windows的powershell
docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.7
這里使用了一個名為todo mysql data的卷,並將其掛載在/var/lib/mysql上
mysql就是在這里存儲數數據,我們從未運行docker volume create命令,Docker認識到我們想要使用一個命名卷,並自動為我們創建一個
為了確認數據庫已經啟動並運行,連接到數據庫並校驗連接
docker exec -it <mysql-container-id> mysql -u root -p
當密碼提示出現時,鍵入密碼,在MySQL shell中,列出數據庫並驗證您看到了todos數據庫。
mysql> SHOW DATABASES;
輸出可能是這個樣子
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
連接到MySQL
現在我們知道MySQL已經啟動並運行了,如果在同一網絡上運行另一個容器,如何找到該容器(記住每個容器都有自己的IP地址)?
為了解決這個問題,我們將使用nicolaka/netshot容器,它附帶了很多工具,這些工具對於解決或調試網絡問題非常有用。
使用nicolaka/netshoot映像啟動新容器,確保將其連接到同一網絡
docker run -it --network todo-app nicolaka/netshoot
在容器內部,我們將使用dig命令,這是一個有用的DNS工具,我們將查找主機名mysql的IP地址
dig mysql
輸出應該是
; <<>> DiG 9.14.1 <<>> mysql
;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44
在“ANSWER”部分,您將看到mysql的A記錄,解析為172.23.0.2(您的IP地址很可能有不同的值)
雖然mysql通常不是有效的主機名,但Docker能夠將其解析為具有該網絡別名的容器的IP地址(還記得我們前面使用的--network alias標志嗎?)
這意味着應用程序只需要連接到一個名為mysql的主機,它就會與數據庫通信
使用MySQL運行APP
todo應用程序支持設置一些環境變量來指定MySQL連接設置
MYSQL_HOST
- the hostname for the running MySQL serverMYSQL_USER
- the username to use for the connectionMYSQL_PASSWORD
- the password to use for the connectionMYSQL_DB
- the database to use once connected
雖然使用env vars來設置連接設置對於開發來說通常是可以的,但是在生產環境中運行應用程序時非常不鼓勵這樣做
更安全的機制是使用容器編排框架提供的秘密支持。在大多數情況下,這些秘密作為文件裝載在正在運行的容器中
您將看到許多應用程序(包括MySQL映像和todo應用程序)也支持env vars,並帶有一個∗FILE后綴來指向包含該變量的文件
例如,設置MYSQL\u PASSWORD\u FILE var將導致應用程序使用引用文件的內容作為連接密碼
Docker沒有做任何事情來支持這些環境變量,您的應用程序需要知道如何查找變量並獲取文件內容
我們將指定上面的每個環境變量,並將容器連接到我們的應用程序網絡
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"
如果使用windows的powershell
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"
如果我們查看容器的日志(docker logs<container id>),我們應該會看到一條消息,指示它正在使用mysql數據庫。
# Previous log messages omitted
$ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Connected to mysql db at host mysql Listening on port 3000
連接mysql
docker exec -it <mysql-container-id> mysql -p todos
輸出
mysql> select * from todo_items;
+--------------------------------------+--------------------+-----------+
| id | name | completed |
+--------------------------------------+--------------------+-----------+
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 |
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 |
+--------------------------------------+--------------------+-----------+
通過docker-compose將前面的操作合並,使得項目管理更加人性化
使用Docker Compose
Docker Compose是一個用於幫助定義和共享多容器應用程序的工具
使用Compose,我們可以創建一個YAML文件來定義服務,並且通過一個命令,可以將所有內容都擰在一起起來或扯開
使用Compose的最大優點是,您可以在文件中定義應用程序堆棧,將其保留在項目repo的根目錄下(它現在是版本控制的),並且可以方便地讓其他人為您的項目貢獻力量
安裝Docker Compose
如果您為Windows或Mac安裝了Docker Desktop/Toolbox,那么您已經擁有Docker Compose!
如果您在Linux機器上,則需要安裝Docker Compose:https://docs.docker.com/compose/install/
安裝結束后查看docker compose的版本
docker-compose version
創建一個compose文件
在應用程序項目的根目錄下,創建一個名為docker-compose.yml的文件
在compose文件中,我們將首先定義模式版本,在大多數情況下,最好使用支持的最新版本
version: "3.7"
接下來定義要作為應用程序的一部分運行的服務(或容器)列表
version: "3.7"
services:
現在可以一次性把一個服務遷移到一個compose文件中
定義應用服務
這個是之前用來定義應用容器的指令
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"
如果使用windows的power shell,使用下面指令
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"
首先,為容器定義服務入口和鏡像。我們可以為這項服務取任何名字。該名稱將自動成為網絡別名,這在定義MySQL服務時非常有用
version: "3.7"
services:
app:
image: node:12-alpine
通常會在鏡像定義部分看到命令,盡管對排序沒有要求
version: "3.7"
services:
app:
image: node:12-alpine command: sh -c "yarn install && yarn run dev"
通過定義服務的ports來遷移命令的 -p3000:3000 部分
version: "3.7"
services:
app:
image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000
接下來,我們將使用工作目錄和卷定義來遷移工作目錄(-w/app)和卷映射(-v“$(pwd):/app”)
Docker Compose卷定義的一個優點是我們可以使用當前目錄中的相對路徑
version: "3.7"
services:
app:
image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app
最后,使用environment鍵遷移環境變量定義
version: "3.7"
services:
app:
image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos
定義mysql服務
之前用過的容器定義指令
docker run -d \
--network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7
如果用windows的power shell則使用下面指令
docker run -d `
--network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.7
我們將首先定義新的服務並將其命名為mysql,以便它自動獲取網絡別名。我們將繼續並指定要使用的鏡像
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
接下來,我們將定義卷映射。當我們使用docker run運行容器時,命名卷是自動創建的
但是,使用Compose運行時不會發生這種情況,需要在頂層volumes:部分中定義卷,然后在服務配置中指定掛載點。只需提供卷名,就可以使用默認選項。不過,還有更多的選擇
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql volumes: todo-mysql-data:
最后我們只需要描述環境變量
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:
此時完整的 docker-compose.yml 文件內容應該如下所示
version: "3.7"
services:
app:
image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:
運行應用棧
保證沒有其他的應用/數據庫備份在之前運行
docker ps and docker rm -f <ids>
使用下面指令啟動應用棧,我們需要使用 -d 標識位使得一切都在后台運行
docker-compose up -d
理論上終端的輸出為
Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver Creating app_app_1 ... done Creating app_mysql_1 ... done
卷和一個網絡一起創建的,默認情況下,Docker Compose會自動為應用程序棧創建一個專門的網絡,這就是為什么沒有在Compose文件中定義一個網絡
使用下面命令查看日志,將看到每個服務的日志交織到一個流中
docker compose logs-f
f標志“跟隨”日志,因此將在生成時提供實時輸出
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) app_1 | Connected to mysql db at host mysql app_1 | Listening on port 3000
服務名稱顯示在行的開頭(通常是彩色的),以幫助區分消息。如果要查看特定服務的日志,可以將服務名稱添加到logs命令的末尾,例如
docker-compose logs -f app
當應用程序啟動時,它實際上會在那里等待MySQL啟動並准備就緒,然后再嘗試連接到它
Docker沒有任何內置的支持來等待另一個容器完全啟動、運行並准備就緒,然后再啟動另一個容器,對於基於節點的項目,可以使用等待端口依賴關系
把服務全部停掉
當准備把服務全部停掉時,只需要運行指令
docker-compose down
所有容器都會停止而且網絡會被移除
刪除卷
默認情況下,運行docker compose時不會刪除compose文件中的命名卷。如果要刪除卷,則需要添加--volumes標志
docker-compose down --volumes
鏡像構建最佳實踐
構建映像后,最好使用docker scan命令對其進行安全漏洞掃描(Docker與Snyk合作提供漏洞掃描服務)
docker scan getting-started
掃描使用一個不斷更新的漏洞的數據庫,因此看到的輸出將隨着新漏洞的發現而變化,但可能如下所示:
✗ Low severity vulnerability found in freetype/freetype
Description: CVE-2020-15999 Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641 Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2 From: freetype/freetype@2.10.0-r0 From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0 Fixed in: 2.10.0-r1 ✗ Medium severity vulnerability found in libxml2/libxml2 Description: Out-of-bounds Read Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791 Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1 From: libxml2/libxml2@2.9.9-r3 From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3 From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3 Fixed in: 2.9.9-r4
輸出列出了漏洞的類型、要了解更多信息的URL,以及修復漏洞的相關庫的版本
除了在命令行上掃描新構建的鏡像外,還可以配置Docker Hub自動掃描所有新推的鏡像
鏡像分層
使用docker image history命令,可以看到用於在圖像中創建每個層的命令
docker image history getting-started
你可以看到以下輸出
IMAGE CREATED CREATED BY SIZE COMMENT
a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B
f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B <missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB <missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B <missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB <missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B <missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB
每一行代表圖像中的一層。這里顯示的是底部的底座和頂部最新的一層,使用此功能,還可以快速查看每個層的大小,幫助診斷大型圖像
您會注意到有幾行被截斷了,如果添加--no trunc標志,您將得到完整的輸出
docker image history --no-trunc getting-started
層緩存
已經看到了分層的實際效果,那么有一個重要的前車之鑒可以幫助減少容器映像的構建時間
一旦圖層發生變化,所有下游圖層也必須重新創建
再看一次Dockerfile
# syntax=docker/dockerfile:1 FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
回到圖像歷史輸出,我們看到Dockerfile中的每個命令都成為圖像中的一個新層。
您可能還記得,當我們對映像進行更改時,必須重新安裝依賴項,每次構建時都圍繞相同的yarn依賴項進行發布是沒有多大意義
要解決這個問題,我們需要重新構造Dockerfile,以幫助支持依賴項的緩存
對於基於節點的應用程序,這些依賴項在package.json文件中定義。所以,如果我們先只復制那個文件,安裝依賴項,然后再復制其他所有內容
最后我們只在package.json發生更改時重新創建yarn依賴關系即可
首先更新Dockerfile以在package.json中復制,安裝依賴項,然后復制包中的所有其他內容。
# syntax=docker/dockerfile:1 FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]
在Dockerfile所在的文件夾中創建一個名為.dockerginore的文件,該文件包含以下內容
node_modules
.dockerginore文件是一種僅選擇性地復制圖像相關文件的簡單方法,在這種情況下,在第二個復制步驟中應該省略node\u modules文件夾
因為否則,它可能會覆蓋由RUN步驟中的命令創建的文件,有關為什么建議對Node.js應用程序和其他最佳實踐使用此方法的更多詳細信息,請參閱他們的Node.js web應用程序停靠指南
使用docker build指令創建一個新鏡像
docker build -t getting-started .
輸出如下
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Running in d53a06c9e4c2 yarn install v1.17.3 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 10.89s. Removing intermediate container d53a06c9e4c2 ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> a239a11f68d8 Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 49999f68df8f Removing intermediate container 49999f68df8f ---> e709c03bc597 Successfully built e709c03bc597 Successfully tagged getting-started:latest
現在在前端html文件隨意更改一處,重新執行一下指令
docker build -t getting-started .
現在輸出如下
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> Using cache ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Using cache ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> cccde25a3d9a Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 2be75662c150 Removing intermediate container 2be75662c150 ---> 458e5c6f080c Successfully built 458e5c6f080c Successfully tagged getting-started:latest
注意看1到4步,using cache,已經使用了緩存
多階段構建優點
- 將生成時依賴項與運行時依賴項分開
- 通過只提供應用程序運行所需的內容來減小整體圖像大小
Maven/Tomcat實例
在構建基於Java的應用程序時,需要使用JDK將源代碼編譯成Java字節碼
然而,JDK在生產中是不需要的,此外,可能正在使用Maven或Gradle等工具來幫助構建應用程序,在我們的最終鏡像中也不需要這些
# syntax=docker/dockerfile:1 FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
- 在本例中,我們使用一個階段(稱為build)來使用Maven執行實際的Java構建
- 在第二階段(從tomcat開始),我們從構建階段復制文件
- 最后一個映像只是創建的最后一個階段(可以使用--target標志覆蓋)
React 實例
在構建React應用程序時,我們需要一個節點環境來將JS代碼(通常是JSX)、SASS樣式表等編譯成靜態HTML、JS和CSS
如果我們不做服務器端渲染,我們甚至不需要為我們的產品構建節點環境。為什么不把靜態資源放在一個靜態nginx容器中呢
# syntax=docker/dockerfile:1 FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html