昨天我們成功運用多階段建置(Multi-stage Build)技術,將非同步 S3 微服務打包成僅有 500MB 的極輕量 Docker 容器,並在獨立隔離的環境中順利跑起來。
不過如果要做壓力測試的話,又要怎麼知道目前系統的問題點在哪裡呢?目前我們的微服務雖然做了很多錯誤處理方式,例如Double-check、Semaphore、Resilience4j降級等等,但是當一直出現錯誤時我們要怎麼快速找到系統的問題點並且對症下藥呢?要怎麼確定是程式碼導致系統效能不足?又或是程式碼沒問題瓶頸其實是在硬體環境上呢?
這時即時監控就非常重要,如果沒有監控,進行壓力測試就跟盲人摸象一樣,不知道系統什麼時候 CPU 達到 100% 飽和、不知道記憶體是接近崩潰還是游刃有餘,更不知道 Netty EventLoop 是否發生了請求塞車。
今天,將帶大家了解監控黃金三角組合:Spring Boot App + Prometheus (時序資料庫) + Grafana (視覺化儀表板)!並且,教大家如何省去多次 docker run 指令,透過Docker Compose 進行容器編排,實現一行指令,一鍵建立全套視覺化監控服務!

Spring Boot Actuator & Micrometer(指標提供者): 在微服務內部默默收集 JVM 記憶體、G1GC 垃圾回收頻率、CPU 佔用率、HTTP QPS、Netty 記憶體等指標,並透過 /actuator/prometheus HTTP 端點暴露成標準格式的文字數據。
Prometheus(指標收集器與時序資料庫 TSDB): 一個極輕量、專為監控設計的 TSDB 資料庫。它會以 Pull(拉取)模式,定時(例如每 5 秒)向我們的微服務發起 HTTP 請求,將最新的指標數值抓回並儲存起來。
Grafana(視覺化儀表板大腦): 最頂尖的視覺化圖表引擎。它向 Prometheus 查詢歷史與即時數據,將數字繪製成絕美的即時動態曲線圖(CPU 飽和線、JVM Heap 鋸齒波形、HTTP 200/429/500 比例)。
接觸 Docker 的工程師一定有遇過三個不同的服務分別包成容器,它們之間會不會遇到網路連線問題,還要手動去建立docker network,不然 Prometheus 就算知道 App 的 IP 也沒有辦法連線成功
透過 Docker Compose,我們可以省去執行三次 docker run和手動建立 Docker Network、傳遞複雜的 --link 或浮動 IP 參數的麻煩。因為一旦容器重啟 IP 改變,整個監控鏈路就會當場斷裂。換言之,Docker compose的優點如下
docker-compose.yml 檔案中。monitoring-net)。在同一個網路下,容器之間可以直接使用服務名稱(Service Name)互相存取!例如:Prometheus 只需要設定目標為 http://s3-app:8080,Docker 底層的 DNS 就會自動解析到 s3-app 容器的內部 IP,完全不需要擔心 IP 改變或網路卡住的問題!docker compose up -d 一鍵開機、docker compose down 一鍵關機。現在,我們打開之前的專案,按部就班地來建立配置文件
在前期的開發示範階段,直接將許多變數在 Java 程式碼中寫死例如aws ak, sk, bucketName等等,或是將參數散落在各個 @Value 中。但要寫好一個好的程式碼,必須落實 Cloud-Native 的 12-Factor App 原則:配置與程式碼嚴格解耦(Decoupling Config from Code)!
所以我們在resources/資料夾下新增一個application.yml 檔案,將變數移進去,好處有二:
application.yml 支援 ${VARIABLE:defaultValue} 語法。當微服務打包成 Docker 容器或部署到 K8s 時,我們可以直接從外部 Docker 容器環境變數注入真實的 AWS Credentials 與配置,完全不需要重新編譯 JAR 包!所以我們需要在application.yml 檔案中加入下列設定,包含s3變數以及後續要用到的prometheus 監控相關設定
server:
port: ${SERVER_PORT:8080}
spring:
application:
name: s3-async-service
s3:
bucket: ${S3_BUCKET:iron-netty}
region: ${AWS_REGION:ap-southeast-2}
semaphore-permits: ${SEMAPHORE_PERMITS:50}
upload:
max-concurrency: ${S3_UPLOAD_MAX_CONCURRENCY:50}
management:
endpoints:
web:
exposure:
include: "health,info,prometheus" # 開放 prometheus 端點
metrics:
tags:
application: ${spring.application.name}
resilience4j:
// 之前建立過的,以下省略
在 pom.xml 中引入 Spring Boot Actuator 與 Micrometer Prometheus 依賴:
<!-- Spring Boot Actuator 監控模組 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 轉換成 Prometheus 格式指標的 Micrometer 套件 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
prometheus.yml)在專案根目錄下新建 prometheus.yml 檔案,設定抓取目標:
global:
scrape_interval: 5s # 每 5 秒抓取一次指標數據
evaluation_interval: 5s
scrape_configs:
- job_name: 's3-microservice-job'
metrics_path: '/actuator/prometheus' # 指定 Actuator 端點
static_configs:
- targets: ['s3-app:8080'] # 利用 Docker Compose 內部 DNS!直接填容器服務名稱「s3-app」
docker-compose.yml在專案根目錄下新建 docker-compose.yml:
version: '3.8'
networks:
monitoring-net:
driver: bridge
services:
# 1. 微服務應用容器
s3-app:
build:
context: .
dockerfile: Dockerfile
image: my-app:v1
container_name: s3-app
ports:
- "8080:8080"
environment:
- AWS_REGION=ap-northeast-1
- AWS_ACCESS_KEY_ID={your_ak}
- AWS_SECRET_ACCESS_KEY={your_sk}
networks:
- monitoring-net
# 2. Prometheus 時序資料庫容器
prometheus:
image: prom/prometheus:v2.50.0
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
networks:
- monitoring-net
depends_on:
- s3-app
# 3. Grafana 視覺化儀表板容器
grafana:
image: grafana/grafana:10.3.3
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin # 設定預設管理員密碼為 admin
networks:
- monitoring-net
depends_on:
- prometheus
所以目前專案程式碼架構會是這樣:
當配置宣告完成後,真正的魔術時刻到了!打開 Terminal 終端機,執行:
# 因為我們有改動過程式碼,所以要加入--build重新打包一次image
docker compose up --build
如果執行成功沒有報錯會顯示以下畫面
若使用背景執行docker compose up -d 可以透過 docker compose ps 查看狀態,可以看到三個容器全部在 monitoring-net 網路下以 Up 狀態完美連線並存!

接著雖然容器成功建立起來,但裡面的不同服務的連線還不一定有成功,我們先打開http://localhost:9090,點擊上方Narbar的 Status -> Targets 看看連線狀態,有時候可能會發生這樣的狀況,發現沒有連上我們的微服務,通常可能的原因是設定檔沒有調好,或是pom.xml更新後沒有重新建立image導致系統讀到舊的版本

調整之後,正確畫面應該看到這樣Up
http://localhost:3000,輸入預設帳號 admin / 密碼 admin 登入。然後他會要求你重設新密碼,看要設定或跳過這個階段都可以
http://prometheus:9090 (雖然我們在本地瀏覽起端是用http://localhost:9090來看頁面但因為grafana和prometheus都是由同一個docker compose建立的,所以我們需要使用 Docker 內部網域 DNS 來宣告連線)。Save & test,連線成功會出現綠色「Successfully queried the Prometheus API」
匯入 JVM 經典儀表板 (Import Dashboard):
畫面中點擊 Dashboards(create your first dashboard)
選擇 Import a dashboard
在 Import via grafana.com 欄位填入 ID 4701 下方json不用填,點擊 Load
Data Source 選擇我們剛剛設定的 Prometheus,點擊 Import!
設定好後回到dashboard介面看到這個就大功告成啦
我們不僅重置了程式碼中全域變數的使用呼叫方式,也完成了容器化微服務最後一道關鍵的可觀測性(Observability)拼圖:
monitoring-net 橋接網路,解決容器通訊疑慮,實現微服務與監控網路設定明天,我們就可以在使用JMeter進行壓力測試!並且使用這套完整的 Grafana 監控服務,看壓測的結果究竟是 CPU 衝上 95% 飽和線、Semaphore(50) 限流閥回傳 429 擋下海嘯,又或是JVM OOM (Out Of Memory) 崩潰!