昨天,我們完成了註冊功能相關的 DTO、Service 、 Controller 與Spring Security 的設定內容,走完了開發帳號註冊 API的流程。
不過,過程中我們沒有在本機安裝資料庫,或在IDE上新增資料庫連線設定。
因為我希望在這次開發的過程中,透過 Docker 來啟動相依的服務,取代需要手動安裝在電腦上的過程。對於這個專案來說,這麼做的好處有:
關於容器化,我也是一知半解,選擇了最常見的 Docker ,透過建立這個專案的過程,來慢慢摸索這個工具的使用方式,所以文中若有筆誤之處,由衷歡迎各位提出修正的建議!!
總之,今天我們會先介紹 Docker 中幾個比較重要的組件。而後說明如何使用 Docker 來為 auth-service
這項服務本身建立標準化的運行環境。
考量到篇幅,明天再介紹如何透過 docker-compose
來進行多容器管理,一鍵啟動所有服務(屆時也就會看到今天所提到的:如何透過容器啟用我們的資料庫服務)。最後再用 API 測試工具來驗證我們過去幾天的成果!
在撰寫 Dockerfile
和 docker-compose.yml
之前,想用一個建築工程的比喻,來理解這幾個核心概念之間的關係:
Dockerfile
:施工說明書
一份文字檔,裡面記錄了如何從無到有,一步步蓋出房子的所有指令與步驟(例如:先打地基、再砌磚、然後安裝門窗)。
Image
(映像檔):房屋的設計藍圖
按照 Dockerfile(施工說明書)中的說明繪製出的房屋設計圖。
是一個靜態且唯讀的 (read-only) 模板,裡面說明了房子本身(應用程式)以及所有必要的傢俱和管線(例如作業系統、JRE、各種函式庫)。
Container
(容器):根據藍圖實際蓋好的房子
根據 Image 內容建立的實例。您可以根據同一份藍圖,蓋出許多棟一模一樣、且互相獨立的房子。房子蓋好後,人們(請求)才能進去活動,它是動態的。
docker-compose
:整個社區的開發計畫書
僅有房子(應用程式本身)的社區是不能運作的,鄰近還會需要發電廠(好比資料庫服務 )、區公所(提供前端介面的服務)等,docker-compose 就像包含這些依賴的開發計畫書。它定義了:
services
)。network
)。depends_on
)。有了這份計畫書,我們就可以用一個指令 (docker-compose up
),一鍵完成整個社區的建造與啟動。
總結這些組件的關聯如下,括弧中的英文代表執行指令:
Dockerfile
(說明書) -- (build) --> Image
(藍圖) -- (run) --> Container
(實際的房子)
docker-compose
(社區計畫書) -- (up) --> 管理多個 Container
(整個社區)
因為篇幅的關係,無法在這篇文章裡很完整的說明容器化的概念,有興趣的朋友可參考網路大神們的介紹。
我們在 auth-service
資料夾下建立 Dockerfile
,其內容如下:
# ---- Build Stage ----
# 指定執行環境映像檔
FROM maven:3.9-eclipse-temurin-21 AS build
# 指定容器內的工作目錄
WORKDIR /app
# 1. 複製專案 POM 檔
# 因為根目錄是專案目錄,所以來源路徑是 'pom.xml'
COPY pom.xml .
# 2. 複製 auth-service 模組的 POM 檔
COPY auth-service/pom.xml ./auth-service/
# 3. 為了利用快取,先下載依賴
# 使用 -f 參數,明確指定 Maven 使用子模組的 POM 檔來下載依賴
RUN mvn -f auth-service/pom.xml dependency:go-offline
# 4. 複製 auth-service 模組的原始碼
# 來源路徑是 'auth-service/src',目標路徑是容器內的 './auth-service/src'
COPY auth-service/src ./auth-service/src
# 5. 執行 Maven 打包指令
# 這次直接在根目錄的 pom.xml 上下文執行,並指定只打包 auth-service
RUN mvn -pl auth-service -am clean package -DskipTests
# ---- Run Stage ----
FROM eclipse-temurin:21-jre
WORKDIR /app
# 從 build 階段,複製打包好的 .jar 檔
COPY --from=build /app/auth-service/target/*.jar app.jar
# 啟動容器時,執行上面那個打包好的jar檔
ENTRYPOINT ["java", "-jar", "app.jar"]
關於以上 Dockerfile 中常見的關鍵字作用說明,可以參考以下表格:
關鍵字 | 作用 |
---|---|
FROM |
指定此階段要基於哪個基礎映像檔 (Base Image)(也就是執行環境)來建置。 |
AS |
為建置階段取別名,方便後續階段引用。 |
WORKDIR |
設定容器內後續指令的工作目錄 (Working Directory)。 |
COPY |
從本機複製檔案到容器內。 |
RUN |
在容器內執行 Shell 指令。 |
ENTRYPOINT |
設定容器啟動時會執行的指令。 |
Docker 在建置映像檔時,是逐行執行 Dockerfile 中的指令的。每一行指令都會建立一個分層 (Layer)。
當 Docker 重新建置時,它會檢查每一行的指令及其依賴的檔案是否有變動。如果沒有變動,Docker 就會直接使用上次建置時留下的快取分層,而不會重新執行該指令。
我們的依賴 (pom.xml) 通常不常變動,而原始碼 (src 資料夾) 則會頻繁修改。因此,我們先 COPY pom.xml 並 RUN 下載依賴,再 COPY src。這樣一來,只要 pom.xml 沒變,即使我們修改了 Java 程式碼,Docker 在重建時也可以跳過依賴下載步驟,直接使用快取,大幅提升建置效率。
這裡採用 Multi-stage 的方式建立Dockerfile
,這兩個 stage 的分別是:
Build Stage
(建構階段):
Run Stage
(運行階段):
build
階段複製了打包好的那個 .jar
檔並執行。這麼做的好處除了建構過程更清晰外,還可以:
因為這個服務依賴資料庫服務,因此今天撰寫好dockerfile後暫不執行,明天我們完成docker-compose後再啟動服務進行測試。