Kubernetes basique : Pod, Deployment et Service

1. Vue d’ensemble

Kubernetes dispose de dizaines de types de ressources, mais 80 % du travail quotidien tourne autour de trois concepts : Pod, Deployment, Service. Cet article vous aide à comprendre la nature de chaque ressource, comment elles s’articulent ensemble, et à rédiger un ensemble de manifestes YAML pour déployer une application qui fonctionne réellement sur un cluster.

Après la lecture, vous pourrez déployer vous-même une application Spring Boot sur Kubernetes, la redimensionner avec plusieurs répliques, et l’exposer à l’extérieur via un Service - tout cela avec juste kubectl et quelques fichiers YAML.

Remarque : Cet article suppose que vous disposez déjà d’un cluster (Minikube, Kind ou cloud) et que kubectl pointe vers le bon contexte.

2. Pod - La plus petite unité de déploiement

2.1. Qu’est-ce qu’un Pod

Un Pod est un groupe d’un ou plusieurs conteneurs partageant un network namespace (même IP, même espace de ports) et des volumes. En pratique, la plupart des Pods ne contiennent qu’un seul conteneur d’application.

apiVersion: v1
kind: Pod
metadata:
  name: order-service-pod
  labels:
    app: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      ports:
        - containerPort: 8080
      resources:
        requests:
          cpu: "250m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"

Explication :

  • metadata.labels ajoute l’étiquette app: order-service - c’est ainsi que Kubernetes « identifie » les Pods ; les Services et Deployments utilisent cette étiquette pour sélectionner les objets liés.
  • resources.requests est la ressource minimale que le scheduler doit avoir pour placer le Pod sur un nœud ; limits est le plafond, au-delà duquel le Pod sera limité (CPU) ou tué (mémoire - OOMKilled).
  • containerPort: 8080 est simplement une déclaration informative, cela n’ouvre pas le port vers l’extérieur.

Appliquez et vérifiez :

kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
order-service-pod    1/1     Running   0          5s

2.2. Pourquoi ne pas créer les Pods directement

Un Pod créé « nu » comme ci-dessus a un gros problème : si le nœud contenant le Pod meurt ou si le Pod s’arrête, personne ne le recréera automatiquement. Un Pod ne dispose pas de mécanisme d’auto-réparation ni de mise à l’échelle.

Remarque : En environnement de production, on ne crée presque jamais directement des Pods. Utilisez toujours un Deployment (ou StatefulSet, Job) pour gérer les Pods.

3. Deployment - Gérer le cycle de vie des Pods

3.1. Déclarer un Deployment

Un Deployment crée et gère un ensemble de Pods via une ReplicaSet, garantissant qu’il y a toujours le bon nombre de répliques en cours d’exécution, et supportant les mises à jour progressives (rolling updates) sans temps d’arrêt.

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
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10

Explication :

  • replicas: 3 demande au Deployment de maintenir constamment 3 Pods en cours d’exécution - si un Pod meurt, le Deployment crée automatiquement un nouveau Pod pour le remplacer.
  • selector.matchLabels doit correspondre à template.metadata.labels. C’est le lien obligatoire entre le Deployment et les Pods qu’il gère.
  • readinessProbe détermine si le Pod reçoit le trafic ou non (utilisé par le Service) ; livenessProbe détermine si le Pod a besoin de redémarrer ou non. Confondre ces deux probes est une erreur très courante pour les débutants en Kubernetes.

Appliquez et observez :

kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
order-service   3/3     3            3           12s

NAME                             READY   STATUS    RESTARTS   AGE
order-service-7d4f9c8b6d-2kx9p   1/1     Running   0          12s
order-service-7d4f9c8b6d-7h2mn   1/1     Running   0          12s
order-service-7d4f9c8b6d-xq1lw   1/1     Running   0          12s

3.2. Mise à l’échelle et mise à jour progressive

Augmentez ou diminuez les répliques sans modifier le YAML :

kubectl scale deployment order-service --replicas=5

Une mise à jour progressive (rolling update) lorsqu’on change l’image - le Deployment remplace automatiquement les anciens Pods par de nouveaux Pods selon une stratégie progressive, sans temps d’arrêt :

kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out

Si la nouvelle version a un bug, revenez immédiatement à la version précédente :

kubectl rollout undo deployment/order-service

3.3. Configurer la stratégie de mise à jour progressive

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

maxUnavailable: 1 permet à un maximum de 1 Pod de « disparaître » pendant la mise à jour ; maxSurge: 1 permet de créer un maximum de 1 Pod supplémentaire au-delà du nombre de répliques déclaré. Avec replicas: 3, le cluster aura tantôt 2-4 Pods pendant le processus de transition, mais jamais moins de 2.

4. Service - Exposer les Pods de manière stable

4.1. Pourquoi un Service est nécessaire

Les Pods ont leur propre IP, mais cette IP change chaque fois qu’un Pod est recréé (crash, mise à jour progressive, mise à l’échelle). Un Service fournit une adresse stable (ClusterIP + nom DNS) devant un groupe de Pods, équilibrant automatiquement le trafic en fonction du sélecteur d’étiquettes - peu importe combien de fois les Pods en dessous changent.

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

Explication :

  • selector.app: order-service correspond à l’étiquette sur les Pods (attachée par le Deployment via template.metadata.labels) - le Service trouve et suit automatiquement ces Pods via un objet intermédiaire appelé Endpoints.
  • port: 80 est le port sur lequel le Service écoute à l’intérieur du cluster ; targetPort: 8080 est le port que le conteneur expose réellement. Ces deux nombres ne doivent pas nécessairement être identiques.
  • type: ClusterIP (par défaut) n’est accessible que depuis l’intérieur du cluster - convient aux services internes entre microservices.

Vérifiez le Service et les Endpoints :

kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
order-service   ClusterIP   10.96.142.201   <none>        80/TCP    4s

NAME            ENDPOINTS                                   AGE
order-service   10.244.1.5:8080,10.244.1.6:8080,+1 more     4s

4.2. Les différents types de Service

Type Portée d’accès Quand l’utiliser
ClusterIP Uniquement au sein du cluster Communication entre microservices
NodePort Port fixe (30000-32767) ouvert sur tous les nœuds Tests rapides, environnement dev/on-prem
LoadBalancer IP publique fournie par le fournisseur cloud Exposer le service sur Internet en cloud

Exemple d’exposition externe pour test rapide sur Minikube :

apiVersion: v1
kind: Service
metadata:
  name: order-service-external
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
  type: NodePort
minikube service order-service-external --url

Remarque : Utiliser NodePort en production est une anti-pattern - plage de ports limitée, pas de terminaison TLS ni de routage basé sur la santé comme avec Ingress. Pour la production en cloud, préférez LoadBalancer ou un Ingress Controller.

5. Combiner les trois dans une application réelle

L’ensemble du pipeline : le client appelle le Service → le Service équilibre la charge vers les Pods gérés par le Deployment.

Client
  │
  ▼
Service (order-service, ClusterIP 10.96.142.201:80)
  │  (label selector: app=order-service)
  ▼
┌─────────────┬─────────────┬─────────────┐
│  Pod #1     │  Pod #2     │  Pod #3     │
│  :8080      │  :8080      │  :8080      │
└─────────────┴─────────────┴─────────────┘
       ▲ géré par
  Deployment (order-service, replicas: 3)

Pour faciliter le déploiement en une seule fois, vous pouvez combiner le Deployment et le Service dans un seul fichier, séparés par --- :

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
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
kubectl apply -f order-service-all.yaml

6. Écrire des tests pour l’endpoint de readiness avant le déploiement

Avant que les probes de Kubernetes ne s’appuient sur /actuator/health/readiness, vous devez vous assurer que cet endpoint retourne le bon état dans votre code Spring Boot.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void readinessProbe_shouldReturnUp() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/actuator/health/readiness", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("\"status\":\"UP\"");
    }
}

Ce test appelle TestRestTemplate sur l’endpoint readiness réel, confirmant HTTP 200 et que le corps contient UP. C’est précisément l’endpoint que le readinessProbe dans le Deployment appellera périodiquement - si le test échoue ici, le Pod ne sera jamais « Ready » sur le cluster.

7. Erreurs courantes

  • Le sélecteur ne correspond pas à l’étiquette : le Service ou le Deployment ne trouve aucun Pod (Endpoints vides) parce que selector et labels sont mal écrits ou décalés. Utilisez toujours kubectl get endpoints <svc-name> pour vérifier.
  • Absence de readinessProbe : le Service achemine toujours le trafic vers les Pods qui n’ont pas fini de démarrer l’application, causant des erreurs 502/503 juste après le déploiement.
  • Resource requests/limits manquantes : les Pods peuvent être placés sur le mauvais nœud par le scheduler, ou être tués par OOMKilled de manière inattendue quand le trafic augmente.
  • Utiliser un Pod nu en production : pas d’auto-réparation, perdre un Pod c’est le perdre définitivement, il faut le recréer manuellement.
  • Tag d’image latest : la mise à jour progressive ne détecte pas le changement, Kubernetes pense que l’image ne change pas et ne la tire pas à nouveau. Utilisez toujours une étiquette de version spécifique.

8. Conclusion

Un Pod est la plus petite unité pour exécuter un conteneur, un Deployment gère le cycle de vie et la mise à l’échelle d’un ensemble de Pods, et un Service fournit une adresse stable pour y accéder même si les Pods en dessous changent continuellement. Maîtriser ces trois piliers est la base pour progresser vers Ingress, ConfigMap/Secret, ou les charts Helm par la suite. L’étape suivante devrait être : déployer une application Spring Boot réelle, observer la mise à jour progressive, et essayer de tuer manuellement un Pod pour voir comment le Deployment se remet automatiquement de l’erreur.

kubernetesdevopscontainerorchestration