Service Kubernetes : pourquoi l’IP d’un Pod ne suffit pas

Les Pods changent d’adresse à chaque recréation. Le Service met un nom stable devant eux et répartit le trafic. Quand il ne répond rien, la cause est presque toujours la même.

5 min de lecturekubernetesconteneursdevops

Un Pod a une adresse IP. La tentation est donc de l’utiliser. C’est une mauvaise idée, et Kubernetes est construit pour vous en empêcher.

Le problème : les Pods sont jetables

Ce n’est pas un défaut, c’est le principe de fonctionnement. Kubernetes détruit et recrée des Pods en permanence : lors d’une mise à jour, quand un nœud tombe, quand vous changez le nombre de réplicas, quand une sonde de vivacité échoue.

Pod 1 → 10.1.0.5
Pod 2 → 10.1.0.6        Pod 2 meurt et revient
Pod 3 → 10.1.0.7                 ↓
                         Pod 2 → 10.1.0.18

Une adresse notée quelque part est donc une adresse qui sera fausse. Et il n’y a rien à corriger : le comportement est voulu.

Le Service : un nom stable devant un groupe mouvant

        Client

       SERVICE            nom et IP fixes, pour toute sa vie

 ┌─────────┼─────────┐
 ↓         ↓         ↓
Pod 1    Pod 2     Pod 3

Le Service apporte trois choses d’un coup :

  • une adresse fixe, qui ne change pas quand les Pods changent ;
  • un nom DNS interne, utilisable par les autres applications du cluster ;
  • une répartition des requêtes entre les Pods disponibles.

Comment il sait quels Pods servir

Pas par leur nom : par leurs étiquettes. C’est le mécanisme central, et le seul à vraiment comprendre.

yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-web
spec:
  selector:
    app: demo-web # sert tous les Pods portant cette étiquette
  ports:
    - port: 80 # le port du Service
      targetPort: 5000 # le port du conteneur
  type: NodePort

Le Service ne connaît aucun Pod à l’avance. Il déclare une condition — les Pods étiquetés app: demo-web — et Kubernetes maintient la liste à jour en continu. Un Pod qui apparaît avec la bonne étiquette entre dans la rotation ; un Pod qui disparaît en sort.

Ce diagramme contient déjà les deux seules raisons pour lesquelles un Service ne répond rien, et nous y reviendrons plus bas.

C’est ce couplage souple qui rend le reste possible : passer de 3 à 10 réplicas ne demande aucune modification du Service.

Les trois ports, qu’on confond toujours

Navigateur

localhost:30080          nodePort    ← exposé sur chaque nœud

Service:80               port        ← l’entrée du Service

Conteneur:5000           targetPort  ← ce que votre application écoute

Le seul que votre application impose est targetPort : c’est le port sur lequel elle écoute réellement. Les deux autres sont des choix d’exposition.

Piège fréquent : targetPort doit correspondre au port réel de l’application, pas au containerPort déclaré dans le Deployment. Ce dernier est purement documentaire — Kubernetes ne le vérifie pas et ne s’en sert pas pour router.

Les trois types, et lequel choisir

TypeJoignable depuisUsage
ClusterIPl’intérieur du cluster seulementle défaut, et le bon choix pour tout service interne
NodePortl’extérieur, sur un port de chaque nœudapprentissage et cluster local
LoadBalancerl’extérieur, via un répartiteur du fournisseurproduction dans le nuage

NodePort ouvre un port dans la plage 30000-32767 sur tous les nœuds. Pratique en local, rarement souhaitable en production : on lui préfère un Ingress ou une passerelle devant un ClusterIP.

Le nom DNS, qui est le vrai intérêt

À l’intérieur du cluster, un Service est joignable par son nom :

http://demo-web              depuis le même namespace
http://demo-web.production   depuis un autre namespace

Concrètement, une application qui veut parler à la base de données écrit postgres:5432 dans sa configuration, et ne se soucie jamais de savoir où tourne le Pod. C’est ce qui remplace les fichiers d’adresses et les variables d’environnement recopiées à la main.

Quand le Service ne répond rien

C’est la panne la plus fréquente, et elle a presque toujours la même cause. Le diagnostic tient en une commande :

bash
kubectl get endpoints demo-web
NAME       ENDPOINTS
demo-web   <none>

<none> signifie que le Service ne trouve aucun Pod. Le trafic n’a nulle part à aller, et vous obtenez un délai d’attente ou une connexion refusée.

Deux causes, dans cet ordre :

  1. Le sélecteur ne correspond pas aux étiquettes des Pods. Une majuscule, un tiret, app: demo-web contre app: demoweb. Rien ne vous prévient : Kubernetes accepte un sélecteur qui ne correspond à rien, parce que les Pods pourraient arriver plus tard.

  2. Les Pods ne sont pas prêts. Un Pod dont la sonde de disponibilité échoue est délibérément retiré du Service. Il tourne, il apparaît dans kubectl get pods, et il ne reçoit aucun trafic. C’est le comportement correct, mais il surprend.

bash
kubectl get pods --show-labels        # les étiquettes réelles
kubectl describe service demo-web     # le sélecteur déclaré
kubectl get pods -o wide              # l’état de disponibilité

Comparer les deux premières sorties règle la grande majorité des cas.

Pourquoi la répartition n’a-t-elle pas l’air équitable ?

Parce que la répartition se fait par connexion, pas par requête. Un client qui garde sa connexion ouverte — ce que fait tout client HTTP moderne avec keep-alive — reste sur le même Pod pour toutes ses requêtes.

Sur un test au navigateur, cela donne l’impression que le Service envoie tout sur un seul Pod. Ce n’est pas un défaut du Service ; c’est la couche transport qui fait son travail. Pour observer la répartition, il faut de nouvelles connexions à chaque fois, ou un maillage de services qui répartit au niveau des requêtes.

Ce qu’il faut retenir

Le Service est une indirection, et c’est tout son intérêt : il découple qui appelle de ce qui répond. Les Pods peuvent naître, mourir, se multiplier et changer d’adresse sans que personne en amont ne le sache.

Et quand il ne marche pas, commencez par kubectl get endpoints. La réponse est là dans neuf cas sur dix.

Pour la vue d’ensemble, voyez Docker et Kubernetes : qui fait quoi. Pour ce qui fabrique les Pods que ce Service dessert, le Deployment.

Ce sujet fait partie d’un cours complet

Développement et déploiement de solutions de données — les premiers modules sont en accès libre.

Voir le plan du cours

Continuer sur le même sujet