Liveness, Readiness và Startup Probe trong Kubernetes

1. Tổng quan

Khi chạy ứng dụng trên Kubernetes, cluster không tự biết pod của bạn đang “sống” hay “sẵn sàng nhận traffic” — nó phải hỏi qua probe. Bài này giải thích sự khác biệt giữa liveness, readiness và startup probe, kèm ví dụ cấu hình Spring Boot Actuator và Deployment YAML. Đọc xong, bạn sẽ cấu hình được probe đúng cho một service thật như OrderService mà không làm pod bị restart loop oan.

2. Vì sao cần probe

Mặc định, Kubernetes chỉ biết process trong container còn chạy (container status Running) hay không. Nó không biết ứng dụng bên trong có bị deadlock, mất kết nối database, hay đang khởi động chưa xong.

Hệ quả nếu không có probe:

  • Pod bị treo (deadlock) nhưng vẫn nhận traffic → request timeout hàng loạt.
  • Pod vừa start, chưa kết nối xong DB, đã bị Service đưa vào danh sách endpoint → lỗi 500.

Ba loại probe giải quyết ba vấn đề khác nhau:

Probe Trả lời câu hỏi Khi fail
Liveness Container còn sống không? Kubernetes restart container
Readiness Container sẵn sàng nhận traffic chưa? Kubernetes gỡ pod khỏi Service endpoint (không restart)
Startup Container đã khởi động xong chưa? Kubernetes chờ, tạm vô hiệu liveness/readiness

3. Liveness probe

Liveness probe trả lời: “process này có đang ở trạng thái không thể tự phục hồi không?”. Nếu probe fail quá failureThreshold lần, kubelet kill và restart container.

3.1. Thêm Spring Boot Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info

Dòng management.endpoint.health.probes.enabled=true bật hai endpoint con: /actuator/health/liveness và /actuator/health/readiness, đúng chuẩn Kubernetes mong đợi.

3.2. Khai báo trong Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
            failureThreshold: 3
  • initialDelaySeconds: 20: chờ 20s sau khi container start mới bắt đầu probe.
  • periodSeconds: 10: mỗi 10s gọi probe một lần.
  • failureThreshold: 3: fail 3 lần liên tiếp mới coi là chết → restart.

Lưu ý: Liveness probe chỉ nên kiểm tra “ứng dụng còn chạy được không”, không nên kiểm tra dependency ngoài (DB, Redis…). Nếu DB chết mà liveness fail theo, Kubernetes sẽ restart pod liên tục mà không giải quyết được gì — đây là lỗi rất thường gặp.

4. Readiness probe

Readiness probe trả lời: “pod đã sẵn sàng xử lý request chưa?”. Khi fail, pod bị gỡ khỏi endpoint của Service — traffic không bị gửi tới nữa, nhưng container không bị restart.

4.1. Custom readiness indicator

Giả sử OrderService cần kết nối tới PaymentGateway trước khi sẵn sàng:

@Component
public class PaymentGatewayReadinessIndicator implements HealthIndicator {

    private final PaymentGatewayClient paymentGatewayClient;

    public PaymentGatewayReadinessIndicator(PaymentGatewayClient paymentGatewayClient) {
        this.paymentGatewayClient = paymentGatewayClient;
    }

    @Override
    public Health health() {
        if (paymentGatewayClient.isConnected()) {
            return Health.up().withDetail("gateway", "connected").build();
        }
        return Health.down().withDetail("gateway", "unreachable").build();
    }
}

Để indicator này gộp vào group readiness (chứ không ảnh hưởng liveness), khai báo group:

management.endpoint.health.group.readiness.include=readinessState,paymentGateway
  • health() trả Health.down() khi PaymentGatewayClient chưa kết nối được — readiness probe sẽ fail, pod không nhận traffic cho tới khi gateway sẵn sàng.
  • Spring tự đặt tên bean paymentGatewayReadinessIndicator thành khóa paymentGateway trong response health.

4.2. Test cho indicator

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <scope>test</scope>
</dependency>
class PaymentGatewayReadinessIndicatorTest {

    @Test
    void shouldReportDownWhenGatewayNotConnected() {
        PaymentGatewayClient client = mock(PaymentGatewayClient.class);
        when(client.isConnected()).thenReturn(false);

        PaymentGatewayReadinessIndicator indicator = new PaymentGatewayReadinessIndicator(client);
        Health health = indicator.health();

        assertThat(health.getStatus()).isEqualTo(Status.DOWN);
        assertThat(health.getDetails()).containsEntry("gateway", "unreachable");
    }
}
  • mock(PaymentGatewayClient.class) giả lập dependency ngoài, không cần kết nối thật.
  • assertThat(health.getStatus()) xác nhận status là DOWN khi gateway chưa connect — đúng hành vi mong đợi của readiness.

4.3. Khai báo readiness trong Deployment

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3
  successThreshold: 1
  • successThreshold: 1: chỉ cần 1 lần probe thành công là coi như ready lại (khác với liveness, thường không cần set field này vì default đã là 1).
  • periodSeconds: 5 ngắn hơn liveness vì readiness cần phản ứng nhanh với thay đổi trạng thái dependency.

5. Startup probe

Với ứng dụng khởi động chậm (load cache lớn, chạy migration DB), nếu chỉ dùng liveness với initialDelaySeconds cố định, bạn phải đoán một con số đủ lớn — dễ sai và lãng phí thời gian chờ cho mọi lần restart sau.

Startup probe giải quyết việc này: trong lúc startup probe chưa pass, Kubernetes tạm vô hiệu liveness và readiness probe, tránh bị kill oan khi app đang khởi động hợp lệ.

startupProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10
  failureThreshold: 3
  • startupProbe dùng failureThreshold: 30 và periodSeconds: 10 → cho phép app khởi động tối đa 300s (30 × 10s) trước khi bị coi là fail.
  • Sau khi startupProbe pass lần đầu, Kubernetes chuyển sang theo dõi livenessProbe với threshold nhỏ hơn (3 lần × 10s = 30s) — phản ứng nhanh với sự cố thật trong lúc chạy ổn định.

Lưu ý: Nếu ứng dụng của bạn khởi động nhanh (dưới vài giây, như REST API đơn giản không load cache lớn), không cần khai báo startupProbe — thêm vào chỉ làm YAML dài hơn mà không có lợi ích rõ ràng.

6. Kết hợp cả ba trong một Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            failureThreshold: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 5
            failureThreshold: 3

Luồng hoạt động khi pod mới được tạo:

  1. Container start, Kubernetes bắt đầu gọi startupProbe ngay (không có initialDelaySeconds vì startup tự xử lý việc chờ).
  2. Trong lúc này livenessProbe và readinessProbe chưa chạy.
  3. Khi startupProbe trả HTTP 200 lần đầu, Kubernetes dừng hẳn startup probe, bắt đầu chạy liveness và readiness song song.
  4. Readiness fail → pod bị gỡ khỏi Service, traffic không tới nữa, nhưng container vẫn sống.
  5. Liveness fail → container bị kill, Kubernetes tạo lại container mới (cùng pod), quy trình lặp lại từ bước 1.

Kiểm tra trạng thái probe thực tế bằng kubectl describe pod:

kubectl describe pod order-service-7d9f8c6b5-xk2pq
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True

Events:
  Type     Reason     Age   From     Message
  Warning  Unhealthy  2m    kubelet  Readiness probe failed: HTTP probe failed with statuscode: 503

Dòng Readiness probe failed cho biết readiness đang fail — kiểm tra log ứng dụng hoặc gọi trực tiếp /actuator/health/readiness để xem detail nào DOWN.

7. Các lỗi hay gặp

  • Dùng chung một endpoint cho liveness và readiness: khi dependency ngoài (DB, cache) chết, cả hai probe cùng fail → pod bị restart liên tục (crash loop) dù ứng dụng chính vẫn sống khỏe. Hãy tách rõ /actuator/health/liveness (chỉ kiểm tra nội bộ) và /actuator/health/readiness (kiểm tra cả dependency).
  • initialDelaySeconds quá nhỏ cho app khởi động chậm: liveness probe fail ngay trong lúc app đang load, gây restart loop vô tận. Giải pháp: dùng startupProbe thay vì tăng initialDelaySeconds tùy tiện.
  • Quên periodSeconds × failureThreshold quá lớn: pod lỗi thật nhưng mất nhiều phút mới bị phát hiện, ảnh hưởng SLA.
  • Readiness probe gọi tới một service downstream không quan trọng: ví dụ gọi tới service logging, nếu service đó sập thì toàn bộ OrderService bị coi là not-ready oan, dù chức năng chính vẫn hoạt động tốt.

8. Kết luận

Ba loại probe phục vụ ba mục đích tách bạch: startupProbe bảo vệ ứng dụng trong giai đoạn khởi động chậm, livenessProbe phát hiện deadlock để Kubernetes tự restart, và readinessProbe điều phối traffic dựa trên tình trạng dependency thực tế. Nguyên tắc quan trọng nhất: đừng để liveness probe phụ thuộc vào hệ thống ngoài — việc đó biến một sự cố downstream thành crash loop của chính service bạn. Cấu hình đúng ba probe này giúp cluster tự phục hồi mượt mà, giảm đáng kể thời gian downtime khi deploy hoặc khi có sự cố hạ tầng.

kubernetesspring-bootdevopshealth-checkmicroservices