🟩 Niveau 2 : Intermédiaire
Spark on Kubernetes — Production-Grade Deployment
Bienvenue dans ce module où tu vas apprendre à déployer Apache Spark sur Kubernetes. C'est l'architecture moderne de référence pour exécuter des workloads Spark en production.
Prérequis
| Niveau | Module | Compétence |
|---|---|---|
| ✅ Requis | Module 14 | Docker Fundamentals |
| ✅ Requis | Module 15 | Kubernetes Fundamentals |
| ✅ Requis | Module 16 | Kubernetes for Data Workloads |
| ✅ Requis | Module 19 | PySpark Advanced |
| ✅ Requis | Module 20 | Spark SQL Deep Dive |
| 💡 Recommandé | - | Expérience avec kubectl et Helm |
Objectifs du module
À la fin de ce module, tu seras capable de :
- Comprendre l'architecture Spark on Kubernetes
- Construire une image Docker Spark optimisée
- Configurer les ressources Kubernetes (RBAC, Secrets, PVC)
- Lancer des jobs avec spark-submit sur K8s
- Utiliser Spark Operator pour la production
- Configurer l'autoscaling (Dynamic Allocation, KEDA)
- Mettre en place le monitoring (Spark UI, Prometheus, Grafana)
- Debugger les erreurs courantes
1. Introduction — Pourquoi Spark sur Kubernetes ?
1.1 L'évolution de l'infrastructure Spark
2010s 2020s+
┌─────────────────┐ ┌─────────────────┐
│ Hadoop/YARN │ │ Kubernetes │
│ ┌─────────┐ │ │ ┌─────────┐ │
│ │ Spark │ │ ────────▶ │ │ Spark │ │
│ └─────────┘ │ │ └─────────┘ │
│ On-premise │ │ Cloud-native │
└─────────────────┘ └─────────────────┘
1.2 Avantages de Kubernetes
| Avantage | Description |
|---|---|
| Cloud-native | Même infra que le reste de tes applications |
| Multi-cloud | AWS, GCP, Azure, on-premise |
| Isolation | Namespaces, RBAC, Network Policies |
| Autoscaling | HPA, VPA, KEDA, Cluster Autoscaler |
| CI/CD | Images Docker versionnées, GitOps |
| Cost optimization | Spot instances, autoscaling down |
| Standardisation | Plus besoin d'expertise Hadoop/YARN |
1.3 Support officiel
- Spark 2.3 : Support expérimental K8s
- Spark 3.0 : Support stable (GA)
- Spark 3.1+ : Améliorations (PVC, Pod templates)
- Spark 3.4+ : External shuffle service sur K8s
1.4 Comparaison YARN vs Kubernetes
| Aspect | YARN | Kubernetes |
|---|---|---|
| Écosystème | Hadoop-centric | Cloud-native, polyglot |
| Scheduling | ResourceManager | kube-scheduler |
| Isolation | Containers/cgroups | Pods, Namespaces, Network Policies |
| Scaling | Manual ou scripts | HPA, VPA, KEDA, Cluster Autoscaler |
| Packaging | JAR/ZIP sur HDFS | Images Docker |
| Secrets | Hadoop credentials | K8s Secrets, Vault |
| Monitoring | YARN UI, Ganglia | Prometheus, Grafana, native |
| Multi-tenancy | Queues | Namespaces + RBAC |
| Data locality | ✅ Excellent | ⚠️ Limité (shuffle coûteux) |
| Courbe d'apprentissage | Hadoop stack | K8s stack |
| Adoption 2024+ | Legacy | Standard moderne |
📊 Quand choisir quoi ?
YARN si : Kubernetes si :
├── Cluster Hadoop existant ├── Infrastructure cloud-native
├── Data locality critique ├── Multi-cloud ou hybrid
├── Équipe Hadoop experte ├── CI/CD moderne (GitOps)
└── Pas de migration prévue └── Autoscaling avancé requis
2. Architecture Spark on Kubernetes
2.1 Vue d'ensemble
┌─────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Namespace: spark │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Driver Pod │ │ Executor Pods │ │ │
│ │ │ ┌───────────┐ │ │ ┌───────┐ ┌───────┐ │ │ │
│ │ │ │ Spark │ │◀────▶│ │ Exec │ │ Exec │ │ │ │
│ │ │ │ Driver │ │ │ │ #1 │ │ #2 │ │ │ │
│ │ │ └───────────┘ │ │ └───────┘ └───────┘ │ │ │
│ │ │ - Spark UI │ │ ┌───────┐ ┌───────┐ │ │ │
│ │ │ - Coordinateur │ │ │ Exec │ │ Exec │ │ │ │
│ │ └─────────────────┘ │ │ #3 │ │ #4 │ │ │ │
│ │ │ │ └───────┘ └───────┘ │ │ │
│ │ │ └─────────────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ ConfigMaps │ │ Secrets │ │ │
│ │ │ - spark-config │ │ - cloud credentials │ │ │
│ │ └─────────────────┘ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐│ │
│ │ │ PersistentVolumeClaims ││ │
│ │ │ - checkpoints - logs - shuffle (optional) ││ │
│ │ └─────────────────────────────────────────────────────┘│ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 Rôles et responsabilités
| Composant | Géré par | Responsabilité |
|---|---|---|
| Pods scheduling | Kubernetes | Placement sur les nodes |
| Networking | Kubernetes | Communication Driver ↔ Executors |
| Secrets | Kubernetes | Credentials cloud/DB |
| Storage | Kubernetes | PVC pour checkpoints/logs |
| Driver | Spark | Coordination du job |
| Executors | Spark | Exécution des tasks |
| Shuffle | Spark | Échange de données entre stages |
2.3 Client Mode vs Cluster Mode
CLIENT MODE CLUSTER MODE
┌──────────────┐ ┌──────────────────────────────┐
│ Local Machine│ │ Kubernetes Cluster │
│ ┌────────┐ │ │ ┌────────┐ │
│ │ Driver │ │◀─────────┐ │ │ Driver │◀────┐ │
│ └────────┘ │ │ │ │ Pod │ │ │
└──────────────┘ │ │ └────────┘ │ │
│ │ ▲ │ │
┌─────────────────────────┼─────────┐ │ │ │ │
│ K8s Cluster │ │ │ ▼ ▼ │
│ ┌────────┐ ┌────────┐ │ │ │ ┌────────┐ ┌────────┐ │
│ │Executor│ │Executor│◀─┘ │ │ │Executor│ │Executor│ │
│ │ Pod │ │ Pod │ │ │ │ Pod │ │ Pod │ │
│ └────────┘ └────────┘ │ │ └────────┘ └────────┘ │
└───────────────────────────────────┘ └──────────────────────────────┘
| Aspect | Client Mode | Cluster Mode |
|---|---|---|
| Driver | Machine locale | Pod Kubernetes |
| Usage | Développement, debug | Production |
| Spark UI | localhost:4040 | Via Service/Ingress |
| Réseau | Doit atteindre le cluster | Interne au cluster |
| Résilience | ❌ Si local crash → job perdu | ✅ Pod peut être reschedulé |
| CI/CD | ❌ Difficile | ✅ Natif |
💡 Règle : Client mode pour le dev/debug, Cluster mode pour la production.
Exercice 1 : Identifier les composants
Réponds aux questions suivantes :
- Dans quel mode le Driver tourne-t-il en tant que Pod K8s ?
- Qui gère le scheduling des Executor Pods ?
- Où sont stockés les credentials cloud ?
💡 Voir les réponses
- Cluster mode — Le Driver est un Pod K8s
- Kubernetes (kube-scheduler) — Spark demande des Pods, K8s les place
- Kubernetes Secrets — Montés dans les Pods Driver/Executor
3. Construire une Image Docker Spark
3.1 Image de base
Plusieurs options :
| Image | Avantage | Inconvénient |
|---|---|---|
apache/spark |
Officielle | Basique |
bitnami/spark |
Bien maintenue, non-root | Taille moyenne |
gcr.io/spark-operator/spark |
Pour Spark Operator | Spécifique |
| Custom | Contrôle total | Plus de travail |
writefileVoir le code
# Image Spark de base
FROM bitnami/spark:3.5
# Passer en root pour installer des packages
USER root
# Installer des dépendances Python
RUN pip install --no-cache-dir \
boto3 \
pyarrow \
pandas
# Copier l'application
COPY app/ /app/
# Revenir à l'utilisateur non-root (sécurité)
USER 1001
# Définir le working directory
WORKDIR /app3.2 Image production-grade avec JARs
writefileVoir le code
# ============================================
# Spark Production Image
# ============================================
FROM bitnami/spark:3.5 AS base
USER root
# ---- Dépendances système ----
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# ---- Dépendances Python ----
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# ---- JARs pour connecteurs cloud ----
# AWS S3
RUN curl -sL https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.3.4/hadoop-aws-3.3.4.jar \
-o /opt/bitnami/spark/jars/hadoop-aws-3.3.4.jar
RUN curl -sL https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/1.12.262/aws-java-sdk-bundle-1.12.262.jar \
-o /opt/bitnami/spark/jars/aws-java-sdk-bundle-1.12.262.jar
# ---- Application ----
COPY app/ /app/
# ---- Sécurité : non-root ----
RUN chown -R 1001:1001 /app
USER 1001
WORKDIR /app
# ---- Healthcheck ----
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:4040/api/v1/applications || exit 1writefileVoir le code
# Python dependencies for Spark jobs
boto3>=1.28.0
pyarrow>=14.0.0
pandas>=2.0.0
requests>=2.31.03.3 Best practices Docker pour Spark
| Pratique | Pourquoi | Comment |
|---|---|---|
| Non-root | Sécurité | USER 1001 |
| Multi-stage builds | Image plus légère | FROM ... AS builder |
| Layer caching | Builds plus rapides | Dépendances avant code |
| No cache pip | Image plus légère | --no-cache-dir |
| Healthcheck | K8s liveness probe | HEALTHCHECK |
| Versioning | Reproductibilité | Tags explicites (pas latest) |
pythonVoir le code
# Commandes pour construire et pousser l'image
docker_commands = """
# Construire l'image
docker build -t my-registry/spark-app:1.0.0 -f Dockerfile.production .
# Tester localement
docker run --rm my-registry/spark-app:1.0.0 spark-submit --version
# Pousser vers un registry
docker push my-registry/spark-app:1.0.0
# Pour Minikube (utiliser le Docker daemon de Minikube)
eval $(minikube docker-env)
docker build -t spark-app:1.0.0 .
"""
print(docker_commands)Exercice 2 : Construire une image Spark
Objectif : Créer un Dockerfile Spark avec :
- Base
bitnami/spark:3.5 - Dépendance Python :
numpy - Un script
hello.pyqui affiche "Hello Spark on K8s!"
💡 Solution
FROM bitnami/spark:3.5
USER root
RUN pip install --no-cache-dir numpy
COPY hello.py /app/hello.py
USER 1001
WORKDIR /app
# hello.py
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("Hello").getOrCreate()
print("Hello Spark on K8s!")
spark.stop()
4. Configuration Kubernetes pour Spark
4.1 Namespace dédié
writefileVoir le code
apiVersion: v1
kind: Namespace
metadata:
name: spark
labels:
app: spark
environment: development4.2 ServiceAccount & RBAC
Spark a besoin de permissions pour :
- Créer des Pods (executors)
- Lister/supprimer des Pods
- Accéder aux ConfigMaps et Secrets
writefileVoir le code
# ServiceAccount pour Spark
apiVersion: v1
kind: ServiceAccount
metadata:
name: spark-sa
namespace: spark
---
# Role avec permissions minimales
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: spark-role
namespace: spark
rules:
# Gérer les pods (executors)
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "get", "list", "watch", "delete"]
# Logs des pods
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# ConfigMaps pour Spark config
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create", "get", "list", "watch", "delete"]
# Services pour Spark UI
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "get", "delete"]
# PersistentVolumeClaims
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["create", "get", "list", "delete"]
---
# Binding Role → ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: spark-role-binding
namespace: spark
subjects:
- kind: ServiceAccount
name: spark-sa
namespace: spark
roleRef:
kind: Role
name: spark-role
apiGroup: rbac.authorization.k8s.io4.3 Secrets (credentials cloud)
⚠️ Note : En production, utilise un gestionnaire de secrets (Vault, AWS Secrets Manager, etc.)
writefileVoir le code
# Secret pour MinIO (ou S3)
apiVersion: v1
kind: Secret
metadata:
name: minio-credentials
namespace: spark
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: "minioadmin"
AWS_SECRET_ACCESS_KEY: "minioadmin"
S3_ENDPOINT: "http://minio.minio.svc.cluster.local:9000"pythonVoir le code
# Alternative : créer le secret via CLI
secret_command = """
kubectl create secret generic minio-credentials \
--namespace=spark \
--from-literal=AWS_ACCESS_KEY_ID=minioadmin \
--from-literal=AWS_SECRET_ACCESS_KEY=minioadmin \
--from-literal=S3_ENDPOINT=http://minio.minio.svc.cluster.local:9000
"""
print(secret_command)4.4 PersistentVolumeClaims
writefileVoir le code
# PVC pour les logs Spark
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: spark-logs-pvc
namespace: spark
spec:
accessModes:
- ReadWriteMany # Plusieurs pods peuvent écrire
resources:
requests:
storage: 10Gi
storageClassName: standard # Adapter selon ton cluster
---
# PVC pour les checkpoints (streaming)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: spark-checkpoints-pvc
namespace: spark
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
storageClassName: standard4.5 ResourceQuotas & LimitRanges (optionnel mais recommandé)
writefileVoir le code
# Limiter les ressources du namespace Spark
apiVersion: v1
kind: ResourceQuota
metadata:
name: spark-quota
namespace: spark
spec:
hard:
requests.cpu: "20" # Max 20 CPU demandés
requests.memory: "40Gi" # Max 40 Gi mémoire demandée
limits.cpu: "40" # Max 40 CPU limites
limits.memory: "80Gi" # Max 80 Gi mémoire limites
pods: "50" # Max 50 pods
---
# Defaults pour les pods sans spec
apiVersion: v1
kind: LimitRange
metadata:
name: spark-limits
namespace: spark
spec:
limits:
- type: Container
default:
cpu: "1"
memory: "2Gi"
defaultRequest:
cpu: "500m"
memory: "1Gi"
max:
cpu: "8"
memory: "16Gi"Exercice 3 : Créer les manifests RBAC
Question : Pourquoi le ServiceAccount Spark a-t-il besoin de la permission pods/log ?
💡 Réponse
Le Driver Spark a besoin de lire les logs des Executor Pods pour :
- Afficher les erreurs dans le Spark UI
- Permettre le debugging via
kubectl logs - Collecter les métriques d'exécution
5. spark-submit sur Kubernetes
5.1 Syntaxe de base
pythonVoir le code
spark_submit_basic = """
# Spark-submit basique sur K8s (cluster mode)
spark-submit \
--master k8s://https://<kubernetes-api-server>:6443 \
--deploy-mode cluster \
--name spark-pi \
--conf spark.kubernetes.container.image=spark-app:1.0.0 \
--conf spark.kubernetes.namespace=spark \
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-sa \
local:///app/pi.py
"""
print(spark_submit_basic)5.2 Commande complète production-grade
pythonVoir le code
spark_submit_full = """
# Spark-submit complet pour production
spark-submit \
# === Master & Mode ===
--master k8s://https://$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}') \
--deploy-mode cluster \
--name etl-job-$(date +%Y%m%d-%H%M%S) \
\
# === Image & Namespace ===
--conf spark.kubernetes.container.image=my-registry/spark-app:1.0.0 \
--conf spark.kubernetes.namespace=spark \
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-sa \
\
# === Driver Resources ===
--conf spark.driver.cores=2 \
--conf spark.driver.memory=4g \
--conf spark.kubernetes.driver.request.cores=1 \
--conf spark.kubernetes.driver.limit.cores=2 \
\
# === Executor Resources ===
--conf spark.executor.instances=4 \
--conf spark.executor.cores=2 \
--conf spark.executor.memory=4g \
--conf spark.kubernetes.executor.request.cores=1 \
--conf spark.kubernetes.executor.limit.cores=2 \
\
# === Secrets (environnement) ===
--conf spark.kubernetes.driver.secretKeyRef.AWS_ACCESS_KEY_ID=minio-credentials:AWS_ACCESS_KEY_ID \
--conf spark.kubernetes.driver.secretKeyRef.AWS_SECRET_ACCESS_KEY=minio-credentials:AWS_SECRET_ACCESS_KEY \
--conf spark.kubernetes.executor.secretKeyRef.AWS_ACCESS_KEY_ID=minio-credentials:AWS_ACCESS_KEY_ID \
--conf spark.kubernetes.executor.secretKeyRef.AWS_SECRET_ACCESS_KEY=minio-credentials:AWS_SECRET_ACCESS_KEY \
\
# === S3/MinIO Config ===
--conf spark.hadoop.fs.s3a.endpoint=http://minio.minio.svc.cluster.local:9000 \
--conf spark.hadoop.fs.s3a.path.style.access=true \
--conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem \
\
# === Application ===
local:///app/etl_job.py \
--input s3a://bronze/data \
--output s3a://silver/data
"""
print(spark_submit_full)5.3 Configurations essentielles
| Configuration | Description | Exemple |
|---|---|---|
spark.kubernetes.container.image |
Image Docker Spark | registry/spark:1.0 |
spark.kubernetes.namespace |
Namespace K8s | spark |
spark.kubernetes.authenticate.driver.serviceAccountName |
ServiceAccount | spark-sa |
spark.executor.instances |
Nombre d'executors | 4 |
spark.executor.cores |
Cores par executor | 2 |
spark.executor.memory |
Mémoire par executor | 4g |
spark.kubernetes.driver.secretKeyRef.* |
Secrets → env vars | Voir exemple |
spark.kubernetes.executor.deleteOnTermination |
Cleanup | true |
5.4 Accéder au Spark UI
En cluster mode, le Spark UI est dans le Pod Driver.
pythonVoir le code
spark_ui_access = """
# 1. Trouver le pod Driver
kubectl get pods -n spark -l spark-role=driver
# 2. Port-forward vers le Spark UI
kubectl port-forward -n spark <driver-pod-name> 4040:4040
# 3. Ouvrir dans le navigateur
# http://localhost:4040
# Alternative : créer un Service
kubectl expose pod <driver-pod-name> -n spark \
--port=4040 --target-port=4040 \
--name=spark-ui --type=NodePort
"""
print(spark_ui_access)Exercice 4 : Lancer un job Spark sur Minikube
Étapes :
- Démarrer Minikube :
minikube start --cpus=4 --memory=8g - Créer le namespace et RBAC
- Lancer le job
SparkPiintégré
💡 Solution
# 1. Setup
minikube start --cpus=4 --memory=8g
kubectl create namespace spark
kubectl apply -f rbac.yaml
# 2. Obtenir l'URL de l'API K8s
K8S_API=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')
# 3. Lancer SparkPi
spark-submit \
--master k8s://$K8S_API \
--deploy-mode cluster \
--name spark-pi \
--conf spark.kubernetes.container.image=apache/spark:3.5.0 \
--conf spark.kubernetes.namespace=spark \
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-sa \
--conf spark.executor.instances=2 \
local:///opt/spark/examples/src/main/python/pi.py 100
# 4. Vérifier
kubectl get pods -n spark
kubectl logs -n spark <driver-pod> | tail -20
6. Spark Operator — Production-Grade
6.1 Pourquoi utiliser Spark Operator ?
| spark-submit | Spark Operator |
|---|---|
| Commande CLI | Manifeste YAML déclaratif |
| Pas de retry automatique | Retry policy configurable |
| Pas de scheduling | CronJob-like scheduling |
| Difficile à intégrer CI/CD | GitOps natif |
| Logs dispersés | Logs centralisés |
6.2 Installation avec Helm
pythonVoir le code
operator_install = """
# Ajouter le repo Helm
helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update
# Installer l'opérateur
helm install spark-operator spark-operator/spark-operator \
--namespace spark-operator \
--create-namespace \
--set webhook.enable=true \
--set sparkJobNamespace=spark
# Vérifier
kubectl get pods -n spark-operator
"""
print(operator_install)6.3 SparkApplication CRD
writefileVoir le code
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: etl-job
namespace: spark
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: my-registry/spark-app:1.0.0
imagePullPolicy: Always
mainApplicationFile: local:///app/etl_job.py
arguments:
- "--input"
- "s3a://bronze/data"
- "--output"
- "s3a://silver/data"
sparkVersion: "3.5.0"
# Configuration Spark
sparkConf:
"spark.hadoop.fs.s3a.endpoint": "http://minio.minio.svc.cluster.local:9000"
"spark.hadoop.fs.s3a.path.style.access": "true"
"spark.hadoop.fs.s3a.impl": "org.apache.hadoop.fs.s3a.S3AFileSystem"
# Restart policy
restartPolicy:
type: OnFailure
onFailureRetries: 3
onFailureRetryInterval: 30
onSubmissionFailureRetries: 3
# Driver config
driver:
cores: 1
coreLimit: "1200m"
memory: "2g"
serviceAccount: spark-sa
labels:
app: spark-etl
role: driver
envSecretKeyRefs:
AWS_ACCESS_KEY_ID:
name: minio-credentials
key: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
name: minio-credentials
key: AWS_SECRET_ACCESS_KEY
# Executor config
executor:
cores: 2
coreLimit: "2400m"
instances: 4
memory: "4g"
labels:
app: spark-etl
role: executor
envSecretKeyRefs:
AWS_ACCESS_KEY_ID:
name: minio-credentials
key: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
name: minio-credentials
key: AWS_SECRET_ACCESS_KEY6.4 ScheduledSparkApplication (Cron jobs)
writefileVoir le code
apiVersion: sparkoperator.k8s.io/v1beta2
kind: ScheduledSparkApplication
metadata:
name: daily-etl
namespace: spark
spec:
schedule: "0 2 * * *" # Tous les jours à 2h du matin
concurrencyPolicy: Forbid # Ne pas lancer si le précédent tourne encore
successfulRunHistoryLimit: 5
failedRunHistoryLimit: 3
template:
type: Python
pythonVersion: "3"
mode: cluster
image: my-registry/spark-app:1.0.0
mainApplicationFile: local:///app/daily_etl.py
sparkVersion: "3.5.0"
restartPolicy:
type: OnFailure
onFailureRetries: 2
driver:
cores: 1
memory: "2g"
serviceAccount: spark-sa
executor:
cores: 2
instances: 3
memory: "4g"pythonVoir le code
operator_commands = """
# Appliquer une SparkApplication
kubectl apply -f spark-application.yaml
# Voir le statut
kubectl get sparkapplication -n spark
kubectl describe sparkapplication etl-job -n spark
# Voir les logs du driver
kubectl logs -n spark -l spark-role=driver -f
# Supprimer
kubectl delete sparkapplication etl-job -n spark
"""
print(operator_commands)Exercice 5 : Déployer avec SparkOperator
Objectif : Créer une SparkApplication qui calcule Pi avec 1000 itérations.
💡 Solution
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi
namespace: spark
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: apache/spark:3.5.0
mainApplicationFile: local:///opt/spark/examples/src/main/python/pi.py
arguments: ["1000"]
sparkVersion: "3.5.0"
restartPolicy:
type: Never
driver:
cores: 1
memory: "1g"
serviceAccount: spark-sa
executor:
cores: 1
instances: 2
memory: "1g"
7. Autoscaling & Resource Management
7.1 Dynamic Allocation (Spark natif)
Spark peut ajuster dynamiquement le nombre d'executors.
pythonVoir le code
dynamic_allocation_config = """
# Dans sparkConf ou spark-submit
spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.minExecutors=1
spark.dynamicAllocation.maxExecutors=10
spark.dynamicAllocation.initialExecutors=2
spark.dynamicAllocation.executorIdleTimeout=60s
spark.dynamicAllocation.schedulerBacklogTimeout=1s
# Shuffle tracking (requis pour K8s)
spark.dynamicAllocation.shuffleTracking.enabled=true
spark.dynamicAllocation.shuffleTracking.timeout=600s
"""
print(dynamic_allocation_config)7.2 Options d'autoscaling K8s
| Type | Description | Use case |
|---|---|---|
| Dynamic Allocation | Spark gère les executors | ✅ Recommandé pour batch |
| HPA | Scale sur CPU/memory | ⚠️ Pas idéal pour Spark |
| VPA | Ajuste les ressources | 💡 Pour dimensionnement initial |
| KEDA | Scale sur événements externes | ✅ Idéal pour streaming (Kafka lag) |
| Cluster Autoscaler | Ajoute/retire des nodes | ✅ Combiné avec Dynamic Allocation |
writefileVoir le code
# KEDA ScaledObject pour Spark Streaming basé sur Kafka lag
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: spark-streaming-scaler
namespace: spark
spec:
scaleTargetRef:
name: spark-streaming # Deployment à scaler
minReplicaCount: 1
maxReplicaCount: 10
triggers:
- type: kafka
metadata:
bootstrapServers: kafka.kafka.svc.cluster.local:9092
consumerGroup: spark-streaming-group
topic: events
lagThreshold: "100" # Scale si lag > 1007.3 Node Affinity & Tolerations
writefileVoir le code
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: gpu-job
namespace: spark
spec:
type: Python
mode: cluster
image: my-registry/spark-gpu:1.0.0
mainApplicationFile: local:///app/gpu_job.py
sparkVersion: "3.5.0"
driver:
cores: 1
memory: "2g"
serviceAccount: spark-sa
executor:
cores: 4
instances: 2
memory: "8g"
# Placer les executors sur des nodes avec GPU
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- gpu
# Tolérer les taints GPU
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"Exercice 6 : Configurer Dynamic Allocation
Objectif : Modifier la SparkApplication pour avoir :
- Min 2 executors
- Max 8 executors
- Initial 3 executors
💡 Solution
sparkConf:
"spark.dynamicAllocation.enabled": "true"
"spark.dynamicAllocation.minExecutors": "2"
"spark.dynamicAllocation.maxExecutors": "8"
"spark.dynamicAllocation.initialExecutors": "3"
"spark.dynamicAllocation.shuffleTracking.enabled": "true"
8. Monitoring & Observability
8.1 Spark UI
writefileVoir le code
# Service pour Spark UI
apiVersion: v1
kind: Service
metadata:
name: spark-ui
namespace: spark
spec:
selector:
spark-role: driver
ports:
- port: 4040
targetPort: 4040
type: ClusterIP
---
# Ingress pour accès externe
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spark-ui-ingress
namespace: spark
spec:
rules:
- host: spark-ui.mycompany.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spark-ui
port:
number: 40408.2 Spark History Server
writefileVoir le code
apiVersion: apps/v1
kind: Deployment
metadata:
name: spark-history-server
namespace: spark
spec:
replicas: 1
selector:
matchLabels:
app: spark-history-server
template:
metadata:
labels:
app: spark-history-server
spec:
containers:
- name: history-server
image: bitnami/spark:3.5
command:
- /opt/bitnami/spark/sbin/start-history-server.sh
env:
- name: SPARK_HISTORY_OPTS
value: "-Dspark.history.fs.logDirectory=s3a://spark-logs/history"
ports:
- containerPort: 18080
envFrom:
- secretRef:
name: minio-credentials
---
apiVersion: v1
kind: Service
metadata:
name: spark-history-server
namespace: spark
spec:
selector:
app: spark-history-server
ports:
- port: 18080
targetPort: 18080
type: ClusterIPExercice 7 : Exposer Spark UI
Objectif : Accéder au Spark UI d'un job en cours.
# 1. Lister les pods driver
kubectl get pods -n spark -l spark-role=driver
# 2. Port-forward
kubectl port-forward -n spark <driver-pod> 4040:4040
# 3. Ouvrir http://localhost:4040
9. Debugging & Troubleshooting
9.1 Erreurs courantes et solutions
| Erreur | Cause | Solution |
|---|---|---|
ImagePullBackOff |
Image introuvable | Vérifier registry, credentials, tag |
OOMKilled |
Mémoire insuffisante | Augmenter memory, réduire données |
Forbidden |
RBAC manquant | Vérifier Role/RoleBinding |
Pending (pods) |
Ressources insuffisantes | Vérifier quotas, cluster capacity |
Connection refused |
Driver inaccessible | Vérifier network policies, services |
ClassNotFoundException |
JAR manquant | Ajouter dans l'image Docker |
pythonVoir le code
debug_commands = """
# === Debugging Spark on K8s ===
# 1. Voir les pods et leur statut
kubectl get pods -n spark -o wide
# 2. Détails d'un pod (événements)
kubectl describe pod <pod-name> -n spark
# 3. Logs du driver
kubectl logs -n spark -l spark-role=driver
# 4. Logs d'un executor spécifique
kubectl logs -n spark <executor-pod-name>
# 5. Logs en temps réel (follow)
kubectl logs -n spark -l spark-role=driver -f
# 6. Shell dans un pod (debug)
kubectl exec -it <pod-name> -n spark -- /bin/bash
# 7. Vérifier les ressources du namespace
kubectl describe resourcequota -n spark
# 8. Vérifier les events
kubectl get events -n spark --sort-by='.lastTimestamp'
# 9. SparkApplication status
kubectl get sparkapplication -n spark
kubectl describe sparkapplication <name> -n spark
"""
print(debug_commands)9.2 Debugging OOMKilled
🔍 Symptôme : Pod executor en état OOMKilled
Causes possibles :
├── spark.executor.memory trop bas
├── spark.executor.memoryOverhead mal configuré
├── Trop de données par partition
├── Broadcast variables trop grandes
└── Fuite mémoire dans le code
Solutions :
├── Augmenter spark.executor.memory
├── spark.executor.memoryOverhead = max(0.1 × memory, 384m)
├── Repartitionner : df.repartition(200)
├── Réduire spark.sql.shuffle.partitions
└── Utiliser spark.memory.fraction = 0.6 (default)
9.3 Debugging Shuffle failures
Le shuffle est plus coûteux sur K8s (pas de data locality).
pythonVoir le code
shuffle_optimizations = """
# Optimisations shuffle pour K8s
# Augmenter les timeouts
spark.network.timeout=600s
spark.shuffle.io.maxRetries=10
spark.shuffle.io.retryWait=30s
# Compression
spark.shuffle.compress=true
spark.shuffle.spill.compress=true
# Buffer sizes
spark.shuffle.file.buffer=64k
spark.reducer.maxSizeInFlight=96m
# Shuffle tracking (pour Dynamic Allocation)
spark.dynamicAllocation.shuffleTracking.enabled=true
"""
print(shuffle_optimizations)Exercice 8 : Diagnostiquer un job qui échoue
Scénario : Un job Spark échoue avec le message "Pod OOMKilled".
Questions :
- Quelle commande pour voir les events du pod ?
- Quelles configs Spark vérifier ?
💡 Solution
- Commandes :
kubectl describe pod <pod-name> -n spark
kubectl get events -n spark --field-selector involvedObject.name=<pod-name>
- Configs à vérifier :
spark.executor.memory
spark.executor.memoryOverhead
spark.kubernetes.executor.limit.memory
10. Optimisations Spark on K8s
10.1 Data Locality
⚠️ Problème : Pas de data locality sur K8s
Sur YARN avec HDFS :
┌──────────────────────┐
│ Node 1 │
│ ┌────────┐ ┌───────┐ │
│ │Executor│◀│ Data │ │ ← Data local (fast)
│ └────────┘ └───────┘ │
└──────────────────────┘
Sur K8s avec Object Storage :
┌──────────────────────┐ ┌─────────────┐
│ Node 1 │ │ S3/MinIO │
│ ┌────────┐ │◀─────│ (remote) │ ← Network transfer
│ │Executor│ │ │ │
│ └────────┘ │ └─────────────┘
└──────────────────────┘
Solutions :
- Utiliser des formats colonnaires (Parquet) optimisés
- Configurer S3A/GCS pour le parallélisme
- External Shuffle Service (Spark 3.4+)
pythonVoir le code
s3a_config = """
# Optimisations S3A pour Spark on K8s
# Parallélisme
spark.hadoop.fs.s3a.connection.maximum=200
spark.hadoop.fs.s3a.threads.max=64
spark.hadoop.fs.s3a.threads.core=16
# Fast upload
spark.hadoop.fs.s3a.fast.upload=true
spark.hadoop.fs.s3a.fast.upload.buffer=bytebuffer
spark.hadoop.fs.s3a.multipart.size=104857600 # 100MB
# Committer (éviter les renames S3)
spark.sql.sources.commitProtocolClass=org.apache.spark.internal.io.cloud.PathOutputCommitProtocol
spark.sql.parquet.output.committer.class=org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter
"""
print(s3a_config)10.2 Pod Templates
Pour des configurations avancées des pods Spark.
writefileVoir le code
apiVersion: v1
kind: Pod
spec:
containers:
- name: spark-executor
volumeMounts:
- name: spark-local-dir
mountPath: /tmp/spark
resources:
requests:
ephemeral-storage: "10Gi"
limits:
ephemeral-storage: "20Gi"
volumes:
- name: spark-local-dir
emptyDir:
medium: Memory # Utiliser RAM pour shuffle local
sizeLimit: "4Gi"pythonVoir le code
pod_template_usage = """
# Utiliser le pod template dans spark-submit
spark-submit \
--conf spark.kubernetes.executor.podTemplateFile=/path/to/executor-pod-template.yaml \
...
# Ou dans SparkApplication
spec:
executor:
podTemplateFile: /path/to/executor-pod-template.yaml
"""
print(pod_template_usage)10.3 Spot/Preemptible Instances
Économiser jusqu'à 90% sur les coûts compute.
writefileVoir le code
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spot-etl
namespace: spark
spec:
type: Python
mode: cluster
image: my-registry/spark-app:1.0.0
mainApplicationFile: local:///app/etl.py
sparkVersion: "3.5.0"
# Driver sur nodes stables (on-demand)
driver:
cores: 1
memory: "2g"
serviceAccount: spark-sa
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- on-demand
# Executors sur spot instances
executor:
cores: 2
instances: 5
memory: "4g"
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-type
operator: In
values:
- spot
tolerations:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"11. Mini-Projet : ETL Pipeline sur Kubernetes
Objectif
Déployer un pipeline Spark complet sur K8s avec MinIO (Object Storage local).
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │
│ │ MinIO │ │ Spark │ │ MinIO │ │
│ │ (source) │────▶│ on K8s │────▶│ (output) │ │
│ │ │ │ │ │ │ │
│ │ bucket: │ │ ┌────────────┐ │ │ bucket: │ │
│ │ bronze/ │ │ │ Driver │ │ │ silver/ │ │
│ │ data.csv │ │ └────────────┘ │ │ data.parquet│ │
│ │ │ │ ┌────┐ ┌────┐ │ │ │ │
│ │ │ │ │Exec│ │Exec│ │ │ │ │
│ │ │ │ └────┘ └────┘ │ │ │ │
│ └─────────────┘ └──────────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Structure du projet
spark-k8s-project/
├── docker/
│ ├── Dockerfile
│ └── requirements.txt
├── app/
│ └── etl_job.py
├── manifests/
│ ├── namespace.yaml
│ ├── rbac.yaml
│ ├── minio-secret.yaml
│ └── spark-application.yaml
├── data/
│ └── sample.csv
└── README.md
Étape 1 : Déployer MinIO sur K8s
pythonVoir le code
minio_deploy = """
# Déployer MinIO avec Helm
helm repo add minio https://charts.min.io/
helm repo update
helm install minio minio/minio \
--namespace minio \
--create-namespace \
--set rootUser=minioadmin \
--set rootPassword=minioadmin \
--set mode=standalone \
--set resources.requests.memory=512Mi \
--set persistence.size=10Gi
# Port-forward pour accéder à la console
kubectl port-forward -n minio svc/minio-console 9001:9001
# Créer les buckets
mc alias set myminio http://localhost:9000 minioadmin minioadmin
mc mb myminio/bronze
mc mb myminio/silver
# Uploader des données de test
mc cp data/sample.csv myminio/bronze/
"""
print(minio_deploy)Étape 2 : Code de l'application ETL
writefileVoir le code
"""
ETL Job : Bronze → Silver
Lit des CSV depuis MinIO, transforme, et écrit en Parquet.
"""
import argparse
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, upper, current_timestamp
def create_spark_session():
"""Créer une SparkSession configurée pour MinIO."""
return SparkSession.builder \
.appName("ETL Bronze to Silver") \
.config("spark.hadoop.fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \
.config("spark.hadoop.fs.s3a.path.style.access", "true") \
.getOrCreate()
def extract(spark, input_path):
"""Lire les données brutes depuis le bucket Bronze."""
print(f"📥 Reading from {input_path}")
df = spark.read \
.option("header", "true") \
.option("inferSchema", "true") \
.csv(input_path)
print(f" Loaded {df.count()} rows")
return df
def transform(df):
"""Appliquer les transformations métier."""
print("🔄 Transforming data")
transformed = df \
.dropDuplicates() \
.dropna() \
.withColumn("processed_at", current_timestamp())
# Normalisation des colonnes string
for col_name, dtype in transformed.dtypes:
if dtype == "string":
transformed = transformed.withColumn(col_name, upper(col(col_name)))
print(f" Transformed to {transformed.count()} rows")
return transformed
def load(df, output_path):
"""Écrire les données transformées dans le bucket Silver."""
print(f"📤 Writing to {output_path}")
df.write \
.mode("overwrite") \
.parquet(output_path)
print(" Done!")
def main():
parser = argparse.ArgumentParser(description="ETL Job")
parser.add_argument("--input", required=True, help="Input path (s3a://...)")
parser.add_argument("--output", required=True, help="Output path (s3a://...)")
args = parser.parse_args()
spark = create_spark_session()
try:
# ETL Pipeline
df = extract(spark, args.input)
transformed = transform(df)
load(transformed, args.output)
print("✅ ETL completed successfully!")
except Exception as e:
print(f"❌ ETL failed: {e}")
raise
finally:
spark.stop()
if __name__ == "__main__":
main()Étape 3 : Dockerfile
writefileVoir le code
FROM bitnami/spark:3.5
USER root
# Dépendances Python
RUN pip install --no-cache-dir pyarrow pandas
# JARs pour S3/MinIO
RUN curl -sL https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.3.4/hadoop-aws-3.3.4.jar \
-o /opt/bitnami/spark/jars/hadoop-aws-3.3.4.jar && \
curl -sL https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/1.12.262/aws-java-sdk-bundle-1.12.262.jar \
-o /opt/bitnami/spark/jars/aws-java-sdk-bundle-1.12.262.jar
# Application
COPY app/ /app/
RUN chown -R 1001:1001 /app
USER 1001
WORKDIR /appÉtape 4 : Manifests Kubernetes
writefileVoir le code
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: etl-bronze-silver
namespace: spark
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: spark-etl:1.0.0 # Image locale (Minikube)
imagePullPolicy: Never # Pour Minikube
mainApplicationFile: local:///app/etl_job.py
arguments:
- "--input"
- "s3a://bronze/sample.csv"
- "--output"
- "s3a://silver/data"
sparkVersion: "3.5.0"
sparkConf:
"spark.hadoop.fs.s3a.endpoint": "http://minio.minio.svc.cluster.local:9000"
"spark.hadoop.fs.s3a.path.style.access": "true"
"spark.hadoop.fs.s3a.impl": "org.apache.hadoop.fs.s3a.S3AFileSystem"
restartPolicy:
type: OnFailure
onFailureRetries: 2
driver:
cores: 1
memory: "1g"
serviceAccount: spark-sa
envSecretKeyRefs:
AWS_ACCESS_KEY_ID:
name: minio-credentials
key: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
name: minio-credentials
key: AWS_SECRET_ACCESS_KEY
executor:
cores: 1
instances: 2
memory: "1g"
envSecretKeyRefs:
AWS_ACCESS_KEY_ID:
name: minio-credentials
key: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
name: minio-credentials
key: AWS_SECRET_ACCESS_KEYÉtape 5 : Déploiement complet
pythonVoir le code
deployment_script = """
#!/bin/bash
set -e
echo "🚀 Déploiement du pipeline ETL Spark on K8s"
# 1. Créer le namespace et RBAC
echo "📁 Création namespace et RBAC..."
kubectl apply -f manifests/namespace.yaml
kubectl apply -f manifests/rbac.yaml
kubectl apply -f manifests/minio-secret.yaml
# 2. Build de l'image (Minikube)
echo "🐳 Build de l'image Docker..."
eval $(minikube docker-env)
docker build -t spark-etl:1.0.0 -f docker/Dockerfile .
# 3. Vérifier que MinIO est prêt
echo "⏳ Attente de MinIO..."
kubectl wait --for=condition=ready pod -l app=minio -n minio --timeout=120s
# 4. Lancer le job Spark
echo "🔥 Lancement du job Spark..."
kubectl apply -f manifests/spark-application.yaml
# 5. Suivre le job
echo "📊 Suivi du job..."
kubectl get sparkapplication -n spark -w
"""
print(deployment_script)Étape 6 : Vérification
pythonVoir le code
verify_commands = """
# Vérifier le statut du job
kubectl get sparkapplication -n spark
# Voir les logs du driver
kubectl logs -n spark -l spark-role=driver
# Vérifier les outputs dans MinIO
mc ls myminio/silver/data/
# Lire un sample des données
mc cat myminio/silver/data/part-00000.parquet | head
"""
print(verify_commands)🧪 Quiz de fin de module
❓ Q1. Quelle est la différence entre Client mode et Cluster mode ?
a) Client mode est plus rapide
b) En Cluster mode, le Driver tourne dans un Pod K8s
c) Client mode ne supporte pas les executors
d) Cluster mode nécessite YARN
💡 Voir la réponse
✅ Réponse : b — En Cluster mode, le Driver est un Pod K8s. En Client mode, il reste sur la machine locale.
❓ Q2. Pourquoi utiliser Spark Operator plutôt que spark-submit directement ?
a) Spark Operator est plus rapide
b) spark-submit ne fonctionne pas sur K8s
c) Spark Operator permet des manifestes YAML déclaratifs et des retries automatiques
d) Spark Operator ne nécessite pas de ServiceAccount
💡 Voir la réponse
✅ Réponse : c — Spark Operator offre une approche déclarative (YAML), des retry policies, du scheduling, et une meilleure intégration CI/CD.
❓ Q3. Quel problème résout Dynamic Allocation ?
a) La sécurité des pods
b) L'ajustement automatique du nombre d'executors
c) Le stockage des logs
d) La communication réseau
💡 Voir la réponse
✅ Réponse : b — Dynamic Allocation permet à Spark d'ajuster automatiquement le nombre d'executors selon la charge.
❓ Q4. Quelle config est requise pour Dynamic Allocation sur K8s ?
a) spark.dynamicAllocation.enabled=true uniquement
b) spark.dynamicAllocation.shuffleTracking.enabled=true
c) spark.kubernetes.allocation.batch.size=5
d) Aucune config spéciale
💡 Voir la réponse
✅ Réponse : b — Sur K8s (sans External Shuffle Service), shuffle tracking est requis pour que Dynamic Allocation fonctionne.
❓ Q5. Que signifie l'erreur OOMKilled ?
a) Le pod n'a pas d'image
b) Le pod a dépassé sa limite mémoire
c) Le réseau est indisponible
d) Le ServiceAccount est invalide
💡 Voir la réponse
✅ Réponse : b — OOMKilled signifie que le container a dépassé sa limite mémoire et a été tué par K8s.
❓ Q6. Quel est l'avantage principal de K8s sur YARN pour Spark ?
a) Meilleure data locality
b) Infrastructure cloud-native et multi-cloud
c) Plus rapide
d) Moins de configuration
💡 Voir la réponse
✅ Réponse : b — K8s offre une infrastructure cloud-native, standardisée, multi-cloud, avec autoscaling avancé.
❓ Q7. Comment exposer le Spark UI d'un job en cluster mode ?
a) Il est automatiquement accessible sur localhost:4040
b) Via port-forward, Service, ou Ingress
c) Via YARN ResourceManager
d) Ce n'est pas possible en cluster mode
💡 Voir la réponse
✅ Réponse : b — On utilise kubectl port-forward, un Service K8s, ou un Ingress pour exposer le Spark UI.
❓ Q8. Pourquoi le shuffle est-il plus coûteux sur K8s que sur YARN/HDFS ?
a) K8s est plus lent
b) Pas de data locality — les données doivent transiter par le réseau
c) Spark n'est pas optimisé pour K8s
d) Les executors ont moins de mémoire
💡 Voir la réponse
✅ Réponse : b — Sur YARN/HDFS, les données peuvent être locales. Sur K8s avec Object Storage, tout passe par le réseau.
❓ Q9. Quel est le rôle du ServiceAccount dans Spark on K8s ?
a) Stocker les credentials
b) Permettre au Driver de créer des Executor Pods
c) Gérer le Spark UI
d) Configurer le réseau
💡 Voir la réponse
✅ Réponse : b — Le ServiceAccount donne au Driver les permissions RBAC pour créer, lister et supprimer des Pods (executors).
❓ Q10. Comment économiser sur les coûts compute avec Spark on K8s ?
a) Utiliser moins d'executors
b) Désactiver le monitoring
c) Utiliser des Spot/Preemptible instances pour les executors
d) Ne pas utiliser de PVC
💡 Voir la réponse
✅ Réponse : c — Les Spot instances peuvent coûter jusqu'à 90% moins cher. Le Driver reste sur des nodes stables, les executors sur Spot.
📚 Ressources pour aller plus loin
🌐 Documentation officielle
📖 Articles & Tutoriels
➡️ Prochaine étape
Maintenant que tu sais déployer Spark sur Kubernetes, passons au Cloud et Object Storage !
👉 Module suivant : 22_cloud_object_storage — Cloud & Object Storage
Tu vas apprendre :
- Cloud Computing : IaaS, PaaS, SaaS
- AWS, GCP, Azure : Services Data Engineering
- Object Storage : S3, GCS, Azure Blob
- MinIO : Pratiquer localement
📝 Récapitulatif de ce module
| Concept | Ce que tu as appris |
|---|---|
| Architecture | Driver/Executor Pods, Client vs Cluster mode |
| Docker Image | Build, JARs, best practices |
| K8s Config | RBAC, Secrets, PVC |
| spark-submit | Configurations essentielles |
| Spark Operator | SparkApplication, ScheduledSparkApplication |
| Autoscaling | Dynamic Allocation, KEDA |
| Monitoring | Spark UI, Prometheus, Grafana |
| Debugging | Erreurs courantes, commandes kubectl |
| Optimisations | S3A config, Pod templates, Spot instances |
🎉 Félicitations ! Tu as terminé le module Spark on Kubernetes.
pythonVoir le code
# Commandes de nettoyage
cleanup_commands = """
# Supprimer les ressources Spark
kubectl delete sparkapplication --all -n spark
kubectl delete namespace spark
# Supprimer Spark Operator
helm uninstall spark-operator -n spark-operator
kubectl delete namespace spark-operator
# Supprimer MinIO
helm uninstall minio -n minio
kubectl delete namespace minio
# Arrêter Minikube (optionnel)
minikube stop
"""
print(cleanup_commands)