🟩 Niveau 2 : Intermédiaire
Kubernetes Fundamentals pour Data Engineers
Bienvenue dans ce module où tu vas apprendre à orchestrer des containers à grande échelle avec Kubernetes. Tu découvriras comment déployer, gérer et monitorer des applications data dans un cluster K8s — des compétences essentielles pour industrialiser tes pipelines !
Prérequis
| Niveau | Compétence |
|---|---|
| ✅ Requis | Avoir suivi le module 14_docker_for_data_engineers |
| ✅ Requis | Maîtriser les bases de Docker (images, containers, volumes) |
| ✅ Requis | Connaissances de base en YAML |
| 💡 Recommandé | Docker Desktop ou Minikube installé |
🎯 Objectifs du module
À la fin de ce module, tu seras capable de :
- Comprendre l'architecture et les concepts clés de Kubernetes
- Installer et configurer un cluster Kubernetes local
- Déployer et gérer des applications avec
kubectl - Utiliser les Jobs et CronJobs pour des tâches Data Engineering
- Configurer le stockage persistant (PVC)
- Débugger des pods et diagnostiquer les erreurs courantes
- Déployer et orchestrer un pipeline ETL complet dans Kubernetes
C'est quoi Kubernetes ?
☸️ Kubernetes (K8s) est une plateforme open-source d'orchestration de containers qui automatise le déploiement, la mise à l'échelle et la gestion d'applications containerisées.
En pratique, Kubernetes te permet de :
- Déployer des containers sur plusieurs machines (un cluster)
- Scaler automatiquement selon la charge
- Redémarrer les containers qui plantent
- Gérer la configuration et les secrets
- Exposer tes applications au réseau
Kubernetes ≠ Docker (mais complémentaires)
| Aspect | Docker | Kubernetes |
|---|---|---|
| Rôle | Créer et exécuter des containers | Orchestrer des containers |
| Échelle | 1 machine | Cluster de machines |
| Focus | Build & Run | Deploy, Scale, Manage |
| Analogie | Le musicien | Le chef d'orchestre |
💡 Docker crée les containers, Kubernetes les orchestre à grande échelle.
Analogies pour bien comprendre
| Analogie | Explication |
|---|---|
| Chef d'orchestre | K8s coordonne tous les containers (musiciens) pour qu'ils jouent en harmonie |
| Agence immobilière | K8s place tes containers (locataires) dans les nodes (appartements) disponibles |
| Pilote automatique | Tu définis la destination (état souhaité), K8s s'occupe d'y arriver et d'y rester |
| 🏭 Usine automatisée | Tu donnes les plans, K8s fabrique, surveille et remplace les pièces défectueuses |
ℹ️ Le savais-tu ?
Le nom Kubernetes vient du grec κυβερνήτης (kubernḗtēs) qui signifie "pilote" ou "gouverneur" — celui qui tient la barre d'un navire.
K8s est né chez Google, inspiré de leur système interne Borg qui gère des milliards de containers depuis 2003. Google a ouvert le projet en 2014 et l'a donné à la Cloud Native Computing Foundation (CNCF).
Le "8" dans K8s représente les 8 lettres entre le K et le S de Kubernetes !
1. Pourquoi Kubernetes pour un Data Engineer ?
En Data Engineering moderne, tu dois souvent :
- Exécuter des jobs ETL de manière fiable et planifiée
- Déployer des bases de données et des brokers (Kafka)
- Faire tourner des jobs Spark distribués
- Orchestrer avec Airflow ou Prefect
- Scaler selon le volume de données
❌ Sans Kubernetes
| Problème | Conséquence |
|---|---|
| Déploiement manuel sur chaque serveur | Lent et source d'erreurs |
| Pas de redémarrage automatique | Jobs perdus en cas de crash |
| Scaling manuel | Sous/sur-utilisation des ressources |
| Configuration dispersée | Difficile à maintenir |
✅ Avec Kubernetes
| Avantage | Exemple concret |
|---|---|
| Déploiement déclaratif | kubectl apply -f etl-job.yaml |
| Auto-healing | Pod crashé = recréé automatiquement |
| CronJobs natifs | ETL planifié sans cron externe |
| Secrets management | Credentials gérés proprement |
| Portabilité | Même config en dev, staging, prod |
Vue d'ensemble : Cluster K8s
┌─────────────────────────────────────────────────────┐
│ CLUSTER K8s │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ NODE 1 │ │ NODE 2 │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │ Pod A │ │ │ │ Pod C │ │ │
│ │ └───────┘ │ │ └───────┘ │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │ Pod B │ │ │ │ Pod D │ │ │
│ │ └───────┘ │ │ └───────┘ │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
2. Architecture Kubernetes (simplifiée)
Un cluster Kubernetes est composé de deux types de machines :
| Composant | Rôle | Analogie |
|---|---|---|
| Control Plane (Master) | Cerveau du cluster, prend les décisions | Le management |
| Worker Nodes | Exécutent les containers | Les ouvriers |
Control Plane (les composants essentiels)
| Composant | Rôle |
|---|---|
| API Server | Point d'entrée unique (kubectl → API) |
| Scheduler | Décide sur quel node placer les pods |
💡 Pour ce module "Fundamentals", on se concentre sur ces 2 composants. Les autres (etcd, Controller Manager) seront vus en niveau avancé.
Worker Nodes
| Composant | Rôle |
|---|---|
| Kubelet | Agent qui gère les pods sur le node |
| Container Runtime | Docker/containerd qui exécute les containers |
Schéma Architecture
┌─────────────────────────────────────────┐
│ CONTROL PLANE │
│ ┌────────────────┐ ┌───────────────┐ │
kubectl ─────────▶│ │ API Server │ │ Scheduler │ │
│ └───────┬────────┘ └───────────────┘ │
└──────────┼─────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ WORKER NODE 1 │ │ WORKER NODE 2 │ │ WORKER NODE 3 │
│ ┌───────────────┐ │ │ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ Kubelet │ │ │ │ Kubelet │ │ │ │ Kubelet │ │
│ └───────────────┘ │ │ └───────────────┘ │ │ └───────────────┘ │
│ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ │
│ │ Pod │ │ Pod │ │ │ │ Pod │ │ Pod │ │ │ │ Pod │ │
│ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │ │ └─────┘ │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
3. Concepts clés Kubernetes
Voici les concepts fondamentaux à maîtriser :
| Concept | Description | Analogie |
|---|---|---|
| Pod | Plus petite unité déployable (1+ containers) | Un appartement |
| Deployment | Gère les replicas de pods, rolling updates | Un syndic d'immeuble |
| Service | Expose les pods sur le réseau | L'adresse postale |
| Namespace | Isolation logique des ressources | Un quartier de la ville |
| ConfigMap | Configuration externe (non sensible) | Fichier .env public |
| Secret | Données sensibles (base64) | Un coffre-fort |
| PersistentVolumeClaim | Demande de stockage persistant | Location d'un disque dur |
| Job | Tâche one-shot (s'exécute puis se termine) | Une mission ponctuelle |
| CronJob | Job planifié (comme cron Linux) | Un réveil programmé |
Pod
Le Pod est l'unité de base dans Kubernetes :
- Contient 1 ou plusieurs containers qui partagent le réseau et le stockage
- A sa propre adresse IP dans le cluster
- Est éphémère : peut être détruit et recréé à tout moment
apiVersion: v1
kind: Pod
metadata:
name: mon-pod
spec:
containers:
- name: mon-container
image: python:3.11-slim
Deployment
Le Deployment gère un ensemble de pods identiques :
- Garantit le nombre de replicas souhaité
- Gère les rolling updates (mise à jour sans downtime)
- Recrée les pods qui crashent
Service
Le Service expose les pods sur le réseau :
| Type | Description | Usage |
|---|---|---|
| ClusterIP | IP interne au cluster | Communication entre pods |
| NodePort | Port ouvert sur chaque node | Tests, développement |
| LoadBalancer | Load balancer cloud | Production (AWS, GCP, Azure) |
Secret vs ConfigMap
| Aspect | ConfigMap | Secret |
|---|---|---|
| Usage | Config non sensible | Passwords, tokens, clés |
| Encodage | Texte clair | Base64 |
| Exemple | URLs, feature flags | DB_PASSWORD, API_KEY |
⚠️ Attention : Base64 n'est PAS du chiffrement !
Base64 est juste un encodage, pas une protection. N'importe qui avec accès au cluster peut décoder les secrets. En production, utilise Sealed Secrets, HashiCorp Vault, ou les secrets managers cloud (AWS Secrets Manager, GCP Secret Manager).
Schéma : Relations entre concepts
┌──────────────┐
User ───────▶│ Service │
└──────┬───────┘
│
▼
┌──────────────┐
│ Deployment │
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ Pod 1 │ │ Pod 2 │ │ Pod 3 │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
▼ ▼ ▼
Container Container Container
4. Installation de Kubernetes
Pour ce module, tu as besoin d'un cluster Kubernetes local.
| Option | Difficulté | Recommandé pour |
|---|---|---|
| Docker Desktop | ⭐ Facile | Mac/Windows, débutants |
| Minikube | ⭐⭐ Moyen | Tous OS, plus de contrôle |
| k3d/k3s | ⭐⭐ Moyen | Léger, CI/CD |
Option 1 : Docker Desktop (recommandé)
- Ouvrir Docker Desktop
- Aller dans Settings → Kubernetes
- Cocher Enable Kubernetes
- Cliquer Apply & Restart
- Attendre que le status passe au vert
Option 2 : Minikube
# Installation (macOS avec Homebrew)
brew install minikube
# Installation (Linux)
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Démarrer le cluster
minikube start
# Vérifier
minikube status
Installer kubectl
kubectl est l'outil CLI pour interagir avec Kubernetes.
# macOS
brew install kubectl
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl
# Windows (avec Docker Desktop, kubectl est inclus)
Vérification
# Version de kubectl
kubectl version --client
# Liste des nodes du cluster
kubectl get nodes
# Vérifier le contexte actif
kubectl config get-contexts
bashVoir le code
# Vérifier l'installation de kubectl
echo "=== Version kubectl ==="
kubectl version --client
echo ""
echo "=== Nodes du cluster ==="
kubectl get nodes
echo ""
echo "=== Contexte actif ==="
kubectl config current-context5. Maîtriser YAML pour Kubernetes
Maintenant que Kubernetes est installé, apprenons à écrire les fichiers de configuration YAML.
5.1 C'est quoi YAML et pourquoi Kubernetes l'utilise ?
YAML = "YAML Ain't Markup Language"
YAML est un format de fichier pour écrire de la configuration de manière lisible par un humain.
Pourquoi YAML plutôt que JSON ou XML ?
| Format | Lisibilité | Exemple |
|---|---|---|
| XML | ❌ Verbeux | <name>postgres</name> |
| JSON | ⚠️ Moyen | {"name": "postgres"} |
| YAML | ✅ Simple | name: postgres |
Kubernetes a choisi YAML parce que :
- Facile à lire : pas de
{}ni de""partout - Facile à écrire : structure claire avec l'indentation
- Commentaires possibles :
# ceci est un commentaire - Multi-documents : plusieurs ressources dans un seul fichier avec
---
Le principe fondamental de Kubernetes
Kubernetes fonctionne sur le principe déclaratif :
┌─────────────────────────────────────────────────────────────┐
│ │
│ TOI (humain) → KUBERNETES (robot) │
│ │
│ "Je VEUX 3 pods → "OK, je CRÉE 3 pods et │
│ avec nginx" je m'assure qu'il y en │
│ a TOUJOURS 3" │
│ │
└─────────────────────────────────────────────────────────────┘
Tu décris l'état souhaité dans un fichier YAML, Kubernetes s'occupe d'y arriver.
5.2 Les règles d'or du YAML
5 règles à ne JAMAIS oublier
| # | Règle | ✅ Correct | ❌ Incorrect |
|---|---|---|---|
| 1 | Indentation = 2 espaces | name: test |
⇥name: test (tab) |
| 2 | Espace après les : |
name: postgres |
name:postgres |
| 3 | Sensible à la casse | apiVersion |
ApiVersion |
| 4 | Tiret - pour les listes |
- item |
item |
| 5 | Pas de tabs, jamais ! | espaces uniquement | tabs = erreur |
Les types de données
# ══════════════════════════════════════════════════════════════
# STRINGS (texte)
# ══════════════════════════════════════════════════════════════
nom: postgres # Sans guillemets (recommandé)
nom: "postgres" # Avec guillemets (si caractères spéciaux)
message: "Bonjour: monde" # Guillemets obligatoires si : dans la valeur
# ══════════════════════════════════════════════════════════════
# NOMBRES
# ══════════════════════════════════════════════════════════════
port: 5432 # Entier
prix: 19.99 # Décimal
replicas: 3 # Entier
# ══════════════════════════════════════════════════════════════
# BOOLÉENS (vrai/faux)
# ══════════════════════════════════════════════════════════════
actif: true # ou false
debug: yes # ou no (équivalent)
# ══════════════════════════════════════════════════════════════
# NULL (vide)
# ══════════════════════════════════════════════════════════════
valeur: null # ou ~
Les structures de données
# ══════════════════════════════════════════════════════════════
# DICTIONNAIRE = ensemble de clé: valeur
# ══════════════════════════════════════════════════════════════
personne: # <- clé parente
nom: Alice # <- 2 espaces = enfant de "personne"
age: 30 # <- même niveau que "nom"
ville: Paris # <- même niveau
# ══════════════════════════════════════════════════════════════
# LISTE = ensemble d'éléments (avec tiret -)
# ══════════════════════════════════════════════════════════════
fruits: # <- clé parente
- pomme # <- premier élément (tiret + espace)
- banane # <- deuxième élément
- orange # <- troisième élément
# ══════════════════════════════════════════════════════════════
# LISTE DE DICTIONNAIRES (très courant en K8s !)
# ══════════════════════════════════════════════════════════════
employes:
- nom: Alice # <- premier employé
poste: Dev # propriété du premier employé
age: 30 # propriété du premier employé
- nom: Bob # <- deuxième employé (nouveau tiret)
poste: DevOps # propriété du deuxième employé
age: 25
💡 Astuce K8s :
containers:est TOUJOURS une liste (même avec 1 seul container), donc toujours avec-
5.3 Pourquoi plusieurs types de ressources ?
Avant d'écrire du YAML, il faut comprendre POURQUOI Kubernetes a différentes ressources.
Le problème à résoudre
Imaginons que tu veux déployer une API Python :
| Besoin | Question | Ressource K8s |
|---|---|---|
| Isoler mes ressources | "Comment séparer dev/prod ?" | Namespace |
| Exécuter mon code | "Où tourne mon container ?" | Pod |
| Avoir plusieurs instances | "Et si un pod crashe ?" | Deployment |
| Accéder depuis le réseau | "Comment appeler mon API ?" | Service |
| Stocker la config | "Où mettre mes variables ?" | ConfigMap |
| Stocker les secrets | "Où mettre mes passwords ?" | Secret |
| Planifier un job | "Comment lancer mon ETL à 2h ?" | CronJob |
Schéma : Qui fait quoi ?
┌─────────────────────────────────────────────────────────────────────────┐
│ POURQUOI CHAQUE RESSOURCE ? │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 👤 Utilisateur │
│ │ │
│ │ "Je veux accéder à mon-api sur le port 80" │
│ ▼ │
│ ┌─────────────────┐ │
│ │ SERVICE │ ← Donne une adresse stable (DNS + IP) │
│ │ "mon-api" │ ← Load balance entre les pods │
│ │ port: 80 │ │
│ └────────┬────────┘ │
│ │ │
│ │ "Envoie le trafic aux pods avec label app=mon-api" │
│ ▼ │
│ ┌─────────────────┐ │
│ │ DEPLOYMENT │ ← Garantit 3 replicas │
│ │ replicas: 3 │ ← Recrée les pods qui crashent │
│ │ │ ← Gère les mises à jour (rolling update) │
│ └────────┬────────┘ │
│ │ │
│ │ "Crée et maintient 3 pods identiques" │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ POD 1 │ │ POD 2 │ │ POD 3 │ ← Chaque pod a son IP │
│ │ (nginx) │ │ (nginx) │ │ (nginx) │ ← Chaque pod peut mourir │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Pourquoi un Namespace ?
Un Namespace est un espace isolé dans le cluster, comme des dossiers sur ton ordinateur.
┌─────────────────────────────────────────────────────────────────────────┐
│ CLUSTER K8S │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ NAMESPACE: dev │ │ NAMESPACE: prod │ │
│ │ ┌───────┐ ┌───────┐│ │ ┌───────┐ ┌───────┐│ │
│ │ │mon-api│ │ db ││ │ │mon-api│ │ db ││ │
│ │ └───────┘ └───────┘│ │ └───────┘ └───────┘│ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ NAMESPACE: staging │ │ NAMESPACE: data-eng │ │
│ │ ┌───────┐ │ │ ┌───────┐ ┌───────┐│ │
│ │ │mon-api│ │ │ │airflow│ │ spark ││ │
│ │ └───────┘ │ │ └───────┘ └───────┘│ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Pourquoi utiliser des Namespaces ?
| Avantage | Exemple |
|---|---|
| Isolation | Les ressources de dev ne voient pas celles de prod |
| Organisation | Séparer par équipe, par environnement, par projet |
| Quotas | Limiter les ressources CPU/RAM par namespace |
| Permissions | Donner accès à dev mais pas à prod |
Namespaces par défaut :
| Namespace | Usage |
|---|---|
default |
Où vont les ressources si tu ne précises pas |
kube-system |
Composants internes de K8s (ne pas toucher !) |
kube-public |
Ressources accessibles à tous |
# Lister les namespaces
kubectl get namespaces
# Créer un namespace
kubectl create namespace mon-projet
# Travailler dans un namespace spécifique
kubectl get pods -n mon-projet
kubectl apply -f manifest.yaml -n mon-projet
# Changer le namespace par défaut
kubectl config set-context --current --namespace=mon-projet
Pourquoi un Service EST indispensable ?
Sans Service :
Pod 1 : IP = 10.1.0.15 ← Cette IP change à chaque recréation du pod !
Pod 2 : IP = 10.1.0.23 ← Comment savoir quelle IP appeler ?
Pod 3 : IP = 10.1.0.41 ← Impossible de hardcoder ces IPs
Avec Service :
Service "mon-api" : IP stable = 10.0.0.100 (ne change jamais)
DNS = mon-api.default.svc.cluster.local
→ Redirige automatiquement vers les pods vivants
Pourquoi un Deployment et pas juste des Pods ?
Sans Deployment (pods manuels) :
- Pod crashe → Personne ne le recrée → Service DOWN
- Mise à jour → Supprimer/recréer manuellement → Downtime
- Scaling → Créer chaque pod à la main → Lent
Avec Deployment :
- Pod crashe → Deployment le recrée automatiquement → ✅
- Mise à jour → Rolling update sans downtime → ✅
- Scaling → kubectl scale deployment mon-api --replicas=10 → ✅
5.4 Structure d'un manifest YAML
La structure de base (4 champs obligatoires)
Tout manifest Kubernetes a cette structure :
apiVersion: ??? # 1. Quelle version de l'API ?
kind: ??? # 2. Quel type de ressource ?
metadata: # 3. Comment s'appelle-t-elle ?
name: ???
spec: # 4. Qu'est-ce que tu veux ?
???
Trouver apiVersion et kind
| Je veux créer... | kind | apiVersion |
|---|---|---|
| Un espace isolé | Namespace |
v1 |
| Un container simple | Pod |
v1 |
| Plusieurs replicas d'un pod | Deployment |
apps/v1 |
| Un point d'accès réseau | Service |
v1 |
| De la configuration | ConfigMap |
v1 |
| Des données secrètes | Secret |
v1 |
| Un job one-shot | Job |
batch/v1 |
| Un job planifié | CronJob |
batch/v1 |
💡 Astuce :
kubectl api-resourceste donne la liste complète !
5.5 Écrire chaque ressource (ligne par ligne)
Namespace (créer un espace isolé)
# namespace.yaml
# ─────────────────────────────────────────────────────────────
apiVersion: v1 # API de base
kind: Namespace # Type = Namespace
# ─────────────────────────────────────────────────────────────
metadata:
name: data-pipeline # Nom du namespace
labels: # Labels optionnels
team: data-engineering
env: production
kubectl apply -f namespace.yaml
kubectl get namespaces
Pod (le plus simple)
# pod.yaml
# ─────────────────────────────────────────────────────────────
apiVersion: v1 # Pods utilisent l'API v1
kind: Pod # Je veux créer un Pod
# ─────────────────────────────────────────────────────────────
metadata:
name: mon-premier-pod # Nom du pod (unique dans le namespace)
namespace: data-pipeline # Dans quel namespace ?
labels: # Labels = tags pour organiser
app: demo # Label arbitraire (clé: valeur)
# ─────────────────────────────────────────────────────────────
spec:
containers: # Liste des containers (avec tiret !)
- name: nginx # Nom du container
image: nginx:alpine # Image Docker à utiliser
ports:
- containerPort: 80 # Port exposé par le container
Deployment (le standard en production)
# deployment.yaml
# ─────────────────────────────────────────────────────────────
apiVersion: apps/v1 # Deployments utilisent apps/v1
kind: Deployment # Type = Deployment
# ─────────────────────────────────────────────────────────────
metadata:
name: mon-api # Nom du deployment
namespace: data-pipeline # Dans quel namespace ?
# ─────────────────────────────────────────────────────────────
spec:
replicas: 3 # Je veux 3 pods identiques
selector: # Comment identifier MES pods ?
matchLabels:
app: mon-api # Cherche les pods avec ce label
template: # Modèle pour créer les pods
metadata:
labels:
app: mon-api # ⚠️ DOIT correspondre au selector !
spec:
containers:
- name: api
image: nginx:alpine
ports:
- containerPort: 80
resources: # Limites de ressources
requests: # Minimum garanti
memory: "64Mi"
cpu: "50m"
limits: # Maximum autorisé
memory: "128Mi"
cpu: "100m"
⚠️ La règle CRITIQUE du selector :
spec.selector.matchLabels.app ═══════╗
║ DOIVENT ÊTRE IDENTIQUES !
spec.template.metadata.labels.app ════╝
Si différents → Erreur : "selector does not match template labels"
Service (exposer sur le réseau)
# service.yaml
# ─────────────────────────────────────────────────────────────
apiVersion: v1 # Services utilisent l'API v1
kind: Service # Type = Service
# ─────────────────────────────────────────────────────────────
metadata:
name: mon-api-svc # Nom = nom DNS dans le cluster
namespace: data-pipeline
# ─────────────────────────────────────────────────────────────
spec:
selector:
app: mon-api # Cible les pods avec ce label
type: ClusterIP # ClusterIP | NodePort | LoadBalancer
ports:
- port: 80 # Port du SERVICE (ce qu'on appelle)
targetPort: 80 # Port du CONTAINER (où ça arrive)
Mapping des ports :
Client → Service:80 → Pod:80 → Container
curl http://mon-api-svc:80 → redirigé vers → container:80
ConfigMap (configuration non sensible)
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: data-pipeline
data: # Clé: valeur (toujours strings)
LOG_LEVEL: "info"
DB_HOST: "postgres.data-pipeline.svc.cluster.local"
DB_PORT: "5432"
Secret (données sensibles)
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: data-pipeline
type: Opaque
stringData: # K8s encode automatiquement en base64
DB_PASSWORD: "SuperSecret123"
API_KEY: "sk-xxxx-yyyy-zzzz"
⚠️ Rappel : Base64 n'est PAS du chiffrement ! C'est juste un encodage.
5.6 Exemple complet : Déployer une app de A à Z
Voici comment déployer une application complète :
# 1. Créer le namespace
kubectl create namespace demo-app
# 2. Appliquer tous les manifests
kubectl apply -f namespace.yaml
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# OU tout d'un coup (si dans un dossier)
kubectl apply -f ./manifests/
# 3. Vérifier
kubectl get all -n demo-app
# 4. Tester l'accès
kubectl port-forward svc/mon-api-svc 8080:80 -n demo-app
# Ouvrir http://localhost:8080
5.7 Outils pour t'aider
Générer un template automatiquement
# Ne jamais écrire from scratch ! Génère un template :
kubectl create namespace mon-ns --dry-run=client -o yaml > namespace.yaml
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deployment.yaml
kubectl create service clusterip nginx --tcp=80:80 --dry-run=client -o yaml > service.yaml
Valider avant d'appliquer
# Vérifier la syntaxe sans créer
kubectl apply -f manifest.yaml --dry-run=client
# Valider côté serveur (plus strict)
kubectl apply -f manifest.yaml --dry-run=server
Explorer avec kubectl explain
kubectl explain pod
kubectl explain pod.spec.containers
kubectl explain deployment.spec.template.spec
5.8 Checklist avant kubectl apply
CHECKLIST YAML KUBERNETES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ ] Indentation = 2 ESPACES (pas de tabs !)
[ ] Espace après chaque ":"
[ ] apiVersion correct pour le kind
[ ] metadata.name présent et unique
[ ] namespace spécifié (si pas default)
[ ] Pour Deployment : selector.matchLabels = template.labels
[ ] containers: avec un tiret (c'est une liste !)
[ ] Validé avec --dry-run=client
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6. Commandes kubectl essentielles
Voici les commandes que tu utiliseras au quotidien :
CRUD sur les ressources
| Action | Commande |
|---|---|
| Créer/Mettre à jour | kubectl apply -f manifest.yaml |
| Lire | kubectl get pods |
| Détails | kubectl describe pod <name> |
| Supprimer | kubectl delete -f manifest.yaml |
Lister les ressources
kubectl get pods # Lister les pods
kubectl get pods -o wide # Avec plus de détails (node, IP)
kubectl get deployments # Lister les deployments
kubectl get services # Lister les services
kubectl get all # Tout lister
kubectl get all -n <namespace> # Dans un namespace spécifique
Debug
kubectl logs <pod> # Voir les logs
kubectl logs -f <pod> # Suivre les logs en temps réel
kubectl logs -p <pod> # Logs du container précédent (après crash)
kubectl describe pod <pod> # Détails + Events
kubectl exec -it <pod> -- bash # Shell dans le pod
kubectl get events # Tous les events du namespace
Réseau
kubectl port-forward <pod> 8080:80 # Forward un port
kubectl port-forward svc/<service> 8080:80 # Forward depuis un service
Namespaces
kubectl get namespaces # Lister les namespaces
kubectl create namespace dev # Créer un namespace
kubectl config set-context --current --namespace=dev # Changer de namespace par défaut
Cheat Sheet visuel
┌─────────────────────────────────────────────────────────────────┐
│ KUBECTL CHEAT SHEET │
├─────────────────────────────────────────────────────────────────┤
│ CREATE/UPDATE │ kubectl apply -f manifest.yaml │
│ READ │ kubectl get pods/deploy/svc/all │
│ DESCRIBE │ kubectl describe <resource> <name> │
│ DELETE │ kubectl delete -f manifest.yaml │
├─────────────────────────────────────────────────────────────────┤
│ LOGS │ kubectl logs -f <pod> │
│ SHELL │ kubectl exec -it <pod> -- bash │
│ PORT-FORWARD │ kubectl port-forward <pod> 8080:80 │
│ EVENTS │ kubectl get events --sort-by='.lastTimestamp'│
└─────────────────────────────────────────────────────────────────┘
bashVoir le code
# Exemples de commandes kubectl
echo "=== Namespaces ==="
kubectl get namespaces
echo ""
echo "=== Pods dans tous les namespaces ==="
kubectl get pods --all-namespaces
echo ""
echo "=== Services ==="
kubectl get services7. Premier déploiement : Hello World
Déployons une application simple pour comprendre le cycle de déploiement.
Manifest YAML : Deployment + Service
# hello-world.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
labels:
app: hello-world
spec:
replicas: 2
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-world-svc
spec:
selector:
app: hello-world
ports:
- port: 80
targetPort: 80
type: NodePort
Déployer
# Appliquer le manifest
kubectl apply -f hello-world.yaml
# Vérifier le déploiement
kubectl get deployments
kubectl get pods
kubectl get services
Accéder à l'application
Méthode 1 : Port-forward (recommandé)
kubectl port-forward svc/hello-world-svc 8080:80
# Ouvrir http://localhost:8080
Méthode 2 : NodePort
# Trouver le NodePort
kubectl get svc hello-world-svc
# Accéder via http://localhost:<NodePort>
💡 Astuce entreprise : Le NodePort n'est pas toujours autorisé en entreprise (firewall).
port-forwardfonctionne partout !
Cycle de déploiement
kubectl apply Deployment créé ReplicaSet créé Pods créés
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌───────────┐ ┌─────────┐
│ YAML │ ───▶ │Deployment │ ───▶ │ReplicaSet │ ───▶ │ Pods │
└─────────┘ └───────────┘ └───────────┘ └─────────┘
│
Service expose les pods ◀──────────────────┘
Nettoyage
kubectl delete -f hello-world.yaml
8. Kubernetes pour Data Engineering
Voici les ressources Kubernetes particulièrement utiles pour le Data Engineering.
7.1 Job : tâche one-shot
Un Job exécute un ou plusieurs pods jusqu'à complétion :
- ETL ponctuel
- Migration de données
- Backfill
# etl-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: etl-job
spec:
template:
spec:
containers:
- name: etl
image: python:3.11-slim
command: ["python", "-c", "print('ETL terminé !')"]
restartPolicy: Never
backoffLimit: 3 # Nombre de retries en cas d'échec
# Lancer le job
kubectl apply -f etl-job.yaml
# Voir le status
kubectl get jobs
# Voir les logs
kubectl logs job/etl-job
7.2 CronJob : tâche planifiée
Un CronJob lance des Jobs selon un schedule (comme cron Linux) :
- ETL quotidien
- Nettoyage de données
- Rapports automatisés
# etl-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: etl-daily
spec:
schedule: "0 2 * * *" # Tous les jours à 2h du matin
jobTemplate:
spec:
template:
spec:
containers:
- name: etl
image: my-etl-image:1.0
env:
- name: DB_HOST
value: "postgres"
restartPolicy: OnFailure
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
Schedule cron :
| Expression | Signification |
|---|---|
0 2 * * * |
Tous les jours à 2h00 |
*/15 * * * * |
Toutes les 15 minutes |
0 0 * * 0 |
Tous les dimanches à minuit |
0 8 1 * * |
Le 1er de chaque mois à 8h |
# Lister les cronjobs
kubectl get cronjobs
# Déclencher manuellement
kubectl create job --from=cronjob/etl-daily test-etl
7.3 Déployer PostgreSQL dans K8s
# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "de_user"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: POSTGRES_DB
value: "de_db"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
⚠️ Note production : Les bases de données dans K8s nécessitent toujours un stockage persistant (PVC) (A voir plus en bas). En production, on préfère souvent des services managés (Cloud SQL, RDS, Azure Database) pour la haute disponibilité et les backups automatiques.
Schéma Pipeline Data Engineering sur K8s
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ CronJob │────▶│ Job │────▶│ Pod │
│ (0 2 * * *) │ │ │ │ (etl.py) │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌───────────────────────┤
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ PVC │ │ PostgreSQL │
│ (input data) │ │ (output) │
└──────────────┘ └──────────────┘
9. Stockage : Volumes & PVC
Les données ne doivent jamais vivre uniquement dans un Pod (éphémère).
Concepts clés
| Concept | Description |
|---|---|
| PersistentVolume (PV) | Ressource de stockage provisionnée par l'admin |
| PersistentVolumeClaim (PVC) | Demande de stockage par l'utilisateur |
| StorageClass | Type de stockage (SSD, HDD, cloud...) |
Schéma : Comment ça marche
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐
│ Pod │────▶│ PVC │────▶│ PV │────▶│ Stockage réel │
│ │ │ (claim) │ │ (provision) │ │ (disk, NFS, EBS) │
└─────────┘ └─────────┘ └─────────────┘ └──────────────────┘
Exemple : PVC pour PostgreSQL
# postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Access Modes :
| Mode | Description |
|---|---|
ReadWriteOnce |
Lecture/écriture par un seul node |
ReadOnlyMany |
Lecture seule par plusieurs nodes |
ReadWriteMany |
Lecture/écriture par plusieurs nodes |
Utilisation dans un Deployment
spec:
containers:
- name: postgres
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
# Vérifier les PVC
kubectl get pvc
# Vérifier les PV
kubectl get pv
9. Configuration : ConfigMaps & Secrets
ConfigMap : configuration non sensible
# config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: etl-config
data:
DB_HOST: "postgres"
DB_PORT: "5432"
LOG_LEVEL: "INFO"
Secret : données sensibles
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
data:
password: ZGVfcGFzc3dvcmQ= # base64 de "de_password"
Créer un secret en ligne de commande :
# Créer un secret
kubectl create secret generic postgres-secret \
--from-literal=password=de_password
# Encoder en base64
echo -n "de_password" | base64
# Décoder
echo "ZGVfcGFzc3dvcmQ=" | base64 -d
Utilisation dans un Pod
spec:
containers:
- name: etl
env:
# Depuis ConfigMap
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: etl-config
key: DB_HOST
# Depuis Secret
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
10. Debug & Monitoring
Commandes de debug essentielles
# Logs
kubectl logs <pod> # Logs actuels
kubectl logs -f <pod> # Suivre en temps réel
kubectl logs -p <pod> # Logs du container précédent (après crash)
kubectl logs <pod> -c <container> # Logs d'un container spécifique
# Détails et Events
kubectl describe pod <pod> # Détails complets + Events
kubectl get events --sort-by='.lastTimestamp' # Events triés par date
# Shell dans le pod
kubectl exec -it <pod> -- bash # Ouvrir un shell
kubectl exec -it <pod> -- sh # Pour images Alpine
# Ressources
kubectl top pods # CPU/RAM des pods (si metrics-server)
kubectl top nodes # CPU/RAM des nodes
Erreurs classiques et solutions
| Erreur | Cause probable | Solution |
|---|---|---|
CrashLoopBackOff |
L'app plante au démarrage | kubectl logs <pod> pour voir l'erreur |
ImagePullBackOff |
Image introuvable ou accès refusé | Vérifier nom/tag, credentials registry |
Pending |
Pas de ressources disponibles | kubectl describe pod → voir Events |
OOMKilled |
Mémoire insuffisante | Augmenter resources.limits.memory |
CreateContainerConfigError |
ConfigMap/Secret manquant | Vérifier que les refs existent |
ErrImageNeverPull |
Image locale introuvable | imagePullPolicy: IfNotPresent |
Processus de debug
1. kubectl get pods → Voir le status
│
▼
2. kubectl describe pod <pod> → Voir les Events
│
▼
3. kubectl logs <pod> → Voir les logs de l'app
│
▼
4. kubectl exec -it <pod> -- sh → Investiguer dans le container
11. Erreurs fréquentes & Bonnes pratiques
❌ Erreurs fréquentes des débutants
| Erreur | Conséquence |
|---|---|
Pas de resources.requests/limits |
Pods non schedulés ou OOMKilled |
| Secrets dans ConfigMap | Fuite de credentials |
| Base de données sans PVC | Perte de données au restart |
Tout dans namespace default |
Difficile à gérer, pas d'isolation |
Images en :latest |
Comportement non reproductible |
| Pas de labels | Impossible de filtrer/organiser |
✅ Bonnes pratiques
| Pratique | Pourquoi |
|---|---|
| Toujours définir requests/limits | Scheduling prévisible, protection contre OOM |
| Un namespace par projet/env | Isolation, quotas, RBAC |
| Tags d'image versionnés | image:1.0.0 au lieu de :latest |
| Labels systématiques | Organisation et filtrage |
| Healthchecks (probes) | K8s sait si l'app est prête/vivante |
| PVC pour toute donnée importante | Persistance garantie |
Exemple de labels recommandés
metadata:
name: etl-job
labels:
app: etl-pipeline
component: etl
env: dev
team: data-engineering
version: "1.0.0"
Exemple de resources requests/limits
spec:
containers:
- name: etl
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
| Ressource | Unité | Exemple |
|---|---|---|
| CPU | millicores | 500m = 0.5 CPU |
| Memory | Mi/Gi | 256Mi, 1Gi |
Quiz de fin de module
Réponds aux questions suivantes pour vérifier tes acquis.
❓ Q1. Quelle est la différence entre un Pod et un Deployment ?
a) Un Pod contient plusieurs Deployments
b) Un Deployment gère des replicas de Pods et leur cycle de vie
c) C'est la même chose
d) Un Pod est pour la production, un Deployment pour le dev
💡 Voir la réponse
✅ Réponse : b — Un Pod est l'unité de base (éphémère), un Deployment gère plusieurs replicas de Pods, les rolling updates, et recrée les Pods qui crashent.
❓ Q2. Pourquoi utiliser un PersistentVolumeClaim (PVC) ?
a) Pour augmenter la vitesse du CPU
b) Pour persister les données même si le Pod est supprimé
c) Pour exposer un Pod sur internet
d) Pour chiffrer les secrets
💡 Voir la réponse
✅ Réponse : b — Les Pods sont éphémères. Sans PVC, les données sont perdues quand le Pod est supprimé ou recréé.
❓ Q3. Quelle commande permet de voir les logs d'un Pod qui a crashé ?
a) kubectl logs <pod>
b) kubectl logs -p <pod>
c) kubectl describe pod <pod>
d) kubectl get logs <pod>
💡 Voir la réponse
✅ Réponse : b — L'option -p (previous) affiche les logs du container précédent, utile après un crash.
❓ Q4. L'encodage Base64 des Secrets Kubernetes est-il sécurisé ?
a) Oui, c'est du chiffrement fort
b) Non, Base64 est juste un encodage, pas du chiffrement
c) Oui, si on utilise une clé de 256 bits
d) Ça dépend de la version de Kubernetes
💡 Voir la réponse
✅ Réponse : b — Base64 est un simple encodage réversible. N'importe qui avec accès au cluster peut décoder les secrets. En production, utilisez Sealed Secrets ou un secrets manager.
❓ Q5. Quelle ressource utiliser pour exécuter un ETL tous les jours à 2h du matin ?
a) Deployment
b) Job
c) CronJob
d) DaemonSet
💡 Voir la réponse
✅ Réponse : c — Un CronJob permet de planifier des Jobs selon une expression cron (0 2 * * *).
❓ Q6. Que signifie l'erreur CrashLoopBackOff ?
a) L'image Docker n'existe pas
b) Le Pod manque de mémoire
c) L'application dans le container plante au démarrage de manière répétée
d) Le réseau est inaccessible
💡 Voir la réponse
✅ Réponse : c — K8s tente de redémarrer le container mais il plante à chaque fois. Utilisez kubectl logs <pod> pour voir l'erreur.
❓ Q7. Quelle est la meilleure pratique pour les tags d'images en production ?
a) Toujours utiliser :latest
b) Utiliser des tags versionnés comme :1.0.0
c) Ne pas mettre de tag
d) Utiliser la date comme tag
💡 Voir la réponse
✅ Réponse : b — Les tags versionnés garantissent la reproductibilité. :latest peut changer et causer des comportements inattendus.
Mini-projet : Pipeline ETL sur Kubernetes
Objectif
Déployer un pipeline ETL complet sur Kubernetes :
- PostgreSQL avec stockage persistant
- CronJob Python qui charge des données CSV dans PostgreSQL
Contexte
Tu dois créer une stack Data Engineering minimale mais réaliste, entièrement orchestrée par Kubernetes.
Structure du projet
k8s-etl-project/
├── manifests/
│ ├── namespace.yaml
│ ├── postgres-secret.yaml
│ ├── postgres-pvc.yaml
│ ├── postgres-deployment.yaml
│ ├── postgres-service.yaml
│ ├── etl-configmap.yaml
│ └── etl-cronjob.yaml
├── etl/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── etl.py
├── data/
│ └── sales.csv
└── README.md
Étapes
- Créer le namespace
data-pipeline - Déployer PostgreSQL (Secret + PVC + Deployment + Service)
- Build & push l'image Docker de l'ETL
- Déployer le CronJob ETL
- Tester manuellement :
kubectl create job --from=cronjob/etl-daily test-etl - Vérifier les données dans PostgreSQL
✅ Solution du mini-projet
📥 Afficher la solution complète
1. manifests/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: data-pipeline
labels:
team: data-engineering
2. manifests/postgres-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: data-pipeline
type: Opaque
data:
password: ZGVfcGFzc3dvcmQ= # de_password
3. manifests/postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: data-pipeline
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
4. manifests/postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: data-pipeline
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "de_user"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: POSTGRES_DB
value: "de_db"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
5. manifests/postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: data-pipeline
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
6. manifests/etl-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: etl-config
namespace: data-pipeline
data:
DB_HOST: "postgres"
DB_PORT: "5432"
DB_NAME: "de_db"
DB_USER: "de_user"
7. manifests/etl-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: etl-daily
namespace: data-pipeline
labels:
app: etl-pipeline
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
metadata:
labels:
app: etl-job
spec:
containers:
- name: etl
image: my-etl-image:1.0 # À remplacer par ton image
envFrom:
- configMapRef:
name: etl-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
restartPolicy: OnFailure
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
8. etl/etl.py
import os
import pandas as pd
from sqlalchemy import create_engine
def main():
# Config depuis variables d'environnement
db_host = os.environ['DB_HOST']
db_port = os.environ['DB_PORT']
db_name = os.environ['DB_NAME']
db_user = os.environ['DB_USER']
db_pass = os.environ['DB_PASSWORD']
print("🚀 Démarrage ETL...")
# Simuler des données (en vrai: lire depuis PVC ou S3)
df = pd.DataFrame({
'date': ['2024-01-01', '2024-01-02'],
'product': ['Laptop', 'Mouse'],
'quantity': [5, 20],
'price': [999.99, 29.99]
})
df['total'] = df['quantity'] * df['price']
# Charger dans PostgreSQL
engine = create_engine(
f'postgresql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}'
)
df.to_sql('sales', engine, if_exists='replace', index=False)
print("✅ ETL terminé !")
if __name__ == "__main__":
main()
9. Commandes de déploiement
# Créer le namespace
kubectl apply -f manifests/namespace.yaml
# Déployer PostgreSQL
kubectl apply -f manifests/postgres-secret.yaml
kubectl apply -f manifests/postgres-pvc.yaml
kubectl apply -f manifests/postgres-deployment.yaml
kubectl apply -f manifests/postgres-service.yaml
# Vérifier que PostgreSQL est prêt
kubectl get pods -n data-pipeline
# Déployer le CronJob
kubectl apply -f manifests/etl-configmap.yaml
kubectl apply -f manifests/etl-cronjob.yaml
# Tester manuellement
kubectl create job --from=cronjob/etl-daily test-etl -n data-pipeline
# Voir les logs
kubectl logs -f job/test-etl -n data-pipeline
# Vérifier dans PostgreSQL
kubectl exec -it $(kubectl get pod -l app=postgres -n data-pipeline -o name) \
-n data-pipeline -- psql -U de_user -d de_db -c "SELECT * FROM sales;"
📚 Ressources pour aller plus loin
🌐 Documentation officielle
- Kubernetes Docs — Documentation officielle
- kubectl Cheat Sheet — Toutes les commandes
- Kubernetes API Reference — Référence API
🎮 Pratique
- Katacoda K8s — Tutoriels interactifs (gratuit)
- Play with Kubernetes — Cluster K8s gratuit en ligne
- Killer.sh — Simulateur d'examen CKA/CKAD
📖 Livres & Cours
- Kubernetes Up & Running — Kelsey Hightower
- The Kubernetes Book — Nigel Poulton
🔧 Outils utiles
- k9s — Terminal UI pour Kubernetes
- Lens — IDE Kubernetes
- kubectx/kubens — Changer de contexte/namespace facilement
➡️ Prochaine étape
Maintenant que tu maîtrises les fondamentaux de Kubernetes, passons aux workloads Data Engineering avancés !
👉 Module suivant : 16_k8s_for_data_workloads — Kubernetes pour les workloads Data
Tu vas apprendre :
- Spark on Kubernetes
- Airflow on Kubernetes
- Helm charts pour packager tes déploiements
- Horizontal Pod Autoscaler
🎉 Félicitations ! Tu as terminé le module Kubernetes Fundamentals pour Data Engineers.