Kubernetes Kubernetes

Services : un réseau stable pour des Pods éphémères

Donnez une adresse stable à vos Pods, exposez-les à l'intérieur et à l'extérieur du cluster, et restreignez qui peut parler à qui.

14 min Intermédiaire

Chaque Pod reçoit sa propre adresse IP, mais les Pods vont et viennent, et leurs adresses avec eux. Un Service fournit une adresse stable devant un ensemble changeant de Pods. Ce guide s’appuie sur le Deployment web du guide précédent et couvre les Services, leur exposition en dehors du cluster, l’Ingress et les network policies.

Le problème : les IP des Pods changent

kubectl get pods -l app=web -o wide
kubectl delete pod POD_NAME
kubectl get pods -l app=web -o wide

Le Pod de remplacement a une IP différente, et il peut se trouver sur un autre nœud. Tout ce qui avait l’ancienne adresse codée en dur est maintenant cassé, et avec des réplicas qui vont et viennent, suivre les adresses à la main n’est pas envisageable.

Créer un Service ClusterIP

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Le selector choisit les Pods qui alimentent le Service, avec les mêmes labels que ceux vus avec les Deployments. port est le port sur lequel le Service écoute, et targetPort est le port des Pods vers lequel il transfère, qui doit correspondre au port du conteneur. Comme aucun type n’est défini, c’est un Service ClusterIP : joignable uniquement depuis l’intérieur du cluster, sur une IP virtuelle stable.

kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslices -l kubernetes.io/service-name=web

L’EndpointSlice liste les IP des Pods actuellement derrière le Service. Augmentez ou réduisez le nombre de réplicas du Deployment et elle se met à jour automatiquement, tandis que l’adresse du Service ne change jamais. (Les anciens clusters et tutoriels utilisent kubectl get endpoints web pour la même information. Les versions récentes déprécient cette API au profit des EndpointSlices.)

À l’intérieur du cluster, le Service reçoit aussi un nom DNS : web depuis le même namespace, ou web.NAMESPACE.svc.cluster.local de n’importe où. Testez-le depuis un Pod jetable :

kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web

Vous devriez voir la page d’accueil de nginx. Sur chaque nœud, kube-proxy maintient les règles qui répartissent les connexions entre les Pods prêts du Service. Dans le mode iptables courant, la répartition est en pratique aléatoire plutôt qu’un round-robin strict, et les Pods qui échouent à leur readiness check sont retirés de la rotation.

Exposer un Service en dehors du cluster

Il existe deux autres types de Service, et chacun s’appuie sur le précédent :

  • NodePort ouvre en plus un port (30000–32767) sur chaque nœud. Tout ce qui peut atteindre l’IP d’un nœud peut atteindre le Service sur ce port.
  • LoadBalancer demande en plus à l’environnement un load balancer externe qui pointe vers ces node ports. Chez un fournisseur cloud, cela se fait automatiquement. Sur du bare metal, il faut quelque chose comme MetalLB.
apiVersion: v1
kind: Service
metadata:
  name: web-public
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Omettez nodePort et Kubernetes en choisit un de libre. Sur minikube avec le driver Docker, l’IP du nœud n’est souvent pas directement joignable depuis votre ordinateur : laissez donc minikube l’ouvrir pour vous :

minikube service web-public --url

Pour un Service LoadBalancer sur minikube, exécutez minikube tunnel dans un autre terminal.

Router le HTTP avec Ingress

Un LoadBalancer par Service coûte vite cher et ne gère que du trafic brut. Un Ingress vous permet de router le trafic HTTP(S) vers de nombreux Services selon l’hôte et le chemin, via un seul point d’entrée, et d’y terminer le TLS. Un Ingress ne fait que décrire les règles. Il a besoin d’un ingress controller exécuté dans le cluster pour les appliquer (sur minikube, minikube addons enable ingress).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Les requêtes pour web.example.local vont désormais vers le Service web. Les certificats pour l’hôte peuvent être automatisés avec un outil comme cert-manager.

Restreindre le trafic avec NetworkPolicy

Par défaut, chaque Pod peut parler à tous les autres Pods. Une NetworkPolicy sélectionne des Pods et liste le trafic autorisé. Dès qu’un Pod est sélectionné en ingress par une policy, tout ce qui n’est pas explicitement autorisé est refusé. Commencez par refuser tout le trafic entrant dans un namespace :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress

Le podSelector vide sélectionne tous les Pods du namespace. Autorisez ensuite uniquement ce dont vous avez besoin, par exemple le trafic vers les Pods web depuis les Pods portant le label role: client :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-client-to-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: client
      ports:
        - protocol: TCP
          port: 80

Les network policies sont rattachées à un namespace, et elles ne sont appliquées que si le plugin réseau du cluster les prend en charge, par exemple Calico ou Cilium. Certaines installations locales minimales acceptent les objets mais les ignorent silencieusement : vérifiez donc qu’une connexion refusée est réellement bloquée.

Guides populaires