Skip to main content

Command Palette

Search for a command to run...

Chaos for Docker - Pumba(a?)

Updated
5 min read
Chaos for Docker - Pumba(a?)

Chaos Engineering

「混沌不是深淵,而是發現系統韌性的鏡子」

混沌工程是什麼,不是什麼?

最常見的誤解是混沌工程只是隨機地破壞營運環境中的事物

只要可以幫助我們確信系統可以抵禦突發事件的方法其實都可以稱為混沌工程。混沌工程主要透過實驗性的方法,從而建立對系統抵禦營運環境中突發事件能力信心的工程。換句話說,混沌工程是一種透過主動注入故障來驗證系統韌性的方法,目標是在真實故障發生前暴露系統脆弱性。

混沌工程 v.s. 故障演練 v.s. 測試 的差別

混沌工程實驗聽起來與另外兩個很像, 但目的其實差異很大。

  • 混沌工程 v.s. 測試

測試目的是關注已知的斷言是否通過. 混沌工程關注的是發現更多未知的暗債。

  • 混沌工程 v.s. 故障演練

故障演練側重於操練已知的故障應對流程, 混沌工程側重透過實驗發現更多未知的暗債

在營運環境上執行實驗, 會有風險

其實任何有人在使用的系統都會有所謂的暗債, 未知的意外可能發生, 這些都是不可預測的. 即使我們不主動引入故障風險, 其實這些風險本來就存在著, 不是不會發生只是時候未到. 所以混沌實驗要定義最小化爆炸半徑來進行實驗.

其次團隊文化也不該一昧要求工程團隊不能也不應該出現任何問題或失誤. 一但有這樣強硬的規範定案下去, 以後在未知的故障出現時, 大部分會出現甩鍋情況, 耽誤了更多發現更多暗債以及設計防範措施的時間.

嘗試看看

團隊如果有以下理由的話,不坊考慮實踐混沌工程試試看︰

  • 確定風險與成本,並設定 SLI、SLO 與 SLA。

  • 在整體上測試系統(相比於端對端的測試,更加全面)。

  • 找到團隊忽略的**湧現性(Emergence)**特性。在複雜系統中,服務與組件之間的複雜交互可能導致意想不到的系統行為。例如,多個服務自己正常運作,但組合在一起時於高負載下出現極大的延遲或故障發生。

實驗的 4 個步驟︰

定義穩態指標

定義什麼是正常的,這取決於該系統跟目標。

假設在測試車輛,車輛的穩態可能涉及速度、機械狀態(如無刮傷)、路線等。這說明穩態指標需根據具體系統的目標而定,不同系統有不同的關鍵參數。我們目標可以是輛在無刮傷的情況下以80公里/小時的速度完成指定路線,也能是其他的條件視為正常。為此我們可以定義幾個要素:可測量性目標相關性容許範圍時間持續性。例如,車輛的例子中,速度需可測量,80公里是目標值,允許一定波動範圍,並需在整個行駛時間內維持。

穩態指標的四大要素

要素定義說明車輛測試案例系統測試案例
可測量性指標是否可以被量化和測量,並具有客觀數據支持速度可由傳感器測量;車體狀態可由影像辨識檢測API延遲時間可由監控工具測量;錯誤率可通過日誌分析獲取
目標相關性指標是否與系統的業務目標或測試目標直接相關車輛需在無刮傷的情況下以80公里/小時完成指定路線99%的請求需在500ms內完成;系統需保持99.95%的可用性
容許範圍指標允許的波動範圍,超出範圍則視為異常速度允許±5公里/小時;刮傷深度需≤2毫米延遲時間P99需≤500ms;錯誤率需≤0.1%
時間持續性指標需要在多長時間內保持穩定,才能被認為系統處於穩態需在整個行駛過程中(如1.5小時)保持穩定每月需保持99.95%的可用性,或在1分鐘內恢復異常

最常見的目標大概是「9x %的使用者可以在 xxx ms 內訪問系統的 API」。要定義哪些穩態指標,應該直接由業務策略來驅動設計的。能參考 Alex 的 Implementing Service Level Objectives: A Practical Guide to SLIs, SLOs, and Error Budgets 一書。

但我們前面有說混沌工程是更加全面的。其實很多因素都會影響這個指標。例如系統程序是否獲得足夠的 CPU time 與 memory?或者這些資源正在被其他程序佔用著。是不是 kernel 把 CPU 分配給令一個更高優先權的程序?諸如此類的。

設計實驗假設

在這階段,我們要把直覺塑造成一個可驗證的假設,在一個明確定義的問題出現時,你的系統會發生什麼情況的有根據的猜測。系統會繼續工作嘛?還是逐漸變慢?變慢了多少?

現實中容易出問題的場景大概常見的如下︰

  • 環境事件(斷電、水災、地震…)

  • 硬體故障(CPU, Mem, Disk, Switch、變壓器)

  • 系統資源(CPU, Mem, 硬碟空間、頻寬)

  • 軟體問題(Bug, Crash, Leak, Hack attack)

  • 系統瓶頸

  • 不可預期的湧現性特性

  • 硬體的 bug

  • 人為失誤(設定錯誤、不小心關機….)

最簡案例

o11y 顯示的是我們跟使用者是否能夠成功調用 API。穩態指標是 API 都能成功的回覆。實驗假設是如果 cache 失效了,依然能獲得回覆。執行實驗,發現現在版本存在缺陷,新版本修復後正常。

實驗階段關鍵動作量化指標範例
定義穩態指標確立系統正常行為基準API P99 延遲 < 200ms,錯誤率 < 0.1%
設計實驗假設明確預期故障影響範圍當 30% 節點失效時,服務降級但不停機
執行故障注入可控環境下觸發故障隨機終止 Pod,網路延遲 500±50ms
觀察與分析比對穩態指標偏差錯誤率飆升至 15%,自動擴容觸發延遲

常見誤區澄清表

迷思現實
隨機製造混亂精準注入故障模式
營運環境專用建議先在預發環境驗證
一次性活動需納入CI/CD常態流程
僅測試基礎架構應包含業務邏輯驗證

Pumba(a)

The Lion King Pumbaa and Simba Sticker - Sticker Mania

Pumba 是一個針對 Docker 容器進行混沌測試的工具,可以終止容器(kill、stop、remove),模擬網路問題(netem)、對容器的 cgroup 資源進行壓力測試(stress)等。

終止容器

模擬隨機服務崩潰的情境,來測試系統的容錯能力(Fault Tolerance)與高可用性(High Availability)。

PauseStop 的區別是,pause 只是 凍結程序,不會讓容器真正停止或重啟。stop完全停止 容器,需要手動或透過 restart 設定來啟動。

pumba --signal SIGKILL/SIGINT \
    kill \
    <container_name>

# 隨機 kill 開頭名為 "killme" 的容器 (每 30 秒一次)
pumba --random \
      --interval 30s \
      kill \
      're2:^killme*'

# 每 15 秒,會隨機挑選 名稱以 stopme 開頭 的容器,並執行 stop 命令讓它暫停運行。
# 容器會 停 7 秒,然後依據 Docker 的 restart 設定可能會自動重啟
# (如果 Docker 配置為 restart=always 或 restart=on-failure,則容器會重啟)。
pumba --interval=15s \
    --random \
    stop \
    --duration=7s \
    --restart \
    're2:^stopme*'

# 每 15 秒,pumba 會對名為 testme 的容器執行 pause 命令。
# 容器會 暫停 10 秒,10 秒後,容器會自動恢復執行。
pumba --interval=15s \
    pause \
    --duration=10s  \
    testme

模擬網路問題

pumba netem 命令用來模擬不同的網路異常,例如 延遲(delay)、封包丟失(loss)、封包重複(duplicate)、封包損壞(corrupt) 以及 頻寬限制(rate)。這些功能對於 混沌工程(Chaos Engineering) 或 網路測試 非常有幫助。

但還是得看應用場景,決定合適的模擬數值。

應用場景適用性建議模擬設定
衛星通訊模擬★★★★★維持 3000ms 延遲,抖動 ±50ms,封包丟失 2%
物聯網(IoT)設備測試★★★☆☆延遲 100-500ms,封包丟失 1-3%
金融交易系統(HFT)☆☆☆☆☆應確保延遲低於 50ms,避免網路異常
5G 通訊測試★★★★☆延遲 50ms,封包丟失 0.5%,模擬擁塞情境
邊緣運算(Edge Computing)★★★☆☆延遲 500ms,封包丟失 2-5%
遊戲伺服器壓測★★★★☆延遲 100ms,封包丟失 1%,模擬真實網路環境
# 每20秒觸發一次對 ping 名稱容器執行 Delay 動作,
# 每次模擬持續 10 秒,增加基礎延遲 3000 ms,延遲抖動± 20ms
# 針對Alpine Linux ,--tc-image=gaiadocker/iproute2,pumba 需要 tc(Traffic Control)來執行網路模擬,
# 但 tc 可能沒有內建在 Docker 容器裡。告訴 pumba 使用 gaiadocker/iproute2 這個 image 來執行 tc 命令。
# correlation 20,代表 20% 的相關性,用來控制 隨機延遲的變動程度。
# 如果沒有 correlation,每次延遲程度都是隨機的。
# 有設定的話,多少會受到上一個封包的延遲影響。這讓延遲變動較為平滑,而不會完全隨機跳動。
pumba --interval=20s \
    netem \
    --tc-image=gaiadocker/iproute2 \
    --duration=10s delay \
    --time=3000 \
    --jitter=20 \
    --correlation 20 \
    ping

# 每20秒觸發一次對開頭是 payment-service 名稱容器執行 Loss 動作,
# 讓 15% 的封包在傳輸過程中被丟棄。
# duplicate 與 corrupt 用法與 loss 一樣。
pumba --duration 20s \
    netem \
    --tc-image gaiadocker/iproute2 \
    loss --percent 15 \
    're2:^payment-service'

# 限制名為 mycontainer 的容器出站流量,最高頻寬為 512 kbit/s。
pumba netem rate \
    --rate 512kbit \
    mycontainer

# 限制開頭名為 web 的容器出站流量,最高頻寬為 1 Mbit/s。
# 每個封包額外增加 50 bytes 的開銷,模擬實際網路傳輸中的額外payload。
pumba netem rate \
    --rate 1mbit \
    --packetoverhead 50 \
    're2:^web'

壓力測試

pumba stress 是 Chaos Engineering 工具 Pumba 中用於對容器施加壓力測試的命令,主要透過 stress-ng 工具模擬 CPU、記憶體、I/O 等資源的高負載情境,也能驗證集群自動擴展能力。或者測試記憶體有無洩漏問題,監控 GC 的行為。

# 對名為 web-server 的容器施加 4 個 CPU 核心的壓力,持續 30 秒
pumba stress \
    --duration 30s \
    --stressors "--cpu 4" \
    web-server

# 對開頭名為 web 的容器們進行 4 核心 CPU 和
# 啟用 2個獨立的記憶體壓力執行緒 ,各自分配 2GB 虛擬記憶體空間做測試,持續 1 分鐘
pumba stress \
    --duration 1m \
    --stressors "--cpu 4 --vm 2 --vm-bytes 2G" \
    "re2:^web"

案例整合 OpenTelemetry

使用上次 DevOps meetup的 OpenTelemetry Demo專案來演示。只是加入 Pumba 。

對指定容器注入 delay

  # Pumba
  pumba:
    image: gaiaadm/pumba:latest
    container_name: pumba
    privileged: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command:
      - --log-level=info
      - --interval=30s
      - netem
      - --tc-image=gaiadocker/iproute2
      - --duration=10s
      - delay
      - --time=3000
      - --jitter=500
      - checkout-service

注入前

注入後

可以看見,延遲突然提高到 5秒以上,但其實也不是全部請求都這麼久。

點擊圖上的 data point,來看該 request 的 tracing,能發現瓶頸是卡在 checkout service,因為 pumba 就是針對 checkout service 做注入咩。

再仔細看,其實也不是 checkout service 每個都很慢,那到底問題出在哪?
其實 pumba delay 是針對 egress 方向,也就是該容器對外發出的流量。所以 checkout service 要回應給 fronted 會變慢。然後下圖 checkout service 對 cartservice 的請求也是會受到影響,明明 cartservice 回應不到 1ms,但 checkut service 好幾秒才收到回應。

對隨機容器進行 Stop

  # Checkout service
  checkoutservice:
    restart: always
    labels:
      - "chaos=true"  

  # Cart service
  cartservice:
    restart: always
    labels:
      - "chaos=true"  

  # Pumba
  pumba:
    image: gaiaadm/pumba:latest
    container_name: pumba
    privileged: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command:
      - --log-level=info
      - --random
      - --interval=15s      
      - --label=chaos=true
      - stop
      - --duration=5s
      - --restart

注入前,能看見好幾分鐘沒錯誤,請求也正常,API 請求沒有 500。

注入後,首先能看到 pumba log,隨機挑選滿足 labels 條件的容器進行 stop, 5秒後啟動。

pumba  | time="2025-02-21T16:38:52Z" level=info msg="stopping container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service signal=SIGTERM timout=5
pumba  | time="2025-02-21T16:38:57Z" level=info msg="starting container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service
pumba  | time="2025-02-21T16:39:07Z" level=info msg="stopping container" dryrun=false id=883cb3ae71fb89a48ab63601be61729e548781832cb318affdce441004d48a46 name=/cart-service signal=SIGTERM timout=5
pumba  | time="2025-02-21T16:39:12Z" level=info msg="starting container" dryrun=false id=883cb3ae71fb89a48ab63601be61729e548781832cb318affdce441004d48a46 name=/cart-service
pumba  | time="2025-02-21T16:39:22Z" level=info msg="stopping container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service signal=SIGTERM timout=5
pumba  | time="2025-02-21T16:39:27Z" level=info msg="starting container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service
pumba  | time="2025-02-21T16:39:37Z" level=info msg="stopping container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service signal=SIGTERM timout=5
pumba  | time="2025-02-21T16:39:42Z" level=info msg="starting container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service
pumba  | time="2025-02-21T16:39:52Z" level=info msg="stopping container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service signal=SIGTERM timout=5
pumba  | time="2025-02-21T16:39:57Z" level=info msg="starting container" dryrun=false id=5499b7067bd5f66e911970435dea064cd5e1f4fbd58c3f473f18c3fc20c9a8d2 name=/checkout-service

也能看見下圖,開始出現大量 500的請求,但其實也不是每筆都失敗的,甚至因為失敗的請求導致回應也提高,產生長尾效應。

總結

對於混沌工程的測試,我沒提到太多,畢竟我經驗也不多。但是有 Pumba、chaos monkey 或是 xk6-disruptor,都蠻方便針對容器化環境進行實驗。只是大部分都需要 K8S 環境,但 K8s 我沒那麼熟稔,剛好 Pumba滿足我的需求又好上手。

但我還是多說幾句,給團隊一些時間來實驗,發現未知的暗債問題,並主動發起討論和應對。會比起趕鴨子上架,硬性要求不能出錯,長遠來看對團隊能力以及文化發展更佳的好。

可觀測性驅動工程搭配混沌工程實驗,可以讓團隊對於複雜系統的未知問題,很方便的在迭代中進行探索。

References

Pumba

Pumba Image

CHAOS MONKEY ALTERNATIVES

Chaos Testing for Docker Containers - Alexei Ledenev (Codefresh)

Microservice resilience testing with Docker & Pumba

More from this blog

Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

稟告主公:此乃司馬懿進呈之兵書,詳解如何以 OpenTelemetry 陣法,令臥龍神算之一舉一動盡在掌握,知糧草消耗、察兵器效能、辨戰報異常,使主公運籌帷幄於大帳之中。 為何需要斥候情報? 司馬懿稟告主公: 臥龍神算(Claude Code)乃當世利器,然若無斥候回報,主公便如蒙眼行軍——兵器耗損幾何、糧草消費幾許、哪路斥候出了差錯,一概不知。臣以為,此乃兵家大忌。 無情報之弊,有四: 軍

Feb 19, 202610 min read164
Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發 本文整合 Anthropic 官方 Best Practices 與社群實戰 Tips,帶你由淺入深掌握 Claude Code。 什麼是 Claude Code?為什麼值得學? 如果你還在用「複製程式碼貼到 ChatGPT,再複製答案貼回去」的工作流程,Claude Code 會讓你大開眼界。 Claude Code 是 Anthropic 推出的命令列工具,它直接活在你的 terminal 裡,能夠讀懂你的整個 codeb...

Feb 18, 20265 min read75
工程師的 Claude Code 實戰指南:從零開始到高效開發

System Design Interview Ch 12 Digital Wallet

確立問題與設計範疇 角色對話內容 面試者我們應該只關注兩個數位錢包之間的餘額轉帳操作嗎?我們是否需要擔心其他功能? 面試官讓我們只關注餘額轉帳操作。 面試者該系統需要支援多少 TPS(每秒交易次數)? 面試官讓我們假設是 1,000,000 TPS (每秒 100 萬次交易)。 面試者數位錢包對正確性有嚴格的要求。我們可以假設事務保證 就足夠了嗎? 面試官聽起來不錯。 面試者我們需要證明正確性嗎? 面試官這是一個很好的問題。正確性(Correctness)通常只有在交...

Feb 2, 202610 min read190
System Design Interview Ch 12 Digital Wallet

Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦

在現代 AI 輔助開發中,我們不僅需要 AI 寫程式,更需要它懂規則、記性好,並且能自動處理那些繁瑣的雜事。透過 Claude Code Hooks 機制,我們可以介入 AI 的思考與執行迴圈,實現真正的「人機協作自動化」。 一、 動機與痛點:為什麼你需要介入 AI 的生命週期? 在預設狀態下,Claude Code 雖然強大,但它是「被動」且「無狀態」的,這導致了開發者常遇到以下痛點: 記憶重置 (Session Amnesia): 痛點:每次重啟終端機,AI 就像失憶一樣。 解法:你...

Jan 24, 20266 min read461
Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦
M

MicroFIRE

71 posts