Liveness, Readiness et Startup Probe dans Kubernetes

1. Aperçu

Quand vous exécutez une application sur Kubernetes, le cluster ne sait pas automatiquement si votre pod « vit » ou « est prêt à recevoir du trafic » — il doit en faire la demande via une probe. Cet article explique les différences entre liveness, readiness et startup probe, avec des exemples de configuration de Spring Boot Actuator et de Deployment YAML. À la fin de la lecture, vous saurez configurer correctement les probes pour un vrai service comme OrderService sans créer une boucle de redémarrage inutile.

2. Pourquoi avez-vous besoin de probes

Par défaut, Kubernetes sait seulement si le processus dans le container est en cours d’exécution (container status Running) ou non. Il ne sait pas si l’application intérieure est en deadlock, a perdu la connexion à la base de données, ou est encore en cours de démarrage.

Les conséquences sans probes :

  • Un pod figé (deadlock) reçoit toujours du trafic → timeouts en masse.
  • Un pod vient de démarrer, n’a pas encore connecté la base de données, et est déjà ajouté à la liste d’endpoints du Service → erreurs 500.

Trois types de probes résolvent trois problèmes différents :

Probe Répond à la question En cas d’échec
Liveness Le container est-il encore vivant ? Kubernetes redémarre le container
Readiness Le container est-il prêt à recevoir du trafic ? Kubernetes retire le pod de l’endpoint du Service (sans redémarrage)
Startup Le container a-t-il fini de démarrer ? Kubernetes attend, désactive temporairement les probes liveness/readiness

3. Liveness probe

La liveness probe répond : « ce processus est-il dans un état irrécupérable ? ». Si la probe échoue plus de failureThreshold fois, kubelet tue et redémarre le container.

3.1. Ajouter 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

La ligne management.endpoint.health.probes.enabled=true active deux endpoints : /actuator/health/liveness et /actuator/health/readiness, conformes aux attentes de Kubernetes.

3.2. Déclarer dans le 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 : attend 20s après le démarrage du container avant de commencer les probes.
  • periodSeconds: 10 : appelle la probe toutes les 10s.
  • failureThreshold: 3 : considère le pod comme mort après 3 échecs consécutifs → redémarrage.

Remarque : La probe liveness ne doit vérifier que « l’application s’exécute-t-elle encore ? », et ne doit pas vérifier les dépendances externes (base de données, Redis…). Si la base de données tombe en panne et que la liveness échoue en conséquence, Kubernetes redémarrera le pod continuellement sans résoudre quoi que ce soit — c’est une erreur très courante.

4. Readiness probe

La readiness probe répond : « le pod est-il prêt à traiter les requêtes ? ». En cas d’échec, le pod est retiré de l’endpoint du Service — le trafic n’est plus envoyé, mais le container n’est pas redémarré.

4.1. Indicateur de readiness personnalisé

Supposons que OrderService doit se connecter à PaymentGateway avant d’être prêt :

@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();
    }
}

Pour inclure cet indicateur dans le groupe readiness (sans affecter liveness), déclarez le groupe :

management.endpoint.health.group.readiness.include=readinessState,paymentGateway
  • health() retourne Health.down() quand PaymentGatewayClient n’est pas connecté — la readiness probe échoue, le pod ne reçoit pas de trafic jusqu’à ce que la gateway soit prête.
  • Spring nomme automatiquement le bean paymentGatewayReadinessIndicator comme clé paymentGateway dans la réponse health.

4.2. Test pour l’indicateur

<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) simule la dépendance externe, sans avoir besoin d’une connexion réelle.
  • assertThat(health.getStatus()) confirme que le statut est DOWN quand la gateway n’est pas connectée — c’est le comportement attendu de la readiness.

4.3. Déclarer readiness dans le Deployment

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3
  successThreshold: 1
  • successThreshold: 1 : un seul succès de probe suffit pour considérer le pod comme prêt (différent de liveness, généralement pas besoin de définir ce champ car la valeur par défaut est déjà 1).
  • periodSeconds: 5 plus court que liveness parce que readiness doit réagir rapidement aux changements d’état des dépendances.

5. Startup probe

Pour une application à démarrage lent (chargement de gros caches, exécution de migrations de base de données), si vous utilisez uniquement liveness avec un initialDelaySeconds fixe, vous devez deviner un nombre suffisamment grand — c’est facile à se tromper et cela gaspille du temps d’attente à chaque redémarrage.

La startup probe résout ce problème : tant que la startup probe n’a pas réussi, Kubernetes désactive temporairement les probes liveness et readiness, évitant un arrêt injustifié quand l’app démarre légitimement.

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 avec failureThreshold: 30 et periodSeconds: 10 → permet à l’app de démarrer jusqu’à 300s (30 × 10s) avant d’être considérée comme échouée.
  • Après le premier succès de startupProbe, Kubernetes bascule vers la surveillance de livenessProbe avec un seuil plus petit (3 fois × 10s = 30s) — réagit rapidement aux vrais problèmes en fonctionnement normal.

Remarque : Si votre application démarre rapidement (quelques secondes, comme une API REST simple sans chargement de gros cache), vous n’avez pas besoin de déclarer startupProbe — l’ajouter ne ferait que allonger le YAML sans bénéfice évident.

6. Combiner les trois dans un 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

Le flux de fonctionnement quand un nouveau pod est créé :

  1. Le container démarre, Kubernetes commence immédiatement à appeler startupProbe (pas d’initialDelaySeconds car la startup gère elle-même l’attente).
  2. Pendant ce temps, livenessProbe et readinessProbe ne s’exécutent pas encore.
  3. Quand startupProbe retourne HTTP 200 pour la première fois, Kubernetes arrête la startup probe et commence à exécuter liveness et readiness en parallèle.
  4. Readiness échoue → le pod est retiré du Service, le trafic n’est plus envoyé, mais le container reste actif.
  5. Liveness échoue → le container est tué, Kubernetes crée un nouveau container (même pod), le processus recommence à l’étape 1.

Vérifiez l’état réel des probes avec 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

La ligne Readiness probe failed indique que readiness échoue — vérifiez les logs de l’application ou appelez directement /actuator/health/readiness pour voir quel détail est DOWN.

7. Erreurs courantes

  • Utiliser le même endpoint pour liveness et readiness : quand une dépendance externe (DB, cache) tombe en panne, les deux probes échouent ensemble → le pod redémarre continuellement (crash loop) même si l’application principale fonctionne bien. Séparez clairement /actuator/health/liveness (vérifie uniquement l’interne) et /actuator/health/readiness (vérifie aussi les dépendances).
  • initialDelaySeconds trop petit pour une app à démarrage lent : la liveness probe échoue pendant que l’app charge, créant une boucle de redémarrage infinie. Solution : utilisez startupProbe au lieu d’augmenter arbitrairement initialDelaySeconds.
  • periodSeconds × failureThreshold trop grand : un pod défaillant réel met plusieurs minutes à être détecté, affectant le SLA.
  • La readiness probe appelle un service downstream sans importance : par exemple, un service de journalisation, si ce service tombe en panne, tout OrderService est considéré comme not-ready à tort, même si la fonctionnalité principale fonctionne bien.

8. Conclusion

Les trois types de probes servent trois objectifs distincts : startupProbe protège l’application pendant le démarrage lent, livenessProbe détecte les deadlocks pour que Kubernetes redémarre, et readinessProbe gère le trafic en fonction de l’état réel des dépendances. Le principe le plus important : ne laissez pas la liveness probe dépendre de systèmes externes — cela transformerait une panne en aval en crash loop de votre propre service. Configurer correctement ces trois probes aide le cluster à se rétablir en douceur, réduisant considérablement les temps d’arrêt lors du déploiement ou lors de problèmes d’infrastructure.

kubernetesspring-bootdevopshealth-checkmicroservices