Probe là thứ hiếm hoi trong Kubernetes mà cấu hình sai còn tệ hơn không cấu hình. Bài này dựng ba kiểu sai phổ biến nhất và đo hậu quả.

Ba bẫy probe và số đo tương ứng

Bẫy 1: livenessProbe kiểm tra phụ thuộc bên ngoài

Đây là kiểu sai nguy hiểm nhất, và nó trông rất hợp lý khi viết.

livenessProbe:
  exec: {command: ["sh","-c","curl -sf -m 2 http://db-svc/"]}
  periodSeconds: 3
  failureThreshold: 2

"Ứng dụng không nói chuyện được với cơ sở dữ liệu thì nó có sống cũng vô ích." Nghe đúng.

Bốn pod ứng dụng, cơ sở dữ liệu còn sống: tổng RESTARTS = 0.

Giảm cơ sở dữ liệu về 0 bản:

ung-...-6tdhz   Running   RESTARTS=1
ung-...-dnrvb   Running   RESTARTS=1
ung-...-jm5jk   Running   RESTARTS=1
ung-...-zqxnv   Running   RESTARTS=1

sau 40 giây nữa:  tổng RESTARTS = 8

Toàn bộ fleet vào vòng lặp khởi động lại, và nó tăng đều: 4 rồi 8 rồi tiếp.

Vấn đề là khởi động lại không sửa được gì. Lỗi không nằm ở ứng dụng. Container mới khởi động lên, probe vẫn hỏng, lại bị giết.

Và nó làm mọi thứ tệ hơn: khi cơ sở dữ liệu quay lại, mọi pod đang ở giữa chừng khởi động, không pod nào sẵn sàng, và cả đám cùng kết nối lại một lúc — chính là kiểu tải mà cơ sở dữ liệu vừa phục hồi không chịu nổi.

Quy tắc: livenessProbe chỉ kiểm tra chính tiến trình. Một endpoint trả về 200 khi vòng lặp sự kiện còn quay là đủ. Kiểm tra phụ thuộc thuộc về readinessProbe — ở đó, mất kết nối cơ sở dữ liệu chỉ khiến pod ngừng nhận lưu lượng, và nó tự quay lại khi mọi thứ ổn.

Bẫy 2: timeoutSeconds mặc định là 1 giây

Probe mất 3 giây để trả lời:

RESTARTS
timeoutSeconds = 1 1
timeoutSeconds = 5 0
Unhealthy: Liveness probe failed: command timed out

Ứng dụng hoàn toàn khoẻ. Nó chỉ trả lời chậm hơn một giây — điều bình thường khi node đang bận, khi bộ dọn rác vừa chạy, hoặc khi ứng dụng đang xử lý một lô lớn.

timeoutSeconds mặc định là 1, và đó là con số gây tai nạn nhiều nhất trong sáu tham số probe. Rất ít người khai nó, vì nó không xuất hiện trong ví dụ mẫu.

Quy tắc: đặt timeoutSeconds bằng p99 của thời gian probe, nhân đôi. Với endpoint trả lời trong 50 ms thì 1 giây là rộng rãi; với endpoint kiểm tra vài thứ nội bộ thì không.

Và chú ý một chi tiết: probe exec tạo một tiến trình mới mỗi lần chạy. Trên node đang tải nặng, chỉ riêng việc khởi tạo tiến trình có thể vượt một giây.

Bẫy 3: readinessProbe quá nhạy

readinessProbe:
  periodSeconds: 1
  failureThreshold: 1

Một lần trượt là ra khỏi Endpoints ngay.

bình thường:                          4 endpoint
sau khi mọi pod cùng trượt probe:     0 endpoint

nhay-...-bmv4d   0/1   Running   RESTARTS=0
nhay-...-g2nr5   0/1   Running   RESTARTS=0
nhay-...-jtq9f   0/1   Running   RESTARTS=0
nhay-...-stsj4   0/1   Running   RESTARTS=0

Dịch vụ chết hoàn toàn, và không pod nào khởi động lại.

Đây là kiểu hỏng khó chịu nhất vì Kubernetes không thấy có gì hỏng để sửa. Mọi pod đều Running, không có CrashLoopBackOff, không có event lỗi. kubectl get pods trông gần như bình thường — chỉ cột READY0/1.

Trong hệ thống thật, nguyên nhân thường là một thứ ảnh hưởng tới mọi pod cùng lúc: cơ sở dữ liệu chậm đi, một dịch vụ chung tăng độ trễ, hoặc node bị nghẽn mạng. Với failureThreshold: 1, một nhịp chậm 200 mili giây là đủ.

Quy tắc: failureThreshold ít nhất là 3. Kết hợp với periodSeconds hợp lý, nó cho ứng dụng vài giây để vượt qua một trục trặc thoáng qua thay vì bị đá ra ngay.

Ba quy tắc, gộp lại

# liveness: rẻ, chỉ kiểm chính mình, khoan dung
livenessProbe:
  httpGet: {path: /healthz, port: 8080}
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3

# readiness: được phép kiểm phụ thuộc, nhưng đừng quá nhạy
readinessProbe:
  httpGet: {path: /ready, port: 8080}
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 3

# startup: cho ứng dụng đủ thời gian lên
startupProbe:
  httpGet: {path: /healthz, port: 8080}
  periodSeconds: 5
  failureThreshold: 30      # tối đa 150 giây

Hai endpoint khác nhau: /healthz chỉ trả lời "tiến trình còn quay", /ready kiểm cả phụ thuộc. Dùng chung một endpoint cho cả hai probe là cách nhanh nhất rơi vào bẫy 1.

Khi nào không nên có livenessProbe

Đây là quan điểm ít được nói: với nhiều ứng dụng, không có livenessProbe là lựa chọn đúng.

Nếu ứng dụng của bạn sập thì tiến trình thoát, và Kubernetes khởi động lại container — không cần probe nào. livenessProbe chỉ có ích cho tình huống còn sống nhưng kẹt, và nhiều ứng dụng đơn giản không có tình huống đó.

Một livenessProbe không cần thiết chỉ thêm một cách để mọi thứ hỏng. Ba bẫy ở trên đều là bẫy của liveness hoặc của việc nhầm nó với readiness.

readinessProbe thì gần như luôn nên có — phần 50 đo được nó là thứ tạo ra cập nhật không gián đoạn.

Thử ba mươi giây

Tìm probe nguy hiểm trong cụm của bạn:

kubectl get pods -A -o json | python3 -c '
import sys,json
d=json.load(sys.stdin)
for p in d["items"]:
    for c in p["spec"].get("containers",[]):
        lp=c.get("livenessProbe")
        if not lp: continue
        t=lp.get("timeoutSeconds",1); f=lp.get("failureThreshold",3)
        e=lp.get("exec",{}).get("command",[])
        canh=[]
        if t<=1: canh.append("timeout=1")
        if f<=1: canh.append("failureThreshold=1")
        if any("curl" in str(x) or "wget" in str(x) for x in e): canh.append("goi ra ngoai?")
        if canh: print(p["metadata"]["namespace"], p["metadata"]["name"], c["name"], ",".join(canh))
'

Mỗi dòng hiện ra là một pod có thể bị giết vì lý do không phải lỗi của nó.

Phần sau đo Service: cách một cái tên DNS biến thành lưu lượng tới đúng pod.