iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Software Development

學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構系列 第 26 篇

[ Day 26 ] K8s 真的比較快嗎?用 JMeter 實測數據拆解:Docker vs k8s 3 副本叢集的 QPS、延遲與抗壓邊界

  • 分享至 

  • xImage
  •  

前兩天我們成功將非同步 S3 微服務部署至 Minikube K8s 叢集,並設定了Liveness/Readiness 雙探針實現了 0% 錯誤率的無感發佈,以及 Prometheus 容器直接跨進 Minikube 的網路的連線!

我們要來和前幾天的內容做壓力測試的比較,進行單機 Docker (1.5核) vs. K8s 固定 3-Node 的效能流量對決!從 QPS 吞吐量、P99 延遲分佈、資源利用率來比較,先說結論單機Docker比較好?! 為什麼呢? 不是因為 K8s 壞掉,而是隱藏著 K8s 內部 Service 網路轉發開銷、port-forward 隧道瓶頸與 Cgroups CPU 限流的底層真相!

今天就來帶大家一探究竟!

實驗規劃壓力測試

規劃

  1. 單機 Docker 容器(1.5 核 CPU / 512MB RAM)
  2. K8s 固定 3-Node 叢集(3 Pods,各配額 512MB RAM,0.5 核 CPU,總計 1.5 核 CPU)
  3. 實驗參數,參考Day18的設定方式
    • list 併發200、500、800,迴圈都重複5次
    • put 併發 20、50、80,迴圈都重複5次
    • 先list在put是為了不讓每次list的數量不一致,且put測試做完後我會到aws console手動刪除檔案,只留下大約100筆檔案列出

1. 單機 Docker 測試

  1. 首先為了硬體資源不要衝突,我們先手動關掉k8s服務 minikube stop
    https://ithelp.ithome.com.tw/upload/images/20260921/20183864bywhQNU2lL.png

  2. 修改prometheus.yml最後一行 - targets: ['s3-app:8080'] (docker的本地url)

  3. 執行docker compose restart 這樣就完成環境建置了

  4. 打開Jmeter就可以來實測protected的listObject和putObject兩個功能的api

2. k8s 3 node 測試

  1. 手動關掉docker compose建立的s3-app節點 docker compose stop s3-app
  2. 啟動服務minikube start
    https://ithelp.ithome.com.tw/upload/images/20260921/20183864K0uRIn9po6.png
    (如果你遇到和我一樣的狀況Address already in use,是因為 macOS 版 Docker Desktop底層的網路橋接器(vpnkit),在特定情況下釋放 Socket 連線的速度比較慢。 這時候手動重啟docker就好了)

正常要看到這樣喔

😄  minikube v1.39.0 on Darwin 26.0 (arm64)
✨  Using the docker driver based on existing profile
👍  Starting "minikube" primary control-plane node in "minikube" cluster
🚜  Pulling base image v0.0.51 ...
🔄  Restarting existing docker container for "minikube" ...
📦  Preparing Kubernetes v1.37.0 on containerd 2.3.4 ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
    ▪ Using image registry.k8s.io/metrics-server/metrics-server:v0.9.0
🌟  Enabled addons: default-storageclass, metrics-server, storage-provisioner

❗  /usr/local/bin/kubectl is version 1.34.1, which may have incompatibilities with Kubernetes 1.37.0.
    ▪ Want kubectl v1.37.0? Try 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
  1. 修改prometheus.yml最後一行 - targets: ['192.168.49.2:30080'](minikube ip),並且重啟服務docker compose restart prometheus
    檢查一下網頁target頁面是否連線成功
    https://ithelp.ithome.com.tw/upload/images/20260921/20183864KDNRuRPU6L.png

  2. 記得到grafana上再次建立data source和dashboard,步驟和Day相同,確認instances是minikube ip還是s3-app(這代表你還在監測單機docker的服務)
    https://ithelp.ithome.com.tw/upload/images/20260921/20183864uuQLZJT0Ow.png

  3. 再次建立url minikube service s3-app-service --url 並將產生的port貼到jmeter上即可
    https://ithelp.ithome.com.tw/upload/images/20260922/20183864LQhhr7A83y.png

實驗結果

我們使用 JMeter 對 ListObjects與 PutObject 進行多階段併發測試,結果如下:

1. ListObjects Protected API 效能對照

架構 併發(Threads x Loop) 平均延遲 (ms) 最小延遲 (ms) 最大延遲 (ms) 標準差 錯誤率 (%) 吞吐量 (QPS) 網路接收 (KB/s)
Docker 200x5 (1,000) 2,093 423 4,106 735.4 0 71.3/s 249.7 KB/s
Docker 500x5 (2,500) 2,632 1,064 4,953 725.4 0 128.5/s 511.3 KB/s
Docker 800x5 (4,000) 4,793 2,489 7,290 969.4 0.04% 134.9/s 536.8 KB/s
K8s 200x5 (1,000) 3,284 345 9,705 1,792.6 0 45.9/s 174.1 KB/s
K8s 500x5 (2,500) 5,328 3 24,544 6,167.5 91.28% 💣 65.1/s 58.1 KB/s
  1. 單機 Docker 表現出極強的 QPS 爆發力:在 500 併發下QPS高達 128.5 且錯誤率0。即使拉高到 800 併發,吞吐量依然推進至 134.9 QPS,僅出現微小的 0.04% 錯誤。
  2. K8s 3-Node 在 500 併發下已達瓶頸:在 200 併發時吞吐量只有 45.9 QPS (約為單機的 64%);且當併發拉升至 500 時,平均延遲飆升錯誤率暴增至 91.28%!
  3. 原因剖析: ListObjects 是純讀取、高 CPU / JSON 序列化型 API。當 500 個併發同時打進 K8s,每個 Pod 分到的 0.5 核 CPU 瞬間被大量 JSON 編解碼吃滿,導致 Netty EventLoop 無法及時處理 TCP Socket 握手,最終在 K8s Service 入口引發大面積連線超時崩潰。

Grafana docker list監控

單機 Docker 進行 List 200, 500, 800併發壓測時,Grafana 呈現三個極其陡峭的 CPU 爆發高峰,最高衝破 90%+,JVM 瞬間將 1.5 核算力發揮至極致。

https://ithelp.ithome.com.tw/upload/images/20260924/20183864Zw7VbiOQQV.png

Grafana k8s 3 node list監控

K8s 3-Node 進行 List 200, 500 併發壓測時,CPU 被 Cgroups 嚴格鎖定在 500m 天花板,500 併發衝擊時引發劇烈的 CPU Throttling 與 TCP 佇列塞車,所以error也有一個高峰
https://ithelp.ithome.com.tw/upload/images/20260924/20183864Z6foAYI8OK.png

分析

  1. 單機 Docker 獨佔 1.5 核算力爆發:ListObjects 屬於 CPU 密集型操作。單機 1.5 核允許單一 JVM 瞬間開啟全量執行緒處理,因此在 500 併發下能繳出 128.5 QPS 且 0% 錯誤率。
  2. K8s 單 Pod 0.5 核 (500m) 的硬傷:K8s 將算力拆成 3 個 0.5 核 Pod。當 500 個併發在 1 秒內湧入時,單一 Pod 瞬間分到 166 個請求,0.5 核 CPU 瞬間滿載,觸發 Cgroups 強制限流,導致大量的 HTTP 請求在 TCP Backlog 佇列中超時崩潰 (91.28% Error)。

PutObject Protected API 效能對照

架構 併發(Threads x Loop) 平均延遲 (ms) 最小延遲 (ms) 最大延遲 (ms) 標準差 錯誤率(%) 吞吐量 (QPS) 網路發送 (KB/s)
Docker 20x5 (100) 922 573 2,008 411.8 0 10.6/s 735.4 KB/s
Docker 50x5 (250) 1,886 574 5,796 901.0 0 17.2/s 1,185.9 KB/s
Docker 80x5 (400) 1,342 1 5,192 1,193.5 36.75% 🚨 26.3/s 1,821.0 KB/s
K8s 20x5 (100) 1,501 562 6,037 1,064.4 0 7.8/s 537.5 KB/s
K8s 50x5 (250) 2,495 605 8,151 1,513.3 0 13.8/s 947.6 KB/s
K8s 80x5 (400) 4,457 874 13,117 2,002.3 0 🛡️ 14.5/s 1,004.0 KB/s
  1. 單機 Docker 在 80 併發時崩潰 : 80 併發雖然 QPS 衝到 26.3,但因為只有 1 個 Semaphore(50) 控流閘門,多餘的 30 個請求在有限的連線池中死等超時,導致 36.75% 的請求拋出 HTTP 500 / Timeout。
  2. K8s 3-Node 成功展現高可用 (HA) 鋼鐵防線: 雖然 K8s 的 QPS 14.5/s 略低於單機,但在 80 併發 (400 筆請求) 衝擊下,錯誤率保持完美的 0.00%!最大延遲成功控制在 13 秒內。
  3. 原因剖析: K8s 將 3 個 Pod 分散開來,相當於全叢集擁有 3 x Semaphore(50) = 150 個控流配額,成功消化了 80 併發海嘯,證明了多節點在寫入防禦上的絕對安全性!

Grafana k8s 3 node put監控

K8s 3-Node 進行 put 20, 50, 80併發壓測時,受惠於 Semaphore(50) 門閥控流,CPU 平緩維持在 40% 左右,成功守住高併發衝擊。

https://ithelp.ithome.com.tw/upload/images/20260924/20183864Vk3Bu7FpmT.png

分析 : Put api結果和 list api相反

  1. 單機 Docker 在 80 併發時崩潰 (36.75% 錯誤率):單機容器只有 1 個 Semaphore(50) 門閥,多餘的 30 個請求在有限連線池中死等超時。
  2. K8s 3-Node 成功實現 0% 錯誤率鋼鐵防線:3 個 Pod 意味著全叢集總共擁有 3 x Semaphore(50) = 150 個控流 Permits!雖然因為網路轉發導致平均延遲略高 (4,457 ms),但 K8s 完美消滅了所有 500/504 錯誤,實現了 0.00% 錯誤率!

和想像的不一樣:為什麼 K8s 在高併發 List 下吞吐量較低?

明明都是 1.5 核 CPU,為什麼單機 Docker 的 QPS 總是高出 30%~50%?

答案就在於分散式開銷 (Distributed Overhead)與K8s 網路封包轉發路徑

1. K8s Service 負載均衡 (kube-proxy & NAT) 的路徑開銷

單機 Docker 的連線路徑極短: JMeter ➔ Localhost ➔ Docker Bridge ➔ Container Netty (1 step)

而在 K8s 叢集中,封包必須穿越繁瑣的網路虛擬化層:

https://ithelp.ithome.com.tw/upload/images/20260922/20183864DGD0v4hqcP.jpg

物理損耗三大主因:

  1. kubectl port-forward 本地隧道瓶頸:port-forward 是本地 User-space 的單執行緒 Go 程序,在高併發時,Socket Buffer 會率先在 Mac 門口塞爆。
  2. kube-proxy iptables NAT 轉換:封包進入 NodePort 後,iptables 必須進行 DNAT(修改目標 IP 為 Pod IP) 與 SNAT(修改來源 IP),每一次轉換都會消耗 CPU 運算並拉長 Latency。
  3. VETH (Virtual Ethernet) 雙向橋接:封包必須穿過 Linux 核心的虛擬網卡對轉交給 Pod,比單機 Docker 直連網卡多了兩次 Context Switch。

2. 多 JVM 與 Cgroups 時間切片 (Context Switch) 消耗

  • 單機 1.5 核:單一 JVM 進程獨佔 1.5 核,JIT 編譯與 GC 垃圾回收可以在同一個記憶體空間內集中高效處理。
  • K8s 3 個 Pod:背景同時運行著 3 個 JVM、3 套 Spring Context、3 套 GC 垃圾回收執行緒。 在 MacBook Air 8 核實體機上,3 個 Pod (limits.cpu: 500m) 會觸發 Linux Cgroups 的 CFS Bandwidth Throttling (時間切片限流),CPU 把大量算力浪費在暫存器上下文切換 (Context Switching),降低了純程式碼執行的效率。

總結

單機 Docker 雖然在純讀取 API 繳出了極致的QPS,但它就像沒有保險絲的高壓電,一旦爆掉就是 100% 死亡;K8s 3-Node 雖然付出了微小的負載均衡網路開銷,卻換來了 0 停機的自我修復與高可用鋼鐵防線!

明天,我們將加入重頭戲 HPA (自動水平擴展) 實測!解密 HPA 如何讓 PUT 寫入突破單機瓶頸,以及為什麼在高併發下我們需要量化容量預估!


上一篇
[ Day 25 ] k8s 剛啟動的 Pod 偷偷吃流量?實測 Readiness/Liveness 達到無感滾動更新 (Zero-Downtime) !
下一篇
[ Day 27 ] HPA 逆襲!PUT 上傳突破單機瓶頸 0% 錯誤率,高可用彈性擴充與 AWS 雲端算力成本精算!
系列文
學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言