iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

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

[ Day 25 ] 無感發佈的黑魔法 - 實測 K8s 雙探針如何在持續併發下達成 0 掉包滾動更新?

  • 分享至 

  • xImage
  •  

昨天我們成功撰寫了 DeploymentService YAML 檔,將非同步 S3 微服務部署至 Minikube 叢集,並親眼驗證了當 Pod 被殺掉時,宣告式 API 如何在 1 秒內自動長出全新的 Pod 完成修復。

然而,在雲端高可用(High Availability, HA)營運的現實世界中,還隱藏著兩個極其致命的隱形炸彈:

  • Pod 剛轉為 Running 狀態,但 Spring Boot Context 還在初始化,流量打進去當場爆發 Socket 斷裂慘案怎麼辦?
  • 系統上線一段時間後發生 JVM 死鎖或連線池乾涸,Pod 進程雖然沒崩潰,但請求打進去全是無效卡死,誰來幫我們自動拔插頭重啟?

今天,我們要手把手實作Liveness 和 Readiness 雙探針機制!對比無探針崩潰慘案與有探針 0% 錯誤率無感發佈的巨大差異,並示範 Prometheus 監控對接的架構抉擇!


Liveness vs Readiness 雙探針

在 K8s 中STATUS: Running 僅代表容器進程(Process)還活著,並不代表應用程式具備處理流量的能力!

https://ithelp.ithome.com.tw/upload/images/20260919/20183864fxhQkpyjJa.png

為了精準監控微服務健康狀態,K8s 提供了兩種探針:Readiness Probe, Liveness Probe

1. Readiness Probe (就緒探針:控管流量門閥)

  • 詢問應用程式是否準備好接收流量?檢查微服務是否已經完成初始化(如 Spring Boot 啟動需要 15 秒)。
  • 如果 Readiness 探針失敗,K8s 不會殺掉 Pod,而是立刻將該 Pod 從 Service 的 Endpoints 列表中移除。流量不會打給未準備好的 Pod,徹底告別部署過程中的 502 Bad Gateway

2. Liveness Probe (存活探針:負責拔插頭重啟)

  • 確認容器還正常活著或內部有無陷入死鎖?檢查微服務是否發生不可逆的崩潰(如 JVM Deadlock 或 Thread 飢餓)。
  • 如果 Liveness 探針連續失敗(超過 failureThreshold),K8s 會強制殺掉容器並重新啟動,實現無人值守自動修復!

多節點 (Multi-Node) vs 多叢集 (Multi-Cluster)

稍微補充一下大家比較容易混淆的概念

    1. 同一個叢集內的多個節點 (Nodes in a Cluster)
    • 目的:不是為了按功能分開,而是為了 高可用 (HA) 與資源分流
    • 比喻:一家公司的 3 台伺服器(Node 1, Node 2, Node 3)組成同一個 K8s 叢集。K8s 會把我們的 s3-app 副本均勻分散在不同 Node 上。當 Node 1 實體斷電時,Node 2 和 Node 3 上的 Pod 繼續對外服務。
  1. 不同的獨立叢集 (Multi-Cluster)
    • 目的:用於 環境完全隔離(如 Dev 開發叢集 vs. Prod 生產叢集),或者 跨地域災備(如美東叢集 vs. 東京叢集)。
    • 監控系統(Prometheus / Grafana)在大型企業中可以放在獨立的「監控維運叢集」,統一監控所有業務叢集的健康度。

本地實作一:有無探針之比較

  1. 打開兩個terminal視窗

    • 一邊執行 kubectl get pods -w,列出所有的pod及時資訊
    • 另一邊輸入kubectl rollout restart deployment s3-app-deployment 等待一下
  2. 打開我們之前壓力測試的jmeter

    • 選擇最新建立好的listObject protected的api, s3/api/async/files/protected
    • 將原本的port 8080 調整成我們之前用minikube service s3-app-service --url產生的port
    • 並且先設定併發數100,重複5次,總共發500筆list的請求
  3. 按下jmeter發送請求的瞬間,請同步切回terminal視窗按下restart deployment

  4. 開始觀察jmeter的發送狀況以及terminal左側的即時pods資訊

    • 會發現jmeter持續發送請求的時候,會有很多失敗,且出現NoHttpResponseException錯誤
    • 左側terminal視窗經歷的一輪pods汰舊換新
    • https://ithelp.ithome.com.tw/upload/images/20260920/20183864BGeKpn9M9p.png
  5. 接著再右側視窗輸入kubectl get pods -o wide列出pods詳細資訊,就會發現重新佈署的pods和原本的是不同的,ip不一樣了

  6. 就算想要透過log找到之前jmeter請求時的錯誤紀錄也找不到因為是在容器更新跌代的時候發生的
    https://ithelp.ithome.com.tw/upload/images/20260920/20183864w2LhYTfKJn.png

詳細補充說明一下restart deployment的過程:

  • 在第 5 秒時,全新 Pod t98h9 進入了 STATUS: Running
  • 因為缺乏就緒探針,K8s 只要看到 Docker 容器進入 Running,就天真地以為應用已經 ready,立刻把 Service 門牌的外部流量轉發給這個新 Pod
  • 但此時 Spring Boot 內部還在載入 Context 與 S3 SDK(需要 10~15 秒)。流量打進來時,Java 的 Socket 監聽門閥根本還沒開好
  • 壓測工具發射 TCP 封包過去,結果對面直接把 Socket 斷開,連一個字元的 HTTP Header 都回不出來,當場爆發 NoHttpResponseException(Socket 斷裂)

在 Deployment YAML 注入雙探針與 Spring Actuator

  1. spring boot 的application.yml設定檔中加入probes設定,
    • 程式碼有調整所以要重新docker build -t my-app .
    • k8s也要重新載入一次image minikube image load my-app:latest
management: 
  endpoints: 
    web: 
      exposure: 
        include: "health,info,prometheus" # 開放 prometheus 端點 
  endpoint:
    health:
      probes:
        enabled: true # 開啟 /actuator/health/liveness 與 /actuator/health/readiness
  1. 更新 s3-app-deployment.yaml
    • 修改完後 kubectl apply -f k8s/s3-app-deployment.yaml 更新k8s
    spec:
      containers:
      - name: s3-service-container
        image: my-app:latest

        # 💡 1. Readiness Probe (就緒探針)
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10   # 容器啟動 10 秒後開始首次檢查
          periodSeconds: 5          # 每 5 秒檢查一次

        # 💡 2. Liveness Probe (存活探針)
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 20   # 容器啟動 20 秒後開始首次檢查
          periodSeconds: 10         # 每 10 秒檢查一次
          failureThreshold: 3       # 連續失敗 3 次則判定死亡並重啟
  1. 重複前面實驗的做法,打開兩個terminal視窗和jmeter,重新實驗

  2. 觀察的過程中會發現Jmeter 沒有出現任何錯誤! 0% error ,請求全數通過
    https://ithelp.ithome.com.tw/upload/images/20260920/20183864xVhtrjhIZh.png

  3. 觀察左側pods及時資訊
    https://ithelp.ithome.com.tw/upload/images/20260920/20183864MIiIZV8Kzi.png

實現零停機部署

上面那張即時資訊的截圖可能還是太過複雜,簡單來說可以簡化成這樣

NAME                               READY  STATUS   AGE
s3-app-deployment-66f5f9bd6b-7d9df   1/1  Running     2m26s
s3-app-deployment-66f5f9bd6b-fqx7l   1/1  Running     3m8s
s3-app-deployment-66f5f9bd6b-s9qg6   1/1  Running     3m45s
s3-app-deployment-849f44f6bb-ghg9k   0/1  Pending      0s
s3-app-deployment-849f44f6bb-ghg9k   0/1  Running      1s -> 進入 Running,但 READY 是 0/1!
s3-app-deployment-849f44f6bb-ghg9k   0/1  Running      51s  ->  靜置等待 51 秒探針通過
s3-app-deployment-66f5f9bd6b-s9qg6   1/1  Terminating  8m31s ->  新 Pod 通過後舊 Pod 才退場!
  • 全新 Pod ghg9k 雖然在第 1 秒就進入 Running,但 READY 保持為 0/1。K8s 就緒探針持續打 /actuator/health/readiness,在其回傳 200 OK 之前,Service 嚴格禁止將任何流量轉發給它
  • 在長達 51 秒的初始化等待期間,舊 Pod s9qg6 保持 1/1 Running 在線上無縫處理 100% 的外部請求
  • ghg9k 在第 51 秒正式拿到 READY 1/1 後,K8s 才將其納入轉發清單,並緊接著發射 SIGTERM 關閉舊 Pod
  • 壓測工具全程保持 0.00% 錯誤率,達成真正的 Zero-Downtime Deployment(零停機部署)!

監控架構選擇:Docker Compose vs. K8s

我們之前在使用docker compose建立服務的時候有提到docker中內部網路有自己的連接方式,會透過docker network來設定,在把微服務搬上 K8s 後,之前Day17提到的 Prometheus 與 Grafana 監控有兩種不同的架構做法:

維度 A:Docker Compose 監控跨網路對接(本地推薦) B:K8s 全量 Pod 原生化部署(生產環境)
部署方式 Prometheus 繼續跑在原本 Docker Compose,指向 host.docker.internal:30080 將 Prometheus & Grafana 也撰寫成 K8s Deployment/Service 部署。
記憶體消耗 極低,不佔用 Minikube 寶貴的 2GB 算力土地。 較高,需要在 K8s 內額外劃分算力給監控 Pods。
高可用性 (HA) 監控為單機 Docker Compose,無自癒能力。 監控系統本身也享有 K8s 的自動自癒與高可用保護。
歷史儀表板 零重建成本,Day 17 建好的 Grafana Dashboard 直接繼續用! 需要透過 Helm Chart 或 ConfigMap 重新匯入 Dashboard。

筆電重啟後,Minikube IP 會變嗎?

  • 在 Minikube 中,執行 minikube stopminikube start,Minikube IP(如 192.168.49.2)通常是固定的。但如果 minikube delete 重新建立,IP 確實有可能改變!
  • 在 macOS / Windows 的 Docker Compose 中,可以直接使用 host.docker.internal:30080 作為 Target!會自動解析為宿主機轉接 IP,無論 Minikube IP 怎麼變都不需要手動修改 YAML!

本系列選擇

這部分為了教學方便我們先採用方法 A!不過除了調整 prometheus.yml之外我們還必須讓 Prometheus 容器直接跨進 Minikube 的網路

  1. 先查出 Minikube 的 IP:
    minikube ip
    (假設印出 192.168.49.2)
  2. 找到Prometheus 容器的id docker ps,找到Prometheus那個欄位對應的id,並且加到下一步的網路中
  3. 把 Prometheus 容器橋接到 minikube 網路:docker network connect minikube <prometheus-container-id>
  4. 修改 prometheus.yml:
scrape_configs: 
  - job_name: 's3-microservice-job' 
    metrics_path: '/actuator/prometheus' # 指定 Actuator 端點 
    static_configs: 
      - targets: ['192.168.49.2:30080'] # 調整這行即可
  1. 重啟 Prometheus docker compose restart prometheus
    https://ithelp.ithome.com.tw/upload/images/20260921/20183864q811F6JfjF.png

  2. 再次進入localhost:9090裡面的Target頁面來觀察是否有連線成功,看到是up就代表沒問題啦

https://ithelp.ithome.com.tw/upload/images/20260921/20183864hNgSL1n4uN.png

總結

今天我們完成了 K8s 高可用防線的兩大重要實作:

  1. 無探針:滾動更新時爆發 NoHttpResponseException,造成線上服務中斷。
  2. 有探針:透過 READY 0/1 靜置保護,達成 0% 錯誤率無感發佈!

也成功將Prometheus的監控成功指向新部屬在k8s上的服務了,明天我們將展開整套課程最重要的壓測實驗!我們將用相同的流量,對單機 Docker、K8s 固定 3-Node以及 K8s HPA 動態擴容三種模式進行 putObject(非同步上傳)與 listObjects(快取/Semaphore 保護)的全方位測試!


上一篇
[ Day 24 ] 告別人工維運 - 從 Docker image 到 K8s 叢集,用 2 個 YAML 檔打造 3 副本高可用微服務
系列文
學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言