Un Service Kubernetes résout un problème fondamental : comment accéder aux Pods de manière stable ?
Les Pods dans Kubernetes sont éphémères :
Un Service fournit :
Imaginez un restaurant avec plusieurs cuisiniers (Pods) :
| Type | Accès | Usage typique |
|---|---|---|
| ClusterIP | Interne uniquement | Communication entre services dans le cluster |
| NodePort | Externe via IP:Port des nœuds | Dev/test, accès direct |
| LoadBalancer | Externe via IP dédiée | Production, apps publiques |
| ExternalName | Alias DNS | Redirection vers service externe |
┌─────────────────────────────────────┐
│ Cluster Kubernetes │
│ │
│ ┌──────┐ Service (ClusterIP) │
│ │ Pod1 │◄───┐ 10.96.0.100:80 │
│ └──────┘ │ │
│ ├─► Load Balancer │
│ ┌──────┐ │ │
│ │ Pod2 │◄───┘ │
│ └──────┘ │
│ │
│ Accessible uniquement depuis │
│ l'intérieur du cluster │
└─────────────────────────────────────┘
# Deployment du backend
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: api
image: myapi:1.0
ports:
- containerPort: 8080
---
# Service ClusterIP pour le backend
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP # Type par défaut
selector:
app: backend # Cible les Pods avec ce label
ports:
- name: http
protocol: TCP
port: 80 # Port du Service
targetPort: 8080 # Port du conteneur
# Accès depuis un autre Pod :
# curl http://backend-service.default.svc.cluster.local
# ou simplement : curl http://backend-service
Le Service crée automatiquement un enregistrement DNS :
<service>.<namespace>.svc.cluster.local<service>backend-service ou backend-service.default.svc.cluster.local<NodeIP>:<NodePort>┌─────────────────────────────────────┐
│ Cluster Kubernetes │
│ │
│ Node1: 192.168.1.10:30080 │
│ ▼ │
│ Service (NodePort) │
│ ▼ │
│ ┌──────┐ ┌──────┐ │
│ │ Pod1 │ │ Pod2 │ │
│ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────┘
▲
│
Internet / Réseau externe
http://192.168.1.10:30080
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp-test
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
# Service NodePort
apiVersion: v1
kind: Service
metadata:
name: webapp-nodeport
spec:
type: NodePort
selector:
app: webapp
ports:
- name: http
protocol: TCP
port: 80 # Port du Service (ClusterIP)
targetPort: 80 # Port du conteneur
nodePort: 30080 # Port sur chaque nœud (optionnel)
# Si omis, Kubernetes assigne automatiquement un port 30000-32767
# Accès externe :
# http://<IP-du-noeud>:30080
# Exemples :
# http://192.168.1.10:30080
# http://192.168.1.11:30080 (si plusieurs nœuds)
# Créer le service
kubectl apply -f webapp-nodeport.yml
# Obtenir l'URL d'accès
minikube service webapp-nodeport --url
# Ou ouvrir directement dans le navigateur
minikube service webapp-nodeport
┌──────────────────────────────────────┐
│ Internet │
└───────────────┬──────────────────────┘
│
LoadBalancer IP
192.168.1.240:80
│
┌───────────────▼──────────────────────┐
│ Cluster Kubernetes │
│ │
│ Service (LoadBalancer) │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Pod1 │ │ Pod2 │ │
│ │ :80 │ │ :80 │ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────────────┘
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp-prod
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: webapp
env: production
template:
metadata:
labels:
app: webapp
env: production
spec:
containers:
- name: app
image: myapp:v2.0
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "200m"
---
# Service LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: webapp-loadbalancer
namespace: production
annotations:
# Pour MetalLB : assigner une IP spécifique
metallb.universe.tf/loadBalancerIPs: "192.168.1.240"
# Pour AWS : type de load balancer
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
type: LoadBalancer
selector:
app: webapp
env: production
ports:
- name: http
protocol: TCP
port: 80 # Port externe
targetPort: 8080 # Port du conteneur
- name: https
protocol: TCP
port: 443
targetPort: 8443
# Optionnel : restreindre les IPs sources
loadBalancerSourceRanges:
- 10.0.0.0/8 # Réseau interne
- 203.0.113.0/24 # IP publique spécifique
# Accès externe :
# http://192.168.1.240
# Le DNS peut pointer vers cette IP :
# monapp.com -> 192.168.1.240
# Appliquer le manifeste
kubectl apply -f webapp-loadbalancer.yml
# Vérifier l'IP assignée
kubectl get svc webapp-loadbalancer
# Sortie :
# NAME TYPE EXTERNAL-IP PORT(S)
# webapp-loadbalancer LoadBalancer 192.168.1.240 80:31234/TCP
# Tester
curl http://192.168.1.240
┌─────────────────────────────────────┐
│ Cluster Kubernetes │
│ │
│ Pod demande: │
│ "database-service" │
│ │ │
│ ▼ │
│ Service ExternalName │
│ Redirige vers: │
│ "db.external.com" │
│ │
└─────────────┬───────────────────────┘
│
▼
┌─────────────────────┐
│ Service externe │
│ db.external.com │
└─────────────────────┘
# Service pointant vers une BD externe
apiVersion: v1
kind: Service
metadata:
name: database-service
namespace: default
spec:
type: ExternalName
externalName: mysql.external-provider.com # DNS externe
ports:
- port: 3306
# Les Pods peuvent maintenant utiliser :
# mysql -h database-service -P 3306
# au lieu de :
# mysql -h mysql.external-provider.com -P 3306
# Service pour une API externe
apiVersion: v1
kind: Service
metadata:
name: payment-api
spec:
type: ExternalName
externalName: api.stripe.com
ports:
- port: 443
# Utilisation dans l'application :
# https://payment-api/v1/charges
# au lieu de :
# https://api.stripe.com/v1/charges
# Phase 1 : Service pointe vers l'ancien système
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ExternalName
externalName: legacy-users.company.com
---
# Phase 2 : Nouveau service Kubernetes déployé
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-new
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: api
image: user-service:v2.0
ports:
- containerPort: 8080
---
# Phase 3 : Changer le Service pour pointer vers les nouveaux Pods
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ClusterIP # Changé de ExternalName à ClusterIP
selector:
app: user-service # Pointe vers les nouveaux Pods
ports:
- port: 80
targetPort: 8080
# Les applications continuent d'utiliser "user-service" sans changement!
Application avec frontend, backend, et base de données :
# Base de données (ClusterIP - interne)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:14
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: "secret"
---
apiVersion: v1
kind: Service
metadata:
name: database-service
spec:
type: ClusterIP # Interne uniquement
selector:
app: db
ports:
- port: 5432
targetPort: 5432
# Backend API (ClusterIP - interne)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapi:1.0
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "database-service" # Utilise le nom du Service
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP # Interne uniquement
selector:
app: api
ports:
- port: 80
targetPort: 8080
# Frontend Web (LoadBalancer - public)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: myfrontend:1.0
ports:
- containerPort: 80
env:
- name: API_URL
value: "http://backend-service" # Utilise le nom du Service
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: LoadBalancer # Accessible publiquement
selector:
app: web
ports:
- port: 80
targetPort: 80
# Architecture :
# Internet → LoadBalancer (frontend) → ClusterIP (backend) → ClusterIP (database)
| Critère | ClusterIP | NodePort | LoadBalancer | ExternalName |
|---|---|---|---|---|
| Accès externe | ❌ Non | ✅ Oui | ✅ Oui | ✅ Oui |
| IP dédiée | ❌ Non | ❌ Non | ✅ Oui | ❌ Non |
| Load balancing | ✅ Oui | ✅ Oui | ✅ Oui | ❌ Non |
| Production | ✅ Oui | ❌ Non | ✅ Oui | ✅ Oui |
| Coût cloud | Gratuit | Gratuit | 💰 Payant * | Gratuit |
| Usage typique | Services internes | Dev/Test | Apps publiques | Services externes |
* Peut-être implémenté localement, par exemple, MetalLB.
# Créer un Service
kubectl apply -f service.yml
# Lister les Services
kubectl get services
kubectl get svc
# Détails d'un Service
kubectl describe service mon-service
# Voir les endpoints (Pods ciblés)
kubectl get endpoints mon-service
# Tester un Service depuis un Pod
kubectl run test --rm -it --image=busybox -- sh
wget -O- http://mon-service
# Pour NodePort : obtenir l'URL (Minikube)
minikube service mon-service --url
# Pour LoadBalancer : obtenir l'IP externe
kubectl get svc mon-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Supprimer un Service
kubectl delete service mon-service
Service = Point d’accès stable pour des Pods éphémères
Règle d’or : Toujours utiliser des Services, jamais les IPs des Pods directement !