Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

☸️ Partie 3 : Objets Essentiels (Deep Dive)

Pods, Deployments, Services, Ingress, ConfigMaps, Secrets, Namespaces, PVs, DaemonSets.

3.1 Facile

1. Pod

L'"atome" de K8s. La plus petite unité déployable. (Enveloppe pour 1+ conteneurs).

Pod Container
3.2 Moyen

2. Deployment

Contrôleur (replicas, rollingUpdate). Gère l'état désiré des Pods.

Deployment ReplicaSet
3.3 Moyen

3. Service

IP/DNS interne stable pour un groupe de Pods (ClusterIP, NodePort, LoadBalancer).

Service ClusterIP
3.4 Avancé

4. Ingress

Routeur L7 (HTTP/S) externe. (host, path). (Nginx, Traefik).

Ingress HTTP
3.5 Facile

5. ConfigMap

Gère la configuration non-sensible (config.toml, ENV_VARS) (texte brut).

ConfigMap Config
3.6 Facile

6. Secret

Gère les données sensibles (API Keys, Passwords). (Stocké en Base64, non chiffré !).

Secret Base64
3.7 Facile

7. Namespace

Isolation logique (cluster virtuel). (default, kube-system, prod, dev).

Namespace Isolation
3.8 Avancé

8. PersistentVolume (PV/PVC)

Stockage persistant (Disque). (PV = L'Admin donne | PVC = Le Dev demande).

PV PVC Storage
3.9 Avancé

9. DaemonSet

Contrôleur qui garantit 1 Pod sur (presque) *chaque* Nœud. (Agents).

DaemonSet Agents
3.1 Objet : Le Pod (L'Atome)

Le 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
3.2 Objet : Le Deployment (L'État Désiré)

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
3.3 Objet : Le Service (Le Réseau Interne)

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
TypeDescription
ClusterIP (Défaut)Expose le Service sur une IP **interne** au cluster. Inaccessible de l'extérieur. (Pour la communication Backend <-> Backend).
NodePortExpose 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))
3.4 Objet : L'Ingress (Le Réseau Externe L7)

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
3.5 Objet : ConfigMap (Configuration)

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
3.6 Objet : Secret (Données Sensibles)

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)
3.7 Objet : Namespace (Isolation)

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
3.8 Objet : PersistentVolume (PV) & Claim (PVC)

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
3.9 Objet : DaemonSet (L'Agent)

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)
AgentRô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.)