Aller au contenu
English
English
Conteneurs & Orchestration medium

Services Kubernetes : exposer et connecter vos applications

70 min de lecture

logo kubernetes

Un Service Kubernetes permet d'exposer un ou plusieurs Pods via une adresse IP stable et un nom DNS interne. Comme les Pods sont éphémères et changent d'adresse IP, le Service fournit un point d'accès durable pour la communication réseau entre applications ou vers l'extérieur du cluster. Ce guide vous montre comment créer des Services, choisir le bon type (ClusterIP, NodePort, LoadBalancer) et diagnostiquer les problèmes de connectivité.

Prérequis : un cluster Kubernetes fonctionnel avec au moins un Deployment ou des Pods.

  • Créer un Service avec kubectl expose et avec un fichier YAML
  • Choisir le bon type : ClusterIP, NodePort, LoadBalancer, Headless
  • Comprendre la résolution DNS entre Pods et Services
  • Utiliser les labels/selectors pour cibler les bons Pods
  • Diagnostiquer quand un Service ne fonctionne pas

Un Service est une abstraction réseau qui expose un groupe de Pods via une adresse IP stable et un nom DNS.

Pour un débutant, retenez simplement :

  • Les Pods changent d'IP à chaque recréation
  • Le Service garde une IP fixe qui redirige vers les Pods
  • Les autres Pods appellent le Service par son nom, pas par IP

Sans Services, chaque Pod devrait connaître l'adresse IP des autres Pods, une adresse qui change à chaque redémarrage.

Les Services résolvent ce problème en fournissant :

FonctionnalitéDescription
IP stableUne adresse fixe qui ne change pas quand les Pods sont recréés
Nom DNSUn nom comme backend-service au lieu d'une IP
Load balancingRépartition automatique du trafic entre plusieurs Pods
DécouverteLes Pods trouvent les autres services par leur nom

Une application web avec :

  • Un frontend (React)
  • Un backend (API Node.js)
  • Une base de données (PostgreSQL)

Le frontend appelle backend-service:8080 au lieu de chercher l'adresse du Pod backend. Si ce Pod redémarre avec une nouvelle adresse, le Service continue de rediriger, sans que rien ne change côté appelant.

Deux chemins mènent au même Service. La méthode impérative tient en une commande et convient pour exposer rapidement un Deployment existant. La méthode déclarative passe par un fichier YAML que vous versionnez, et c'est celle que vous emploierez en équipe. Commencez par la première pour voir un Service fonctionner, la seconde arrive juste après.

La façon la plus rapide de créer un Service pour un Deployment existant :

Fenêtre de terminal
kubectl expose deployment nginx --name=nginx-service --port=80 --target-port=80

Résultat : un Service ClusterIP nommé nginx-service qui expose le port 80.

Vérifiez sa création :

Fenêtre de terminal
kubectl get svc nginx-service
Sortie
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 10.96.243.54 <none> 80/TCP 5s

Pour un contrôle total, créez un fichier manifest :

nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

Les champs essentiels :

ChampDescription
selectorLabels des Pods à cibler (ici app: nginx)
portPort exposé par le Service
targetPortPort sur lequel écoutent les Pods
typeType de Service (ClusterIP par défaut)

Appliquez-le :

Fenêtre de terminal
kubectl apply -f nginx-service.yaml

Le champ selector est celui qui décide de tout : il doit correspondre aux libellés de vos Pods. Un Service dont le selector ne trouve personne est créé sans la moindre erreur, répond à son nom DNS, et ne route vers aucune destination.

Une fois votre Service créé, vérifiez qu'il fonctionne.

C'est la première commande à lancer après une création. Elle dit en une ligne quel type de Service vous avez obtenu, quelle adresse de cluster lui a été attribuée, et, avec -o wide, quel selector il utilise pour trouver ses Pods.

Fenêtre de terminal
kubectl get svc -o wide
Sortie
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-service ClusterIP 10.96.243.54 <none> 80/TCP 2m app=nginx

Les colonnes importantes :

ColonneSignification
TYPEClusterIP, NodePort, LoadBalancer...
CLUSTER-IPAdresse IP interne du Service
PORT(S)Ports exposés
SELECTORLabels utilisés pour trouver les Pods
Fenêtre de terminal
kubectl describe svc nginx-service

Cette commande montre :

  • Endpoints : IPs des Pods ciblés par le Service
  • Session Affinity : sticky sessions ou non
  • Events : historique (rarement utile pour les Services)
Sortie (extrait)
Name: nginx-service
Namespace: default
Selector: app=nginx
Type: ClusterIP
IP: 10.96.243.54
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.32:80,10.244.1.33:80,10.244.1.34:80
Session Affinity: None

La ligne Endpoints est la seule qui dise si le Service sert à quelque chose. Vide, le Service existe, répond à son nom DNS, et ne route vers personne. Kubernetes ne signale aucune erreur : de son point de vue, c'est un état parfaitement valide.

Deux causes très différentes produisent la même ligne vide, et confondre les deux fait perdre beaucoup de temps :

CauseCe qui se passeComment la reconnaître
Selector sans correspondanceaucun Pod ne porte les libellés attenduskubectl get pods --show-labels ne montre aucun Pod avec ces libellés
Pods présents mais pas Readyles Pods sont bien sélectionnés, mais leur readiness probe échouekubectl get pods affiche 0/1 Running

La seconde est de loin la plus fréquente en production, et c'est précisément celle que describe ne vous dit pas.

La liste des Pods derrière un Service vit dans un objet EndpointSlice. C'est lui qu'il faut interroger :

Fenêtre de terminal
kubectl get endpointslice -l kubernetes.io/service-name=nginx-service
Sortie
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
nginx-service-mf9f8 IPv4 80 10.244.1.32,10.244.1.33,10.244.1.34 5m

Chaque adresse correspond à un Pod qui recevra du trafic. Le suffixe aléatoire du nom sera différent chez vous.

Un Pod qui n'est pas Ready ne reçoit aucun trafic

Section intitulée « Un Pod qui n'est pas Ready ne reçoit aucun trafic »

C'est la seconde cause, et le mécanisme que beaucoup découvrent en plein incident : en fonctionnement normal, un Service ne route que vers les Pods Ready. Un Pod peut être Running, son conteneur tourner parfaitement, et ne recevoir aucune requête parce que sa readiness probe échoue.

Fenêtre de terminal
kubectl get pods -l app=web
Sortie
NAME READY STATUS RESTARTS AGE
web-686dd5f4b7-d7z6q 0/1 Running 0 31s
web-686dd5f4b7-gwznn 0/1 Running 0 31s

Le 0/1 de la colonne READY est le signal : les Pods existent, ils tournent, et ils ne sont pas prêts. Le Service les écarte, sa ligne Endpoints est vide, et rien n'explique pourquoi. C'est en regardant les Pods, pas le Service, que le diagnostic se fait.

Dernière remarque de vocabulaire : vous verrez souvent kubectl get endpoints dans les tutoriels. La commande fonctionne encore, mais elle vous répond v1 Endpoints is deprecated in v1.33+. Prenez l'habitude d'endpointslice dès maintenant.

Kubernetes propose quatre types de Services selon vos besoins d'exposition.

Le Service est accessible uniquement depuis l'intérieur du cluster.

spec:
type: ClusterIP # ou omettez, c'est le défaut

Cas d'usage : communication entre microservices (backend ↔ base de données).

À l'intérieur du cluster, un Frontend Pod appelle backend-service, un Service de type ClusterIP, qui relaie le trafic vers les Backend Pods sans jamais sortir du cluster

ClusterIP est le type que vous emploierez le plus. C'est le défaut, et c'est le bon choix pour tout ce qui ne doit pas être joignable depuis l'extérieur du cluster : une API interne, une base de données, un cache.

Le Service est accessible via un port sur chaque nœud du cluster.

spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080 # optionnel, entre 30000-32767

Accès : http://<IP-du-noeud>:30080

Cas d'usage : tests locaux, environnements sans cloud provider.

Un appel venu d'Internet ou du réseau externe atteint le port 30080 ouvert sur chaque nœud du cluster, Node 1 comme Node 2 ; les deux nœuds renvoient le trafic vers le même Service ClusterIP, qui le distribue ensuite aux Pods

Le nodePort est alloué à l'échelle du cluster, pas nœud par nœud : deux Services ne peuvent pas demander le même numéro. La création du second échoue explicitement :

The Service "np2" is invalid: spec.ports[0].nodePort: Invalid value: 30099:
provided port is already allocated

Omettre nodePort laisse Kubernetes en choisir un libre dans la plage 30000-32767, et supprime entièrement la question.

Le Service demande un équilibreur de charge externe au cloud provider (AWS, GCP, Azure...).

spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80

Accès : via l'IP externe fournie par le cloud (EXTERNAL-IP).

Cas d'usage : applications en production sur le cloud.

Fenêtre de terminal
kubectl get svc
Sortie
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service LoadBalancer 10.96.243.54 203.0.113.50 80:32117/TCP 2m

La question se tranche sur une seule ligne : qui doit joindre ce Service ? Un appelant à l'intérieur du cluster n'a jamais besoin de plus qu'un ClusterIP. Dès que l'appel vient de l'extérieur, le choix dépend de l'endroit où tourne le cluster.

BesoinType recommandé
Communication interneClusterIP
Test local sans cloudNodePort
Production sur cloudLoadBalancer

Le type ExternalName est à part : il ne sélectionne pas de Pods et ne fait pas de proxy. Il redirige simplement vers un nom DNS externe.

spec:
type: ExternalName
externalName: database.example.com

Cas d'usage : pointer vers une base de données managée (RDS, Cloud SQL...). Les Pods appellent external-db et sont redirigés vers database.example.com.

Un Headless Service (clusterIP: None) ne possède pas d'IP de cluster. Le DNS retourne directement les IPs des Pods au lieu de l'IP du Service.

spec:
clusterIP: None
selector:
app: nginx
Service normalHeadless Service
DNS → IP du ServiceDNS → IPs des Pods
Load balancing par kube-proxyClient choisit le Pod

Cas d'usage : bases de données stateful, StatefulSets avec identité réseau stable. Voir le guide StatefulSets pour plus de détails.

Kubernetes crée automatiquement des enregistrements DNS pour chaque Service.

Chaque Service reçoit un nom complet et prévisible, construit à partir de son nom et de son namespace. C'est ce nom que CoreDNS résout, et c'est celui dont dérivent toutes les formes abrégées.

<service-name>.<namespace>.svc.cluster.local

En pratique vous écrirez rarement le nom complet. Depuis un Pod, le nom court suffit tant que le Service est dans le même namespace : le suffixe .<namespace> ne devient nécessaire que pour franchir une frontière de namespace, et le FQDN presque jamais.

Appel depuisNom à utiliser
Même namespacenginx-service
Autre namespacenginx-service.production
Nom complet (FQDN)nginx-service.production.svc.cluster.local

Un piège attend ici tous les débutants, et mieux vaut le connaître avant de le rencontrer. Lançons un Pod outil, puis interrogeons le nom court.

Fenêtre de terminal
kubectl run outil --image=busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 --restart=Never --command -- sleep 300
kubectl exec outil -- nslookup web
Sortie réelle
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: web.default.svc.cluster.local
Address: 10.96.195.43
** server can't find web.svc.cluster.local: NXDOMAIN
** server can't find web.cluster.local: NXDOMAIN

La résolution a réussi, la quatrième ligne le prouve. Mais nslookup essaie ensuite tous les suffixes de la liste de recherche, signale chaque échec, et sort en code 1. Ce mur de NXDOMAIN n'est pas une panne, c'est le fonctionnement normal de l'outil, et il fait croire à un cluster cassé.

Ce qui explique ce comportement vit dans le Pod :

Fenêtre de terminal
kubectl exec outil -- cat /etc/resolv.conf
Sortie
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5

10.96.0.10 est l'adresse du Service kube-dns, dans le namespace kube-system : c'est le résolveur interne du cluster. La ligne search énumère les suffixes essayés, dans l'ordre.

Une application, elle, ne voit rien de tout cela : la bibliothèque C s'arrête au premier suffixe qui répond. Depuis le même Pod :

Fenêtre de terminal
kubectl exec outil -- wget -qO- http://web

La page arrive. Testez donc la connectivité avec l'outil que votre application utilise, wget ou curl, plutôt qu'avec nslookup, qui rapporte des échecs sans conséquence.

Utilisez toujours le nom du Service, jamais son adresse de cluster. Le nom reste valide quand l'IP change, et c'est précisément ce que le Service apporte.

Par défaut, kube-proxy répartit le trafic aléatoirement. Pour garder un client sur le même Pod, ajoutez sessionAffinity: ClientIP :

spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 600

Cas d'usage : websockets, sessions en mémoire. Attention : ne fonctionne pas bien derrière un proxy qui masque l'IP client.

Une question fréquente : quand utiliser un Service, quand utiliser un Ingress ?

ServiceIngress
CoucheL4 (TCP/UDP)L7 (HTTP/HTTPS)
RoutagePar IP/portPar path, host, headers
TLSNonOui (terminaison)
UsageCommunication interne ou exposition simpleAPI publique, sites web

Règle simple : utilisez un Service pour la communication interne ou l'exposition basique, un Ingress pour du routage HTTP avancé.

Un Service Kubernetes est un outil Layer 4 (TCP/UDP). Il a des limitations :

BesoinSolution
Routage HTTP (path, host)Ingress
Terminaison TLSIngress + cert-manager
Filtrage de sécuritéNetworkPolicies
Rate limiting, circuit breakerService mesh

Un point mérite d'être dit sans détour : un Service n'offre aucune sécurité. Tout Pod du cluster peut appeler n'importe quel Service, dans n'importe quel namespace. Restreindre ces flux relève des NetworkPolicies, et de rien d'autre.

Quand un Service ne répond pas, quatre vérifications suffisent presque toujours, et elles se font dans cet ordre. Les trois premières partent du Service et descendent vers les Pods ; la quatrième change de point de vue et teste depuis l'intérieur du cluster.

Symptôme : Endpoints: <none> dans kubectl describe svc.

Fenêtre de terminal
# Vérifier les labels des Pods
kubectl get pods --show-labels
# Comparer avec le selector du Service
kubectl get svc nginx-service -o jsonpath='{.spec.selector}'

Deux causes possibles, à écarter dans cet ordre : le selector ne correspond à aucun libellé, ou bien les Pods sont sélectionnés mais pas Ready. L'EndpointSlice tranche en une commande, là où describe reste muet.

Symptôme : curl nginx-service timeout ou connexion refusée.

Fenêtre de terminal
kubectl describe svc nginx-service | grep TargetPort
kubectl exec -it nginx-pod -- netstat -tlnp

Cause : le targetPort ne correspond pas au port de l'application.

Symptôme : le nom du Service ne se résout plus du tout, y compris en FQDN. Avant de conclure, écartez le faux positif vu plus haut : des lignes NXDOMAIN après une résolution réussie sont normales. Un vrai échec DNS ne renvoie aucune adresse.

Fenêtre de terminal
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl run dns-test --image=busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 --rm -it --restart=Never -- nslookup kubernetes

Les trois vérifications précédentes partent de l'API. Celle-ci se place du point de vue d'un Pod, c'est-à-dire exactement là où votre application échoue. Une image outillée évite de découvrir que le conteneur applicatif n'a ni curl ni dig.

Fenêtre de terminal
kubectl run debug --image=nicolaka/netshoot:v0.16@sha256:b09d9b21381f47a79b3cbcb30da25266dc17186ea00ae65e99fdc51396f48e70 --rm -it --restart=Never -- bash

Depuis ce Pod : curl, nslookup, dig, tcpdump. Commencez par curl http://<service> : si la page arrive, le problème n'est pas réseau.

Sept questions pour vérifier que l'essentiel est acquis : choix du type, rôle du selector, lecture des EndpointSlices et diagnostic d'un Service qui ne répond pas.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  1. Un Service donne une IP stable et un nom DNS à un groupe de Pods
  2. Le selector doit correspondre aux labels des Pods (sinon pas d'endpoints)
  3. ClusterIP (défaut) : communication interne uniquement
  4. NodePort : exposition sur un port de chaque nœud (30000-32767)
  5. LoadBalancer : demande un équilibreur externe au cloud provider
  6. Headless (clusterIP: None) : DNS retourne directement les IPs des Pods
  7. EndpointSlices : API moderne qui stocke les adresses des Pods
  8. Un Service ne fait pas de routage HTTP, TLS ou filtrage sécurité, utilisez Ingress et NetworkPolicies

Ces questions reviennent souvent quand on connecte des applications dans Kubernetes. Les réponses ci-dessous reprennent les points clés du guide, types de Services, choix entre ClusterIP, NodePort et LoadBalancer, diagnostic des endpoints manquants.

Du premier Service à la panne. Vous exposez d'abord trois replicas par un ClusterIP et prouvez la répartition réelle entre les Pods, puis vous reprenez un Service qui ne dessert plus rien, dont le selector, le port et une politique réseau sont tous les trois en cause. Le second lab est le dépannage de ce que le premier vient de construire, pas un sujet voisin.

  • Les Ingress : L'exposition HTTP et HTTPS de vos Services vers l'extérieur du cluster.
  • Network Policies : Le filtrage du trafic qui atteint les Pods derrière vos Services.
  • Pod Networking : Le chemin réseau réel emprunté par une requête envoyée à une ClusterIP.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn