本文目標:
前一篇文章已經有向各位介紹容器化技術的用途,這篇文章會帶大家使用 Docker 去建構容器化的應用程式。
在 Docker 的世界中,會有許多的 image file,這些 image file 都是開發者撰寫 Dockerfile 後使用 docker build
產生而成的。
將 dockerfile 放置在需要打包的專案資料夾後,輸入:
docker build .
就可以將應用程式打包成 docker image 囉!
不過,這樣做會產生出一個沒有 tag 的 docker image,當 image 的數量變多時會變得不容易辨識。
為了方便 docker image 的識別與管理,開發者在編譯 image 時通常會使用 -t
為 image 打上 tag name:
docker build -t free5gc/amf:latest
如果想查詢 local 上存放了多少 image,也可以使用以下命令進行檢查:
docker image ls
image 與 container 就好比作業系統中 program 與 process 的關係,當 image 被 docker 執行後,就會產生出一個運作應用程式的 container。
要執行 local 環境的 docker image,可以使用以下命令:
docker run <IMAGE_NAME> --name <CONTAINER_NAME>
上面的 --name
flag 其實是可選的,它會以我們輸入的 <CONTAINER_NAME>
為 container 命名。
實際上,docker 還提供了很多不同的 flag 供開發者使用:
--privileged
:privileged mode--pull
:啟動前是否要從遠端的 docker registry 重新 pull image("always"|"missing"|"never"
)--volume
:將 host 的指令路徑對應到 container 內的指定路徑--net
:讓容器連接到 docker network補充:
docker run
等價於docker create
+docker start
。- 更詳細的操作可以參考了解 docker run 指令一文。
網路上有很多 Dockerfile 的範例可以參考,這邊以 free5gc/free5gc-compose 為例:
FROM free5gc/base:latest AS builder
FROM alpine:3.15
LABEL description="Free5GC open source 5G Core Network" \
version="Stage 3"
ENV F5GC_MODULE amf
ARG DEBUG_TOOLS
# Install debug tools ~ 100MB (if DEBUG_TOOLS is set to true)
RUN if [ "$DEBUG_TOOLS" = "true" ] ; then apk add -U vim strace net-tools curl netcat-openbsd ; fi
# Set working dir
WORKDIR /free5gc
RUN mkdir -p config/ log/ support/TLS/ ${F5GC_MODULE}/
# Copy executable and default certs
COPY --from=builder /free5gc/${F5GC_MODULE} ./${F5GC_MODULE}
COPY --from=builder /free5gc/support/TLS/${F5GC_MODULE}.pem ./support/TLS/
COPY --from=builder /free5gc/support/TLS/${F5GC_MODULE}.key ./support/TLS/
# Move to the binary path
WORKDIR /free5gc/${F5GC_MODULE}
# Config files volume
VOLUME [ "/free5gc/config" ]
# Certificates (if not using default) volume
VOLUME [ "/free5gc/support/TLS" ]
# Exposed ports
EXPOSE 8000
FROM
關鍵字會載入該容器所需的執行環境,以 amf 的環境來看,就需要 alpine
(一個輕量化的 Linux 作業系統,使用它可以方便開發者在容器執行期間進行除錯)以及 free5gc/base
(它是一個基於 ubuntu 的 image,我們在這個 image 上安裝 dependencies 並且編譯 NF 的執行檔案)。LABEL
關鍵字可以讓開發者新增該容器的資訊。ENV
關鍵字可以幫助我們定義一些變數,讓 Docker 執行剩餘指令時可以參考該變數的內容。ARG
關鍵字可以供我們定義 docker build
時需要傳入的參數。docker build -t NF_AMF --no-cache --build-arg DEBUG_TOOLS=true .
WORKDIR
關鍵字可以讓 Docker 知道需要移動到哪一個 Path 作為當前的工作目錄。VOLUME
關鍵字可以供我們建立一個 Docker volume,每一個 Docker volume 都會 mapping 到電腦中的實體檔案系統上,所以資料是可以永久保存的,不怕 container 停止運作以後遺失資料。EXPOSE
關鍵字可以告訴 Docker 要讓 Container 的哪一個 PORT 提供對外服務。除了像是剛剛那樣在 dockerfile 內使用 VOLUME
關鍵字,還可以在 docker run
時使用 -v
參數建立 volume:
docker run -it -v /Users/ianchen/storage:/storage ...
/Users/ianchen/storage
為電腦的實體位址,後者為 container 的 path。-it
參數可以讓 container 運作以後進入互動模式。此外,使用 docker volume ls
可以列出目前已經存在的 volume:
如果將情境換成在沒有指定實體路徑的情況下就建立 volume:
docker run -it -v /storage ...
可以透過以下方式檢查 volume 的實體位址:
docker inspect -f '{{.Mounts}}' CONTAINER_ID
假設已經有一個運作中並帶有 volume 的 Container: container1,若我們希望 container2 可以與 container1 共享 volume,可以使用以下指令:
docker run -it --volumes-from container1 --name=container2 ...
使用 docker container ls
可以查詢正在運作的 container。
使用 docker ps -a
可以列出所有的 container。
docker container
的主要功能是管理 container,除了 list(ls
)以外,還有 remove(rm
)、create、kill、exec 等功能。
我們在撰寫 Dockerfile 的時候,如果希望應用程式運作在 ubuntu 的環境上,那麼我們可能會以官方提供的 ubuntu image 作為 Dockerfile 的基礎環境,接著在上面使用 apt 安裝應用程式所需要的套件(git、make、node.js、mongodb 等等),當 image 的規模到達一個程度,我們又需要頻繁的更新 image file 時,就會碰到一些問題:
這幾個問題可以依靠幾種方式改善,不過,在提出改善方式之前,我們必須先知道一件事:docker image 有 layer 以及 cache 的概念!
我們在 Dockerfile 上面使用的每一行命令(如:COPY
、RUN
)對 docker 來說都是一層層 layer:
圖片出處:https://www.metricfire.com/blog/how-to-build-optimal-docker-images/
以上圖為例,Docker 在編譯 image 時會一層層 layer 進行階段式的編譯,並且每一層的編譯結果都會被 cache:
了解 layer 的概念後,就來談談如何最佳化 image 吧!
1. 精挑細選基本環境:如果應用程式一定要執行在 linux 上,可以評估是否需要用到 ubuntu image?還是 alpine 這類的 image 就能達成需求,這兩者的 size 可是差了數十倍呢!
2. 編譯過程中的檔案是否需要保留?像是 apt install
這類的都會保留安裝檔案在系統內,如果非必要,應該在安裝完畢後主動清除這些檔案以節省 image 的大小。
3. layer 的拆分:很多人提倡應該盡可能將功能類似的命令組合起來,以 apt install
為例,如果我的容器需要 3 個套件,我可以使用三個 RUN apt install
完成,也可以選擇使用 RUN apt install -y ...
一次安裝所有套件,當套件的數量非常大,一次性安裝時可能會帶來一個問題(如果 image 使用的套件數量需要增減,那麼編譯時這個 layer 的 cache 將變得不可用)。
4. 將功能類似的命令組合起來:這部分需要與第三點提到的情況一起考慮進去,但像是 ls
、cd
、mv
這種指令,就可以毫不猶豫地將它們合併在一塊囉!
5. 拆解 image:以本文使用的範例 free5gc-compose
來說,就是一個很好的例子!我們將 NF 的執行檔案編譯都交給一個 image 來做,其他的 NF container 只需要運作一個 alpine linux 上,並且在 image 編譯期間把 base image 內部的 NF 執行檔複製進來就好了。如果把 NF 執行檔案的編譯工作交給各別的 image,那麼空間的使用率會有數十倍到數百倍的差異。
這篇文章簡單的介紹 Docker 的使用方式,也順便介紹一下很實用的最佳化技巧,network 相關的技術會在下一篇文章中提到,考慮到系列文會接著介紹 docker-compose,所以我並不會花很多時間在 docker 指令的介紹(怎麼建立負責的網路模型、一些進階的使用技巧),如果對這些指令有興趣,可以直接翻閱 Docker 的官方文件。