☸️ Partie 3 : Objets Essentiels (Deep Dive)
Pods, Deployments, Services, Ingress, ConfigMaps, Secrets, Namespaces, PVs, DaemonSets.
1. Pod
L'"atome" de K8s. La plus petite unité déployable. (Enveloppe pour 1+ conteneurs).
Pod Container2. Deployment
Contrôleur (replicas, rollingUpdate). Gère l'état désiré des Pods.
3. Service
IP/DNS interne stable pour un groupe de Pods (ClusterIP, NodePort, LoadBalancer).
4. Ingress
Routeur L7 (HTTP/S) externe. (host, path). (Nginx, Traefik).
5. ConfigMap
Gère la configuration non-sensible (config.toml, ENV_VARS) (texte brut).
6. Secret
Gère les données sensibles (API Keys, Passwords). (Stocké en Base64, non chiffré !).
Secret Base647. Namespace
Isolation logique (cluster virtuel). (default, kube-system, prod, dev).
8. PersistentVolume (PV/PVC)
Stockage persistant (Disque). (PV = L'Admin donne | PVC = Le Dev demande).
9. DaemonSet
Contrôleur qui garantit 1 Pod sur (presque) *chaque* Nœud. (Agents).
DaemonSet AgentsLe Pod est la plus petite unité de calcul déployable dans Kubernetes. C'est l'**atome** de K8s.
Important : Un Pod n'est **pas** un conteneur. C'est une **enveloppe** (un "groupe") pour **un ou plusieurs** conteneurs qui partagent :
- Le même réseau (la même adresse IP,
localhost). - Les mêmes volumes (stockage).
Cas 99% : Un Pod = 1 Conteneur (ex: votre API Python).
Cas 1% (Sidecar) : Un Pod = 2+ Conteneurs. (Ex: 1 conteneur App + 1 conteneur "Sidecar" pour le logging (DataDog Agent), le réseau (Istio Proxy), ou le cache).
Exemple YAML (pod-simple.yaml)
(Note : On ne crée (presque) jamais de Pod "nu" (naked pod). On utilise un Deployment (3.2) pour les gérer).
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: webserver
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
Commandes kubectl
# 1. (NE PAS FAIRE : kubectl run... (Impératif)) # 2. Créer (Déclaratif) $ kubectl apply -f pod-simple.yaml # 3. Lister $ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-pod 1/1 Running 0 10s # 4. Debugger (POURQUOI il plante ?) # (Regarde les "Events" (ex: ImagePullBackOff, CrashLoopBackOff)) $ kubectl describe pod nginx-pod # 5. Voir les logs (stdout) $ kubectl logs nginx-pod # 6. Ouvrir un shell DANS le pod $ kubectl exec -it nginx-pod -- /bin/bash
Un Deployment est un "Contrôleur" (Controller). Son rôle est de gérer l'État Désiré (voir 1.2) d'un groupe de Pods identiques via un ReplicaSet.
C'est l'objet que vous utilisez 90% du temps pour déployer une application "stateless" (ex: API, Frontend).
Il gère :
replicas: Le nombre de copies (ex: "Je veux 3 copies de mon Pod NGINX").strategy: RollingUpdate: Comment mettre à jour (ex: "Mettre à jour 1 Pod à la fois, sans temps d'arrêt").
Diagramme (Hiérarchie)
+------------------+
| Deployment | (Votre YAML)
| (Stratégie) |
+------------------+
| (Crée/Met à jour)
▼
+------------------+
| ReplicaSet (v1.2)| (Gère le "Count")
+------------------+
| (Crée/Met à jour)
▼
[Pod] [Pod] [Pod]
Exemple YAML (deploy-nginx.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # <-- 1. L'ÉTAT DÉSIRÉ (Je veux 3 Pods)
selector: # <-- 2. LE LIEN (Quels Pods ce Deploy gère-t-il ?)
matchLabels:
app: nginx-web
strategy:
type: RollingUpdate # (Stratégie de mise à jour)
template: # <-- 3. LE MODÈLE (Le YAML du Pod à créer)
metadata:
labels:
app: nginx-web # (Doit correspondre au 'selector' ci-dessus)
spec:
containers:
- name: nginx
image: nginx:1.25.0
ports:
- containerPort: 80
Commandes kubectl
# 1. Appliquer (Créer ou Mettre à jour) $ kubectl apply -f deploy-nginx.yaml # 2. Lister les déploiements $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 5m # 3. Mettre à l'échelle (Scaler) $ kubectl scale deployment/nginx-deployment --replicas=5 # 4. Mettre à jour (Changer l'image dans le YAML, puis 'apply') # (Ou en impératif :) $ kubectl set image deployment/nginx-deployment nginx=nginx:1.26.0 # 5. Voir le statut du "Rollout" $ kubectl rollout status deployment/nginx-deployment # 6. Redémarrer (Forcer un "Rolling Restart" de tous les Pods) $ kubectl rollout restart deployment/nginx-deployment
Problème : Les Pods (gérés par un Deployment) sont "éphémères". Ils peuvent être tués, redémarrés, ou scalés, et leur adresse IP change constamment. Comment une API "Frontend" peut-elle parler à une API "Backend" si l'IP change ?
Solution : Un Service. C'est un **load balancer interne** (L4) qui fournit une **adresse IP et un nom DNS stables** pour un groupe de Pods (définis par un selector).
Diagramme (ClusterIP)
+----------+
| Pod A | (IP: 10.1.1.1)
| (Frontend)|
+----------+
| (Appelle "http://service-backend")
| (K8s DNS (CoreDNS) résout "service-backend" -> 10.96.0.5)
▼
+-----------------------+
| Service (ClusterIP) | (IP Fixe: 10.96.0.5)
| (service-backend) |
| (Selector: app=backend) |
+-----------------------+
| (kube-proxy : Load Balancing)
+---------------------+
| |
▼ ▼
+----------+ +----------+
| Pod B | (IP: 10.1.1.2) | Pod C | (IP: 10.1.1.3)
| (Backend) | | (Backend) |
+----------+ +----------+
Types de Services
| Type | Description |
|---|---|
ClusterIP (Défaut) | Expose le Service sur une IP **interne** au cluster. Inaccessible de l'extérieur. (Pour la communication Backend <-> Backend). |
NodePort | Expose le Service sur un port statique (ex: 30001) sur l'IP de **chaque Nœud**. (Pour le dev/test, ou Ingress (3.4)). |
LoadBalancer | (Cloud) Crée un Load Balancer **externe** (ex: ELB/ALB sur AWS, GCLB sur GCP) qui pointe vers le NodePort. |
Exemple YAML (service-backend.yaml)
apiVersion: v1
kind: Service
metadata:
name: service-backend
spec:
# 1. Type (Défaut: ClusterIP)
type: ClusterIP
# 2. Selector (Le LIEN)
# (Trouve tous les Pods avec ce label)
selector:
app: backend-api
# 3. Ports
ports:
- protocol: TCP
port: 80 # (Port du Service)
targetPort: 8080 # (Port du Conteneur (Pod))
Problème : Un Service de type LoadBalancer (3.3) est L4 (TCP) et coûte cher (1 IP publique par service). Comment exposer 50 services (sites web) sur une seule IP, avec des URLs (https://.../api) et du HTTPS ?
Solution : Un Ingress. C'est un **routeur L7 (HTTP/S)**. Il gère le routage basé sur le Host (hôte) et le Path (chemin) vers les Services (ClusterIP) internes.
Note : Un Ingress n'est qu'une "règle". Il nécessite un "Ingress Controller" (ex: NGINX, Traefik) [cite: 18] pour l'exécuter.
Diagramme (Routage L7)
(Internet)
|
▼
[Load Balancer Cloud (ex: AWS ELB)]
|
▼
+-----------------------+
| Ingress Controller | (Ex: Pod NGINX)
| (Expose 80/443) |
+-----------------------+
| (Lit les règles Ingress)
|
| (Host: "site.com", Path: "/api/users")
+----------------------> vers "service-users" (ClusterIP)
|
| (Host: "site.com", Path: "/api/payments")
+----------------------> vers "service-payments" (ClusterIP)
Exemple YAML (ingress-main.yaml)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
annotations:
# (Ex: Activer la réécriture (NGINX))
nginx.ingress.kubernetes.io/rewrite-target: /
# (Ex: Gérer le HTTPS via cert-manager)
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- "mon-site.com"
secretName: mon-site-tls # (Secret contenant le certif HTTPS)
rules:
- host: "mon-site.com"
http:
paths:
- path: /api/users # (Trafic pour /api/users/...)
pathType: Prefix
backend:
service:
name: service-users # (Va au Service "users")
port:
number: 80
- path: /api/payments # (Trafic pour /api/payments/...)
pathType: Prefix
backend:
service:
name: service-payments # (Va au Service "payments")
port:
number: 80
Problème : Ne pas "hardcoder" (écrire en dur) la configuration (ex: API_URL, DEBUG_MODE) dans l'image Docker (sinon il faut re-builder l'image pour changer d'env).
Solution : Un ConfigMap. C'est un objet K8s (un dictionnaire) qui stocke des données de configuration **non-sensibles** (en texte brut).
1. Définir le ConfigMap (configmap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
# (Clé/Valeur simple, pour les variables d'env)
API_URL: "https://api.monsite.com"
DEBUG: "false"
# (Clé/Valeur multi-lignes, pour monter en fichier)
config.toml: |
[database]
host = "localhost"
timeout = 30
2. Consommer le ConfigMap (deployment.yaml)
# (Extrait de spec.template.spec.containers[0])
spec:
containers:
- name: my-app
image: my-app:1.2
# Voie 1: Injecter (toutes les clés) en Variables d'Env
envFrom:
- configMapRef:
name: my-app-config # (Nom du ConfigMap)
# Voie 2: Monter en Volume (Fichier)
volumeMounts:
- name: config-volume
mountPath: /etc/config # (Le fichier sera /etc/config/config.toml)
# (Définir le volume)
volumes:
- name: config-volume
configMap:
name: my-app-config
Un Secret est identique à un ConfigMap (un dictionnaire), mais il est conçu pour les données **sensibles** (API_KEY, DB_PASSWORD, certif-tls.key).
Attention : Les Secrets sont stockés dans etcd en Base64 (un simple encodage, **PAS DU CHIFFREMENT**). [cite: 20] (N'importe qui pouvant lire etcd ou kubectl get secret -o yaml peut les déchiffrer).
L'avantage est sémantique (K8s les traite différemment) et permet le chiffrement "au repos" (etcd encryption-at-rest) ou l'injection via des gestionnaires externes (ex: HashiCorp Vault).
Création (Impératif - Recommandé)
# (Ne pas stocker de mots de passe en clair dans un YAML Git !)
# 1. Créer un secret "generic"
$ echo -n "mon-super-password" | base64
bW9uLXN1cGVyLXBhc3N3b3Jk
# (Ou laisser kubectl le faire)
$ kubectl create secret generic db-secret \
--from-literal=DB_USER="admin" \
--from-literal=DB_PASSWORD="mon-super-password"
Consommation (deployment.yaml)
# (Extrait de spec.template.spec.containers[0])
spec:
containers:
- name: my-app
image: my-app:1.2
# Injecter (toutes les clés) en Variables d'Env
envFrom:
- secretRef:
name: db-secret # (Nom du Secret)
# (Ou monter en Volume/Fichier)
Un Namespace (Espace de noms) est un "cluster virtuel" à l'intérieur de votre cluster K8s. C'est un "dossier" pour organiser vos ressources.
Pourquoi ? Pour l'**isolation**. (Séparer prod, staging, dev).
Un Service (ex: db-service) dans le namespace prod ne peut pas (facilement) parler à un Service dans le namespace dev.
Les noms des objets (Pods, Services...) doivent être uniques *au sein d'un namespace*, mais pas *à travers* les namespaces.
Namespaces par Défaut
default: Là où tout va si vous ne spécifiez rien. (À éviter en prod).kube-system: (Protégé) Là où vit le Control Plane (api-server,etcd,coredns...).kube-public: (Rare) Ressources publiques.
Commandes kubectl (-n)
# 1. Créer un namespace $ kubectl create namespace production $ kubectl create namespace staging # 2. Lister les pods DANS un namespace (-n) $ kubectl get pods -n production # 3. Lister TOUS les pods (Tous les namespaces) $ kubectl get pods -A # 4. Changer votre contexte (namespace) ACTIF # (Pour éviter de taper '-n production' à chaque fois) $ kubectl config set-context --current --namespace=production
Problème : Les Pods (et leur système de fichiers) sont **éphémères**. Si un Pod de base de données (Postgres) plante et redémarre, toutes les données sont perdues.
Solution : Le Stockage Persistant (PV/PVC). C'est une abstraction pour attacher un "vrai" disque (ex: disque dur réseau, volume AWS EBS[cite: 18], GCE PD) à un Pod.
Diagramme (La Séparation des Rôles)
+-------------------------+
| Admin (Infra) |
| (Crée le disque (ex: EBS))|
| (Définit le "PV") |
| (Ex: "Disque-rapide-50Go")|
+-------------------------+
| (1. Met à disposition)
▼
+-------------------------+
| Cluster (Pool de PVs) |
+-------------------------+
| (3. K8s "lie" le PV au PVC)
▲
| (2. Demande/Claim)
+-------------------------+
| Dev (App) |
| (Définit le "PVC") |
| (Ex: "Besoin de 10Go") |
+-------------------------+
|
▼ (4. Pod utilise le PVC)
+-------------------------+
| Pod (ex: Postgres) |
+-------------------------+
Exemple (pvc.yaml - Le Développeur)
(Le "Claim" / La demande)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce # (1 Nœud lit/écrit)
resources:
requests:
storage: 10Gi # (Je demande 10Go)
# storageClassName: "fast-ssd"
Exemple (pod.yaml - Utilisation)
(Le Pod "monte" le PVC)
apiVersion: v1
kind: Pod
metadata:
name: postgres-pod
spec:
containers:
- name: postgres
image: postgres
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
# (Le Pod utilise le "Claim" (PVC))
persistentVolumeClaim:
claimName: postgres-pvc
Un DaemonSet est un "Contrôleur" (comme un Deployment) qui garantit qu'une copie d'un Pod tourne sur **CHAQUE** (ou un sous-ensemble) Nœud (Node) du cluster.
Pourquoi ? Pour les "agents" d'infrastructure qui *doivent* tourner sur la machine hôte elle-même.
Cas d'Usage (Les plus courants)
| Agent | Rôle |
|---|---|
DataDog Agent (ou Prometheus node-exporter) | (Monitoring) Doit tourner sur chaque Nœud pour lire les métriques/logs de ce Nœud. [cite: 22, 28] |
| Calico / Flannel (CNI) | (Réseau) L'agent CNI (Réseau) *doit* tourner sur chaque Nœud pour gérer le réseau des Pods. [cite: 18] |
| Falco / Trivy (Sécurité) | (Sécurité) L'agent de sécurité (ex: Falco) doit tourner sur chaque Nœud pour écouter les appels système (syscalls) du kernel. [cite: 22] |
Exemple YAML (daemonset-datadog.yaml)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: datadog-agent
namespace: datadog
spec:
selector:
matchLabels:
app: datadog-agent
template:
metadata:
labels:
app: datadog-agent
spec:
# (Tolère les "Taints" (marques)
# du Control Plane pour tourner aussi
# sur les Masters)
tolerations:
- operator: Exists
hostNetwork: true # (Utilise le réseau du Nœud)
containers:
- name: datadog-agent
image: gcr.io/datadoghq/agent:latest
env:
- name: DD_API_KEY
valueFrom:
secretKeyRef:
name: datadog-api-key
key: api-key
# ... (montage des volumes /proc, etc.)
