Tình Huống
2 giờ sáng. PagerDuty kêu. Bạn kiểm tra cluster và thấy các pod đang khởi động lại liên tục mỗi 60–90 giây. kubectl get pods hiển thị số lần restart tăng nhanh. Bạn xem events và thấy ngay:
Warning Unhealthy pod/api-server-7d9f4b8c6-xk2pq Liveness probe failed: Get "http://10.0.0.1:8080/healthz": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Kubernetes nghĩ ứng dụng của bạn đã chết. Nó kill container, khởi động lại, chờ startup — rồi probe lại thất bại trước khi app sẵn sàng. Bạn đang mắc kẹt trong vòng lặp, và nó sẽ không tự thoát ra được.
Tại Sao Điều Này Xảy Ra
Liveness probe gửi một HTTP GET đến container của bạn. Không có phản hồi trong vòng timeoutSeconds (mặc định: 1 giây)? Thất bại. Sau failureThreshold lần thất bại liên tiếp (mặc định: 3), Kubernetes khởi động lại container.
Có bốn nguyên nhân gây ra điều này. Hầu hết đều có thể sửa trong chưa đến 10 phút.
- Timeout của probe quá ngắn — Đôi khi app của bạn mất hơn 1 giây để phản hồi
/healthz, đặc biệt khi tải cao hoặc trong các đợt JVM/GC pause. - App khởi động chậm — Probe kích hoạt trước khi app sẵn sàng nhận kết nối.
initialDelaySecondschưa được đặt, hoặc được đặt với giá trị lạc quan như 5 giây. - Thiếu tài nguyên — Container bị giới hạn CPU. Health endpoint không thể phản hồi kịp thời vì process đang chờ CPU.
- Health endpoint làm quá nhiều việc — Route
/healthzcủa bạn kiểm tra kết nối DB, chạy query, hoặc gọi các dịch vụ bên ngoài. Ổn khi server nhàn; nguy hiểm khi có bất kỳ tải thực tế nào.
Chẩn Đoán Trước
Chưa cần chạm vào cấu hình probe. Hãy xác nhận thứ gì đang thực sự thất bại.
Kiểm tra events và số lần restart
# Xem số lần restart
kubectl get pods -n your-namespace
# Xem các event thất bại của probe
kubectl describe pod api-server-7d9f4b8c6-xk2pq -n your-namespace | grep -A 20 Events
Kiểm tra cấu hình probe hiện tại
kubectl get deployment api-server -n your-namespace -o yaml | grep -A 20 livenessProbe
Kiểm tra health endpoint từ bên trong pod
# Exec vào pod trước khi nó restart
kubectl exec -it api-server-7d9f4b8c6-xk2pq -n your-namespace -- sh
# Gọi endpoint và đo thời gian
time wget -qO- http://localhost:8080/healthz
Bất kỳ kết quả nào trên 1 giây là vấn đề của bạn. Thất bại hoàn toàn có nghĩa là app của bạn có lỗi trong chính health handler.
Kiểm tra áp lực tài nguyên
kubectl top pod api-server-7d9f4b8c6-xk2pq -n your-namespace
kubectl top node
CPU gần đến giới hạn? Throttling rất có thể đang làm chậm mọi request — kể cả health check.
Cách Sửa 1: Điều Chỉnh Thời Gian Probe (Cách Sửa Phổ Biến Nhất)
Kubernetes đi kèm với các giá trị mặc định cho probe được thiết kế cho app đơn giản và nhanh. Timeout 1 giây và interval 10 giây quá ngặt cho hầu hết các dịch vụ production. Hãy điều chỉnh deployment của bạn:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # Chờ 30s sau khi container khởi động trước lần probe đầu tiên
periodSeconds: 15 # Kiểm tra mỗi 15s (không phải mỗi 10s)
timeoutSeconds: 5 # Cho phép 5s để phản hồi (không phải mặc định 1s)
failureThreshold: 3 # Vẫn restart sau 3 lần thất bại liên tiếp
successThreshold: 1
Sau đó apply:
kubectl apply -f deployment.yaml
Các app JVM thường cần initialDelaySeconds: 60 hoặc cao hơn — Spring Boot với full context load có thể mất 45 giây khi cold start. Với app chịu tải nặng, timeoutSeconds: 5 là điểm khởi đầu an toàn; tăng lên 10 nếu vẫn còn thất bại.
Cách Sửa 2: Thêm Startup Probe (Kubernetes 1.16+)
Khởi động chậm là vấn đề riêng. Đối phó bằng cách đặt initialDelaySeconds lớn chỉ là đoán mò — bạn đang hardcode một con số sẽ sai trong CI và sai trên node bị suy giảm hiệu năng. Hãy dùng startup probe thay thế. Nó giữ liveness probe lại cho đến khi app báo hiệu sẵn sàng:
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # Thử trong tối đa 5 phút (30 * 10s)
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
Startup probe chạy trước. Ngay khi nó thành công một lần, nó dừng lại và liveness tiếp quản. Không cần đoán mò nữa.
Cách Sửa 3: Sửa Chính Health Endpoint
/healthz chậm thường là thủ phạm khó phát hiện nhất. Hãy loại bỏ mọi logic không thuộc về nó. Liveness probe chỉ có một nhiệm vụ: xác nhận process còn sống. Kiểm tra kết nối database, ping cache, hoặc xác thực API bên ngoài thuộc về /readyz sau readiness probe — không phải ở đây.
Một liveness endpoint đúng trông như thế này:
# Go
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
# Node.js
app.get('/healthz', (req, res) => res.status(200).send('ok'));
# Python/FastAPI
@app.get("/healthz")
def healthz():
return {"status": "ok"}
Nó phải trả về 200 trong chưa đến 5 millisecond. Nếu endpoint của bạn không làm được vậy, hãy rút gọn nó cho đến khi đạt được.
Cách Sửa 4: Tăng Resource Limits
CPU throttling dễ bỏ sót vì app trông khỏe mạnh theo mọi cách khác. Khi container chạm đến giới hạn CPU, kernel sẽ throttle nó — và đột nhiên một health check 2ms mất đến 2 giây. Hãy kiểm tra xem điều này có đang xảy ra không:
kubectl exec -it api-server-7d9f4b8c6-xk2pq -- cat /sys/fs/cgroup/cpu/cpu.stat | grep throttled
Nếu throttled_time khác không và đang tăng, app của bạn đang bị thiếu CPU. Hãy tăng giới hạn:
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m" # Tăng giá trị này nếu bị throttle
memory: "512Mi"
Bắt đầu bằng cách nhân đôi CPU limit, redeploy, và kiểm tra lại throttled_time.
Cách Sửa 5: Chuyển Sang TCP hoặc Exec Probe
Đôi khi probe đơn giản nhất lại là lựa chọn đúng. TCP chỉ kiểm tra xem port có mở không — không có HTTP overhead, không lo code handler. Exec chạy một lệnh bên trong container:
# TCP probe — xác nhận port đang lắng nghe
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 5
# Exec probe — chạy lệnh bên trong container
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 10
Pattern dùng exec hoạt động tốt với các app ghi /tmp/healthy khi khởi động và xóa nó khi muốn báo hiệu có vấn đề. Thô sơ, nhưng đáng tin cậy.
Xác Nhận Bản Sửa
# Theo dõi pods ổn định lại
kubectl get pods -n your-namespace -w
# Xác nhận số lần restart dừng tăng
kubectl get pods -n your-namespace
# Cột RESTARTS phải giữ nguyên
# Kiểm tra events đã sạch
kubectl describe pod -n your-namespace | tail -20
# Không nên có cảnh báo Unhealthy
Chờ ít nhất 3–5 chu kỳ probe trước khi kết luận đã ổn định. Với periodSeconds: 15, đó là khoảng 75 giây output sạch trước khi bạn đóng laptop lại.
Phòng Ngừa
- Đừng bao giờ triển khai lên production với giá trị probe mặc định. Các giá trị mặc định (timeout 1s, period 10s) được thiết kế cho demo, không phải workload thực. Hãy điều chỉnh cho từng service.
- Giữ liveness và readiness riêng biệt. Liveness = process còn sống không. Readiness = có an toàn để nhận traffic không. Trộn lẫn hai loại này gây ra các lần restart dây chuyền khi một dependency bị sập.
- Load test health endpoint của bạn trước khi triển khai. Chạy
ab -n 1000 -c 50 http://localhost:8080/healthzvà kiểm tra p99 latency. Nếu trên 200ms, hãy sửa trước khi Kubernetes phát hiện ra. - Đặt
terminationGracePeriodSecondsđủ cao để các request đang xử lý có thể hoàn thành. 30 giây là điểm khởi đầu hợp lý cho hầu hết các API. - Review cấu hình probe trong code review như bất kỳ thiết lập production nào khác. Bỏ qua bước này chính là nguyên nhân dẫn đến những cuộc gọi lúc 2 giờ sáng.

