Introduction
Vous savez désormais déployer et dépanner des applications, mais les clients ont encore besoin d’un moyen fiable pour y accéder. L’adresse IP d’un Pod est temporaire : un Deployment peut remplacer un Pod à tout moment, et le nouveau Pod peut recevoir une autre adresse IP.
Kubernetes résout ce problème grâce à un Service. Un Service sélectionne un groupe de Pods susceptible d’évoluer et fournit aux clients une identité réseau stable. Dans ce laboratoire, vous suivrez le chemin complet d’une connexion, depuis les labels jusqu’aux EndpointSlices et au DNS du cluster, puis vous découvrirez deux types de Services : ClusterIP pour les accès internes au cluster et NodePort pour les accès via un nœud.
Ce laboratoire s’arrête volontairement aux Services. Ingress ajoute le routage HTTP ainsi qu’un contrôleur distinct ; il est plus facile à comprendre une fois la sélection et l’accessibilité des Services bien maîtrisées.
Déployer les backends du Service
Démarrage de l’environnement : Ce lab démarre pour vous un cluster Kubernetes complet. La configuration du plan de contrôle, du nœud et des composants réseau prend généralement 2 à 3 minutes. Veuillez patienter jusqu’à la fin du chargement de l’environnement avant de commencer.
Dans cette étape, vous allez créer l’application répliquée que les Services exposeront par la suite. Ici, un backend désigne un Pod capable de recevoir le trafic destiné à un Service.
Accédez à l’espace de travail. cd modifie le répertoire courant du shell ; en cas de réussite, la commande n’affiche généralement rien :
cd /home/labex/project/service-lab
Créez un Deployment comportant deux répliques. Le label de Pod app: course-nginx est particulièrement important, car les Services l’utiliseront comme règle de sélection.
La syntaxe shell cat <<'EOF' > filename est un here-document. Tout le contenu situé avant le EOF de fermeture est écrit dans le fichier, et > crée ce fichier ou le remplace. Le premier EOF étant placé entre guillemets, le shell n’effectue aucune expansion accidentelle dans le YAML.
cat <<'EOF' > course-nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: course-nginx
spec:
replicas: 2
selector:
matchLabels:
app: course-nginx
template:
metadata:
labels:
app: course-nginx
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
EOF
Appliquez le manifeste et attendez que les deux Pods soient prêts. -f indique à apply le fichier à lire ; rollout status attend la fin de la mise à jour gérée par le contrôleur du Deployment ; --timeout=60s limite cette attente. Dans la dernière commande, -l filtre les résultats par label et -o wide ajoute les colonnes contenant l’adresse IP du Pod et le nœud :
kubectl apply -f course-nginx-deployment.yaml
kubectl rollout status deployment/course-nginx --timeout=60s
kubectl get pods -l app=course-nginx -o wide
Le port nommé http documente le port TCP 80 de chaque conteneur. Les deux lignes doivent afficher l’état Running et des adresses IP de Pods différentes. Ces adresses sont réelles, mais ne constituent pas des points d’accès durables pour les clients ; les prochaines étapes vont placer devant elles une identité stable fournie par un Service.
Relier les labels à la sélection du Service
Dans cette étape, vous allez examiner les métadonnées qui relient un Service aux Pods. Un Service ne sélectionne pas un Deployment par son nom ; il recherche indépendamment les Pods dont les labels correspondent à son sélecteur.
Affichez les labels des Pods. -l app=course-nginx est un sélecteur de labels, tandis que --show-labels ajoute l’ensemble complet des labels dans une dernière colonne :
kubectl get pods -l app=course-nginx --show-labels
Chaque Pod possède app=course-nginx ainsi qu’un label pod-template-hash généré automatiquement. Votre Service doit sélectionner uniquement le label stable de l’application.
Comparez les noms, les labels et les adresses IP dans une vue compacte. -o custom-columns crée un tableau à partir des champs sélectionnés des objets. Chaque en-tête situé avant : est suivi du chemin du champ utilisé pour cette colonne ; la barre oblique inverse permet de poursuivre une même commande sur la ligne d’affichage suivante :
kubectl get pods -l app=course-nginx \
-o custom-columns='NAME:.metadata.name,LABEL:.metadata.labels.app,IP:.status.podIP,READY:.status.containerStatuses[0].ready'
Les noms et adresses IP générés permettent de distinguer les Pods individuellement ; le label commun décrit leur rôle. Cette indirection permet au Service de continuer à fonctionner lorsqu’un Pod est remplacé.
Vérifiez que le sélecteur du Deployment et le label du modèle de Pod correspondent. -o jsonpath='...' extrait uniquement les champs demandés. Le texte placé en dehors des accolades devient un libellé, les chemins de champs placés entre accolades renvoient les valeurs et {"\n"} insère un retour à la ligne :
kubectl get deployment course-nginx \
-o jsonpath='Selector: {.spec.selector.matchLabels.app}{"\n"}Pod label: {.spec.template.metadata.labels.app}{"\n"}'
Les deux valeurs doivent être course-nginx. Un sélecteur incorrect empêcherait le contrôleur ou le Service de communiquer avec les Pods attendus.
Créer un Service ClusterIP
Dans cette étape, vous allez créer le type de Service par défaut, ClusterIP. Il fournit une adresse IP virtuelle et un nom DNS accessibles aux workloads à l’intérieur du cluster.
Créez le manifeste en reprenant le modèle de here-document présenté au début de ce laboratoire :
cat <<'EOF' > course-nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: course-nginx
spec:
type: ClusterIP
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
EOF
Examinez attentivement la correspondance des ports :
port: 80est le port utilisé par les clients sur le Service.targetPort: httpdésigne le port nommé du conteneur dans chaque Pod sélectionné.- Le sélecteur choisit les Pods backend ; il ne constitue pas une adresse réseau.
Validez, appliquez et inspectez le Service. --dry-run=client analyse le manifeste localement sans rien créer ; sa suppression déclenche l’application réelle ; get service lit ensuite l’objet actif :
kubectl apply --dry-run=client -f course-nginx-service.yaml
kubectl apply -f course-nginx-service.yaml
kubectl get service course-nginx
La valeur CLUSTER-IP est attribuée par Kubernetes. Elle reste stable pendant toute la durée de vie de ce Service, même lorsque ses Pods backend changent.
Suivre le Service jusqu’aux EndpointSlices
Dans cette étape, vous allez suivre le sélecteur du Service jusqu’aux adresses réelles des backends. Kubernetes enregistre ces adresses dans des objets EndpointSlice.
Décrivez le Service. describe développe un objet nommé afin d’afficher sa configuration, son état et les informations associées aux endpoints ; cette commande est donc utile après le tableau plus succinct produit par get :
kubectl describe service course-nginx
Recherchez Selector: app=course-nginx ainsi qu’une ligne Endpoints contenant deux adresses IP de Pods sur le port 80.
Répertoriez l’EndpointSlice sélectionnée par le label correspondant au nom du Service. Ce sélecteur -l utilise le label ajouté automatiquement kubernetes.io/service-name=course-nginx :
kubectl get endpointslices -l kubernetes.io/service-name=course-nginx
Inspectez ses adresses et leur état de disponibilité. Ce JSONPath utilise range pour répéter le modèle inclus pour chaque endpoint. Il affiche la première adresse, le texte littéral ready=, la valeur de disponibilité, puis un retour à la ligne :
kubectl get endpointslices -l kubernetes.io/service-name=course-nginx \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
Vous devriez voir deux adresses avec ready=true. La chaîne de correspondance est désormais concrète :
Service selector -> matching Pod labels -> EndpointSlice addresses -> ready Pods
Si un Service existe mais ne possède aucun endpoint, commencez par comparer son sélecteur avec les labels et l’état de disponibilité des Pods.
Accéder au Service via le DNS du cluster
Dans cette étape, vous allez vous comporter comme un client situé dans le cluster. Le DNS Kubernetes permet à un Pod du même namespace d’utiliser le nom du Service course-nginx plutôt que de mémoriser son adresse IP virtuelle.
Lancez un Pod BusyBox temporaire, demandez la page NGINX et supprimez automatiquement le client lorsqu’il se termine. Lisez les options de haut en bas :
--imagechoisit l’image du conteneur et--image-pull-policy=IfNotPresentréutilise la copie mise en cache.--restart=Nevercrée un Pod autonome plutôt qu’un workload géré par un contrôleur.--rmsupprime le Pod à la fin de sa commande, tandis que-imaintient l’entrée et la sortie de la commande attachées.- Le séparateur
--marque la fin des options de kubectl ; tout ce qui suit est exécuté dans le conteneur. wget -qO-demande l’URL silencieusement et écrit le corps de la réponse dans le terminal.
kubectl run service-client \
--image=busybox:1.36 \
--image-pull-policy=IfNotPresent \
--restart=Never \
--rm -i \
-- wget -qO- http://course-nginx
La réponse contient la page d’accueil de NGINX. Le trafic est passé par le Service, et non directement par l’adresse IP d’un Pod choisi.
Effectuez un contrôle de réussite plus discret. >/dev/null ignore le corps HTML, tandis que && n’affiche le message que si la commande de requête réussit :
kubectl run service-client-check \
--image=busybox:1.36 \
--image-pull-policy=IfNotPresent \
--restart=Never \
--rm -i \
-- wget -qO- http://course-nginx >/dev/null && echo "ClusterIP Service responded"
Le message de réussite confirme à la fois la résolution DNS et l’accessibilité HTTP. Le Pod client est temporaire ; le Service et ses deux Pods backend restent en place.
Ajouter un Service NodePort
Dans cette étape, vous allez créer un second Service pour les mêmes Pods, en utilisant le type NodePort. Un NodePort ouvre un port compris dans la plage par défaut 30000–32767 sur chaque nœud et redirige le trafic vers les backends du Service.
Kubernetes peut lire plusieurs objets depuis un seul fichier YAML multi-document. Chaque objet conserve ses propres champs apiVersion, kind, metadata et spec ; une ligne contenant --- sépare deux documents YAML successifs.
Créez un fichier réutilisable contenant le Service ClusterIP existant ainsi que le nouveau Service NodePort. Répéter la définition du Service ClusterIP est sans danger : appliquer le même état souhaité ne le modifie pas.
cat <<'EOF' > course-nginx-services.yaml
apiVersion: v1
kind: Service
metadata:
name: course-nginx
spec:
type: ClusterIP
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: course-nginx-nodeport
spec:
type: NodePort
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
nodePort: 30080
protocol: TCP
EOF
Validez les deux documents YAML ensemble avant de modifier le cluster. La sortie doit mentionner service/course-nginx et service/course-nginx-nodeport, puis (dry run) :
kubectl apply --dry-run=client -f course-nginx-services.yaml
Appliquez les manifestes et inspectez les Services. La première commande lit les deux documents depuis un seul fichier ; le Service ClusterIP existant doit apparaître comme unchanged, tandis que le Service NodePort est créé. La seconde commande lit le nouveau Service actif :
kubectl apply -f course-nginx-services.yaml
kubectl get service course-nginx-nodeport
La colonne PORT(S) affiche 80:30080/TCP : le port 80 est le port du Service et 30080 est le port exposé sur le nœud. Les deux Services sélectionnent les mêmes Pods et peuvent donc utiliser les mêmes adresses de backend.
Comparer les deux points d’accès
Dans cette étape, vous allez tester le NodePort et récapituler dans quels cas chaque type de Service est approprié.
Obtenez l’adresse IP du nœud Minikube. $(...) est une substitution de commande : le shell exécute minikube ip et stocke sa sortie dans la variable NODE_IP. -p labex-v135 sélectionne le profil préparé, tandis que echo permet d’afficher la valeur stockée :
NODE_IP=$(minikube ip -p labex-v135)
echo "$NODE_IP"
Demandez l’application via le port exposé sur le nœud. Kubernetes peut avoir besoin de quelques secondes pour configurer la nouvelle règle réseau au niveau du nœud après l’acceptation du Service. Les options de nouvelle tentative demandent à curl d’attendre pendant cette courte phase de convergence plutôt que d’échouer dès le premier refus de connexion :
curl -s --retry 5 --retry-connrefused --retry-delay 2 "http://${NODE_IP}:30080" | grep 'Welcome to nginx'
Ici, -s masque l’indicateur de progression, --retry 5 autorise jusqu’à cinq nouvelles tentatives, --retry-connrefused considère un refus initial comme une erreur permettant une nouvelle tentative, et --retry-delay 2 attend deux secondes entre chaque essai. Les guillemets doubles permettent l’expansion de ${NODE_IP} dans l’URL. Le tube | transmet le HTML reçu à grep, qui affiche la ligne d’accueil correspondante comme preuve.
Le titre HTML correspondant prouve que la requête a atteint un Pod backend. Comparez les deux chemins mis en place :
in-cluster Pod -> course-nginx:80 -> ready backend Pod
VM/node client -> NODE_IP:30080 -> course-nginx-nodeport:80 -> ready backend Pod
Inspectez les deux Services ensemble :
kubectl get services course-nginx course-nginx-nodeport
Utilisez ClusterIP pour les communications stables à l’intérieur du cluster ; il s’agit du type par défaut et du socle habituel des autres mécanismes d’exposition. NodePort ajoute un point d’entrée au niveau du nœud et convient à l’apprentissage, au développement ou à l’intégration avec des équilibreurs de charge externes. Les deux types dépendent de sélecteurs corrects et d’EndpointSlices dont les backends sont prêts.
Vous mettrez ces notions en pratique de façon autonome dans le prochain défi, en exposant plusieurs workloads web.
Résumé
Vous avez créé une identité réseau stable devant des Pods susceptibles d’être remplacés. Vous avez relié les sélecteurs de Service aux labels des Pods, suivi les backends sélectionnés à travers les EndpointSlices, atteint un Service ClusterIP via le DNS du cluster et ajouté un point d’entrée NodePort via le nœud.
Le modèle mental essentiel est qu’un Service n’est ni l’application ni un conteneur de Pods. Il représente en permanence un ensemble sélectionné de backends prêts. En cas de problème de connectivité, suivez la chaîne dans l’ordre : ports du Service, sélecteur, labels des Pods, EndpointSlices, disponibilité des backends, puis frontière au niveau du client.


