☸️ Partie 4 : Réseau & Stockage (Deep Dive)
CNI (Calico, Flannel), DNS (CoreDNS), Ingress (Nginx, Traefik), Storage (PV, PVC, StorageClass).
1. Modèle Réseau K8s
Les 3 règles : 1 IP par Pod (flat network). Pods <-> Pods. Pods <-> Services.
Réseau IP-per-Pod2. CNI (Le "Plugin" Réseau)
Container Network Interface. L'implémentation (Calico, Flannel, Cilium).
3. DNS (Service Discovery)
CoreDNS. Résolution service.namespace.svc.cluster.local.
4. Service (Rappel Réseau)
kube-proxy (iptables/ipvs). (ClusterIP, NodePort, LoadBalancer).
5. Ingress (Rappel Réseau)
Routeur L7 (HTTP/S). Ingress (Règle) vs Ingress Controller (Moteur).
6. NetworkPolicy (Firewall)
Le "Firewall" L3/L4 des Pods. (Défaut : Allow All). (Nécessite un CNI compatible).
7. Stockage (PV / PVC)
PV (L'Offre, Admin) vs PVC (La Demande, Dev). Statique vs Dynamique.
8. StorageClass (Provisioning)
Le "Provisioner" (modèle) pour le stockage dynamique. (aws-ebs, gce-pd, ceph).
9. Stockage (Access Modes)
RWO (ReadWriteOnce), ROX (ReadOnlyMany), RWX (ReadWriteMany).
K8s impose un modèle réseau (implémenté par le CNI, voir 4.2) simple mais puissant :
- Chaque Pod a sa propre adresse IP unique : C'est le modèle "IP-per-Pod". Il n'y a pas de "NAT" (translation d'adresse) complexe entre les Pods.
- Tous les Pods peuvent communiquer avec tous les autres Pods : (Sans NAT, quel que soit le Nœud sur lequel ils se trouvent).
- Tous les Services peuvent communiquer avec tous les Pods.
Le réseau est "flat" (plat). Un Pod sur le Nœud A (10.1.1.5) peut joindre un Pod sur le Nœud B (10.1.2.10) directement.
Ceci est généralement réalisé via un **Overlay Network** (ex: VXLAN, IP-in-IP) géré par le CNI.
Le "Modèle Réseau" (4.1) est la spécification. Le CNI (Container Network Interface) est le "plugin" (le "driver") qui l'implémente.
Lorsque vous installez un cluster (ex: kubeadm), une des premières étapes est kubectl apply -f cni.yaml. C'est là que vous choisissez votre CNI.
| CNI | Méthode (Simplifié) | Point Fort |
|---|---|---|
| Flannel (CoreOS) | Overlay (VXLAN). Simple. | Très simple à installer et à débugger. (Défaut K3s). |
| Calico (Tigera) | Routage L3 (BGP) (pas d'overlay par défaut). | Performance. Implémente NetworkPolicy (Firewall). |
| Cilium (Isovalent) | eBPF (in-kernel). | Moderne, ultra-performant. Implémente NetworkPolicy et Observabilité avancée (Hubble). |
| (Cloud-specific) | (Ex: AWS VPC CNI, GKE CNI) | Intégration native (IPs du VPC). |
Comment un Pod "Frontend" trouve-t-il l'IP (ClusterIP) du Service "Backend" ? Par DNS.
K8s exécute son propre serveur DNS interne : CoreDNS (un Deployment/Service dans kube-system).
Le kubelet configure automatiquement chaque Pod (dans son /etc/resolv.conf) pour utiliser ce CoreDNS.
Résolution de Noms
CoreDNS crée des enregistrements A (DNS) pour chaque Service :
service-name.namespace-name.svc.cluster.local(Nom complet - FQDN)- (Un Pod dans le même
namespacepeut juste appelerservice-name).
Exemple (/etc/resolv.conf d'un Pod)
# (Fichier à l'intérieur d'un Pod dans le namespace 'prod') nameserver 10.96.0.10 # (IP du Service 'kube-dns' (CoreDNS)) # (Domaines de recherche) search prod.svc.cluster.local svc.cluster.local cluster.local # (Quand vous appelez "api-backend" dans votre code...) # (Le résolveur essaie :) # 1. api-backend.prod.svc.cluster.local -> (TROUVÉ) # 2. api-backend.svc.cluster.local # 3. api-backend.cluster.local
kube-proxy)Un Service (voir 3.3) est un **load balancer L4 (TCP/UDP)** interne. Il fournit une **IP virtuelle (ClusterIP)** stable.
Comment ? Grâce à kube-proxy.
kube-proxy est un DaemonSet (voir 3.9) qui tourne sur **chaque Nœud**. Il observe l'API Server. Quand un Service (ou un Pod) est créé/supprimé, kube-proxy met à jour les règles iptables (ou ipvs) sur le Nœud hôte.
Diagramme (Flux kube-proxy)
(Pod A veut joindre "10.96.0.5" (ClusterIP du Service B))
|
▼
(Nœud 1: Règle iptables (créée par kube-proxy))
"Paquet pour 10.96.0.5 ?
-> Choisir aléatoirement (Load Balance):
- 10.1.1.2 (Pod B1)
- 10.1.2.3 (Pod B2)
-> Réécrire (DNAT) et envoyer."
|
▼
(Paquet arrive au Pod B1)
Un Ingress (voir 3.4) est un **routeur L7 (HTTP/S)**. Il gère le trafic externe (Internet) vers les Services (internes).
Attention : Il y a deux parties :
- L'Objet
Ingress: C'est le YAML. Une simple "Règle" (ex:host: a.com -> service-a). - L'
Ingress Controller: C'est le **vrai moteur** (un Pod NGINX, Traefik, HAProxy...). C'est un Deployment que vous devez installer (ex: via Helm) dans votre cluster. Il lit les "Règles" Ingress et configure son routage.
Diagramme (Flux Ingress)
(Internet)
|
▼
[Load Balancer Cloud (Service type: LoadBalancer)]
|
▼
+-----------------------+
| Pod Ingress Controller| (Ex: NGINX)
| (Service: 'nginx') |
+-----------------------+
| (Lit l'objet 'Ingress')
| (Règle: "Host: a.com -> service-a")
▼
+-----------------------+
| Service A (ClusterIP) |
+-----------------------+
|
▼
[Pod App A]
Par défaut, K8s a un réseau "flat" (plat) : **tous les Pods peuvent parler à tous les Pods** (zéro-confiance non-activée).
Une NetworkPolicy (NetPol) est un "Firewall" L3/L4 (règles IP/Port) pour les Pods. (Nécessite un CNI compatible, ex: Calico, Cilium. Flannel ne les supporte pas).
Dès qu'une NetPol cible un Pod (via podSelector), ce Pod devient **"Deny All"** (isolé) et n'accepte que le trafic explicitement autorisé (ingress/egress).
Exemple YAML (Isoler un Backend)
"Le Pod api-backend (dans le namespace prod) ne doit accepter du trafic (ingress) que des Pods app:frontend sur le port 8080."
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-backend-policy
namespace: prod
spec:
# 1. Quels Pods sont ciblés (isolés) ?
podSelector:
matchLabels:
app: api-backend
policyTypes:
- Ingress # (On applique des règles 'Ingress' (entrantes))
# 2. Règles 'Ingress'
ingress:
- from:
# (Autoriser les Pods qui ont ce label)
- podSelector:
matchLabels:
app: frontend
ports:
# (Sur ce port)
- protocol: TCP
port: 8080
Problème : Les Pods sont éphémères. Si un Pod de base de données (Postgres) plante, ses données sont perdues.
Solution : Le Stockage Persistant. C'est une abstraction pour attacher un "vrai" disque (ex: AWS EBS) à un Pod.
Il y a deux objets (séparation des rôles) :
PersistentVolume(PV) : "L'Offre". (L'Admin Infra) "J'ai créé un disque EBS de 100Go (pv-100go) et je le mets à dispo du cluster." (Provisioning Statique).PersistentVolumeClaim(PVC) : "La Demande". (Le Développeur) "J'ai besoin de 10Go de stockage rapide (postgres-pvc) pour mon Pod." (Le PVC "réclame" (claim) un PV disponible).
Diagramme (Provisioning Statique)
+-------------------------+
| Admin (Infra) |
| (Crée le disque (ex: EBS))|
| (Crée le YAML 'PV') |
| (PV "disque-rapide-50Go") |
+-------------------------+
| (1. `kubectl apply -f pv.yaml`)
▼
+-------------------------+
| Cluster (Pool de PVs) | (PV "disque-rapide-50Go" [Available])
+-------------------------+
| (3. K8s "lie" le PV au PVC)
▲
| (2. `kubectl apply -f pvc.yaml`)
+-------------------------+
| Dev (App) |
| (Crée le YAML 'PVC') |
| (PVC "db-claim" 10Go) |
+-------------------------+
|
▼ (4. Pod utilise le PVC)
+-------------------------+
| Pod (ex: Postgres) |
+-------------------------+
Le "Provisioning Statique" (4.7) est lourd (l'Admin doit pré-créer les disques manuellement).
Le Provisioning Dynamique (via StorageClass) est le standard.
Un StorageClass (SC) est un "template" (modèle) de stockage. (Ex: "fast-ssd", "slow-hdd", "backup-ceph").
Workflow :
- L'Admin crée un
StorageClass(ex: "aws-ebs-gp3"). - Le Dev crée un
PVC(Demande) qui **référence** ceStorageClass(storageClassName: "aws-ebs-gp3"). - K8s (via le "Provisioner" du SC) appelle l'API AWS, **crée (provisionne) dynamiquement** un nouveau disque EBS, et crée le
PV(Offre) correspondant. - K8s "lie" (bind) le PV au PVC.
Exemple YAML (storageclass.yaml)
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard-ebs # (Le nom que le PVC utilisera) # (Le "driver" qui sait comment appeler AWS) provisioner: kubernetes.io/aws-ebs parameters: type: gp3 # (Type de disque EBS) fsType: ext4 reclaimPolicy: Retain # (Garde le disque si le PVC est supprimé) # (ou 'Delete')
Les "Access Modes" (dans le PV/PVC) définissent *comment* le disque peut être "monté" (attaché) aux Nœuds.
| Mode | Nom Complet | Description | Cas d'usage / Backends |
|---|---|---|---|
RWO | ReadWriteOnce | Le volume peut être monté (en lecture/écriture) par **UN SEUL** Nœud à la fois. | (Standard) Bases de données (Postgres, MySQL), Caches (Redis). (AWS EBS, GCE PD, Ceph RBD, Longhorn). |
ROX | ReadOnlyMany | Le volume peut être monté (en lecture seule) par **PLUSIEURS** Nœuds. | (Rare) Partager des assets/config (ex: modèles ML). |
RWX | ReadWriteMany | Le volume peut être monté (en lecture/écriture) par **PLUSIEURS** Nœuds simultanément. | (Complexe/Coûteux) CMS (WordPress), stockage partagé. (Nécessite un VRAI système de fichiers réseau : NFS, CephFS, GlusterFS). |
Piège classique : Vous ne pouvez pas faire de RWX (ReadWriteMany) avec un StorageClass AWS EBS (Disque bloc). Vous devez utiliser EFS (NFS).
