Un volume hostPath monte un fichier ou répertoire du nœud hôte directement dans le Pod.
Exemple basique
apiVersion: v1
kind: Pod
metadata:
name: mon-pod
spec:
containers:
- name: mon-app
image: nginx
volumeMounts:
- mountPath: /data
name: host-volume
volumes:
- name: host-volume
hostPath:
path: /mnt/data # Chemin sur le nœud
type: DirectoryOrCreate
Les types disponibles
| Type | Comportement |
|---|---|
"" (vide) |
Aucune vérification, chemin doit exister |
Directory |
Le répertoire doit déjà exister |
DirectoryOrCreate |
Crée le répertoire s’il n’existe pas |
File |
Le fichier doit déjà exister |
FileOrCreate |
Crée le fichier s’il n’existe pas |
Socket |
Un socket Unix doit exister |
CharDevice |
Un périphérique caractère doit exister |
BlockDevice |
Un périphérique bloc doit exister |
Cas d’usage typiques
# Accès aux logs du nœud
volumes:
- name: logs
hostPath:
path: /var/log
type: Directory
# Accès au socket Docker
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
type: Socket
# Accès au socket containerd
volumes:
- name: containerd-socket
hostPath:
path: /run/containerd/containerd.sock
type: Socket
⚠️ Limitations importantes
Pas partagé entre nœuds
Node 1 Node 2
/mnt/data ≠ /mnt/data
(Pod A) (Pod B)
Si le Deployment a plusieurs réplicas sur des nœuds différents, chaque Pod voit son propre /mnt/data local.
Risques de sécurité
Comparaison avec les autres volumes
| Volume | Persistance | Partagé entre nœuds | Usage |
|---|---|---|---|
hostPath |
Oui (sur le nœud) | ❌ Non | Debug, DaemonSet |
emptyDir |
Non (vie du Pod) | ❌ Non | Cache temporaire |
PVC |
Oui | ✅ Possible | Production |
configMap |
N/A | ✅ Oui | Configuration |
Quand l’utiliser ?
hostPath est cohérentdocker.sock, containerd.sock)emptyDir — Stockage lié au Pod
Pod démarre → emptyDir créé (vide)
Pod s'arrête → emptyDir détruit ✗
hostPath — Stockage lié au Nœud
Pod démarre → hostPath monté depuis /mon/chemin
Pod s'arrête → /mon/chemin reste intact sur le nœud ✓
Pod redémarre → retrouve les mêmes fichiers ✓
Analogie concrète
| emptyDir | hostPath | |
|---|---|---|
| 🏨 Analogie | Tableau blanc dans une salle de réunion — effacé après la réunion | Clé USB branchée sur l’ordi — les fichiers restent après le meeting |
| Créé par | Kubernetes automatiquement | L’opérateur, sur le nœud |
| Survit au Pod ? | ❌ Non | ✅ Oui |
| Chemin connu ? | ❌ Non | ✅ Oui (/mon/chemin) |
Exemple visuel
# emptyDir
Pod A (vivant) Pod A (mort)
┌──────────────┐ ┌──────────────┐
│ /tmp/cache │ →→→ │ DÉTRUIT │
│ [fichiers] │ │ │
└──────────────┘ └──────────────┘
# hostPath
Pod A (vivant) Pod A (mort) Pod A (redémarré)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ /data │ │ DÉTRUIT │ │ /data │
│ [fichiers] │ →→→ │ │ →→→ │ [fichiers] │ ✓
└──────────────┘ └──────────────┘ └──────────────┘
│ │
└──────── /mnt/data sur le nœud ────────────┘
(toujours là)
En une phrase :
emptyDir = mémoire temporaire du Pod (disparaît avec lui)hostPath = disque dur du nœud (persiste après lui)Non, pas besoin de le créer manuellement → avec DirectoryOrCreate ou FileOrCreate
volumes:
- name: mon-volume
hostPath:
path: /mnt/data
type: DirectoryOrCreate # Kubernetes crée le dossier si absent
Oui, le dossier doit exister → avec Directory ou "" (vide)
volumes:
- name: mon-volume
hostPath:
path: /mnt/data
type: Directory # Erreur si le dossier n'existe pas !
Résumé rapide
| Type | Dossier doit exister ? |
|---|---|
DirectoryOrCreate |
❌ Non, créé automatiquement |
FileOrCreate |
❌ Non, créé automatiquement |
Directory |
✅ Oui obligatoire |
File |
✅ Oui obligatoire |
"" (vide) |
✅ Oui obligatoire |
Recommandation
En pratique, on utilise presque toujours DirectoryOrCreate pour éviter les erreurs de démarrage du Pod :
hostPath:
path: /mnt/data
type: DirectoryOrCreate # ← safe par défaut
Si le dossier existe déjà, Kubernetes le monte simplement sans le modifier.
S3 (Simple Storage Service) est un service de stockage objet créé par Amazon Web Services en 2006.
Le concept de stockage objet
Contrairement à un système de fichiers classique, S3 ne stocke pas des fichiers dans des dossiers hiérarchiques, mais des objets dans des buckets :
Système de fichiers classique vs S3
───────────────────────────── ──────────────────────
/home/ Bucket: mon-bucket
└── photos/ ├── photo1.jpg
├── photo1.jpg ├── photos/photo2.jpg
└── photo2.jpg └── docs/rapport.pdf
Tout est à plat — les / dans les noms donnent l’illusion de dossiers, mais ce sont juste des caractères dans la clé.
Les 3 concepts fondamentaux
| Concept | Description | Analogie |
|---|---|---|
| Bucket | Conteneur principal | Disque dur |
| Object | Fichier + métadonnées | Fichier |
| Key | Identifiant unique de l’objet | Chemin du fichier |
Ce qu’on peut stocker
L’API S3 est devenue un standard
C’est là où S3 est vraiment important — son API HTTP est devenue le standard universel du stockage objet. Tous ces outils sont compatibles S3 :
AWS S3 → l'original
MinIO → self-hosted
Garage → self-hosted distribué
Cloudflare R2 → compatible S3
Backblaze B2 → compatible S3
Google GCS → compatible S3
Azure Blob → partiellement compatible
Ce qui veut dire que le même code fonctionne partout :
import boto3
s3 = boto3.client("s3",
endpoint_url="http://localhost:9000", # MinIO local
# endpoint_url="https://s3.amazonaws.com", # AWS S3
aws_access_key_id="minioadmin",
aws_secret_access_key="minioadmin"
)
# Upload
s3.upload_file("photo.jpg", "mon-bucket", "photos/photo.jpg")
# Download
s3.download_file("mon-bucket", "photos/photo.jpg", "local.jpg")
# Lister
response = s3.list_objects_v2(Bucket="mon-bucket")
Comparaison avec les autres types de stockage
| Type | Exemple | Accès | Idéal pour |
|---|---|---|---|
| Objet (S3) | AWS S3, MinIO | HTTP/API | Fichiers statiques, backups, médias |
| Bloc | EBS, PVC K8S | Système de fichiers | Bases de données, OS |
| Fichier | NFS, EFS | Système de fichiers | Partage entre serveurs |
En résumé
Kubernetes et S3 sont complémentaires — ils résolvent des problèmes différents et s’utilisent souvent ensemble.
Le problème fondamental
K8S gère des Pods éphémères S3 stocke des données persistantes
────────────────────────── ─────────────────────────────────
Pod démarre ┐ Objet uploadé ┐
Pod travaille│ Objet existe │ forever...
Pod meurt ┘ ← données perdues! Objet récupéré ┘
K8S ne résout pas tout le stockage
| Besoin | Solution K8S native | Problème |
|---|---|---|
| Stockage temporaire | emptyDir |
Perdu à la mort du Pod |
| Stockage sur le nœud | hostPath |
Lié à un seul nœud |
| Stockage persistant | PVC |
Complexe, souvent bloqué sur un nœud |
| Stockage partagé entre tous les Pods | ❌ Pas natif | → C’est là que S3 entre en jeu |
Comment ils s’utilisent ensemble
┌─────────────────────────────────────────┐
│ Cluster Kubernetes │
│ │
│ Pod A ──┐ │
│ ├──→ SDK S3 ──→ S3 / MinIO │
│ Pod B ──┘ (HTTP) / Garage │
│ │ │
│ Pod C ──────────────────────┘ │
└─────────────────────────────────────────┘
Tous les Pods partagent
le même stockage via HTTP
Les cas d’usage typiques
1. Partage de fichiers entre Pods
Pod upload (réplica 1) ──→ S3 ←── Pod download (réplica 2)
Impossible avec hostPath ou emptyDir !
2. Stockage de médias
# L'app uploade les images user vers S3
env:
- name: STORAGE_BACKEND
value: "s3"
- name: S3_BUCKET
value: "user-uploads"
3. Backups de base de données
# Un CronJob K8S qui sauvegarde PostgreSQL vers S3
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
spec:
schedule: "0 2 * * *" # Chaque nuit à 2h
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:15
command:
- /bin/sh
- -c
- |
pg_dump $DATABASE_URL | \
aws s3 cp - s3://backups/$(date +%Y%m%d).sql
4. Logs centralisés
Pod A ──→ ┐
Pod B ──→ ├──→ Fluentd/Vector ──→ S3 (logs archivés)
Pod C ──→ ┘
5. Assets statiques (CDN)
Build CI/CD ──→ S3 ──→ CloudFront/CDN ──→ Utilisateurs
↑
K8S Job d'upload
Vue d’ensemble
KUBERNETES
┌──────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Pod App │ │ CronJob │ │
│ │ (nginx) │ │ (backup) │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌────▼───────────────▼─────┐ │
│ │ S3 Service │ │
│ │ (MinIO / Garage) │ │
│ └────────────┬──────────────┘ │
│ │ │
│ ┌────────────▼──────────────┐ │
│ │ PVC → Stockage disque │ │
│ └───────────────────────────┘ │
└──────────────────────────────────────────┘
En résumé
| Kubernetes | S3 | |
|---|---|---|
| Rôle | Orchestrer les apps | Stocker les données |
| Durée | Éphémère (Pods) | Permanent |
| Accès | Interne au cluster | HTTP depuis n’importe où |
| Scalabilité | Horizontal (réplicas) | Illimitée |
K8S fait tourner tes applications, S3 garde leurs données — ils se complètent naturellement.
Voici un exemple de PVC avec un stockage S3 local via MinIO (l’implémentation S3 la plus courante en local sur K8S) :
1. Déployer MinIO
# minio-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: storage
# minio-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
namespace: storage
spec:
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: minio/minio:latest
args:
- server
- /data
- --console-address
- ":9001"
env:
- name: MINIO_ROOT_USER
value: "minioadmin"
- name: MINIO_ROOT_PASSWORD
value: "minioadmin"
ports:
- containerPort: 9000 # API S3
- containerPort: 9001 # Console web
volumeMounts:
- name: minio-storage
mountPath: /data
volumes:
- name: minio-storage
persistentVolumeClaim:
claimName: minio-pvc
---
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: storage
spec:
selector:
app: minio
ports:
- name: api
port: 9000
targetPort: 9000
- name: console
port: 9001
targetPort: 9001
2. PersistentVolume + PVC pour MinIO
# minio-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio-pv
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /mnt/minio-data # Chemin sur le nœud
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-pvc
namespace: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
3. Utiliser MinIO depuis un Pod applicatif
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mon-app
spec:
replicas: 2
selector:
matchLabels:
app: mon-app
template:
metadata:
labels:
app: mon-app
spec:
containers:
- name: mon-app
image: mon-app:latest
env:
- name: S3_ENDPOINT
value: "http://minio.storage.svc.cluster.local:9000"
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-secret
key: access-key
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-secret
key: secret-key
- name: S3_BUCKET
value: "mon-bucket"
4. Secret pour les credentials
# minio-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
type: Opaque
stringData:
access-key: minioadmin
secret-key: minioadmin
Architecture résultante :
Pods applicatifs (replicas)
│
│ SDK S3 standard
▼
MinIO Service (ClusterIP)
minio.storage.svc:9000
│
▼
MinIO Pod
│
▼
PVC → PV → /mnt/minio-data (nœud)
Points clés :
hostPath par un StorageClass distribué (Longhorn, Rook-Ceph)Voici un exemple avec Garage, une alternative légère et distribuée à MinIO, très adaptée pour un cluster K8S multi-nœuds :Garage est un stockage objet distribué compatible S3, conçu pour le self-hosting à petite et moyenne échelle. Voici un exemple complet de déploiement sur Kubernetes :
1. Namespace + Secret RPC
apiVersion: v1
kind: Namespace
metadata:
name: garage
---
apiVersion: v1
kind: Secret
metadata:
name: garage-rpc-secret
namespace: garage
type: Opaque
stringData:
rpc-secret: "votre_secret_hex_32_bytes" # openssl rand -hex 32
2. ConfigMap (garage.toml)
apiVersion: v1
kind: ConfigMap
metadata:
name: garage-config
namespace: garage
data:
garage.toml: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"
replication_factor = 3
compression_level = 2
rpc_bind_addr = "[::]:3901"
rpc_secret = "votre_secret_hex_32_bytes"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "votre_admin_token"
3. StatefulSet (3 réplicas distribués)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: garage
namespace: garage
spec:
serviceName: garage
replicas: 3
selector:
matchLabels:
app: garage
template:
metadata:
labels:
app: garage
spec:
containers:
- name: garage
image: dxflrs/garage:v1.0.0
ports:
- containerPort: 3900 # S3 API
- containerPort: 3901 # RPC
- containerPort: 3903 # Admin
volumeMounts:
- name: garage-config
mountPath: /etc/garage.toml
subPath: garage.toml
- name: meta
mountPath: /var/lib/garage/meta
- name: data
mountPath: /var/lib/garage/data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "1000m"
volumes:
- name: garage-config
configMap:
name: garage-config
volumeClaimTemplates:
- metadata:
name: meta
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
4. Services
# Service headless pour le RPC inter-noeuds
apiVersion: v1
kind: Service
metadata:
name: garage
namespace: garage
spec:
clusterIP: None
selector:
app: garage
ports:
- name: rpc
port: 3901
---
# Service S3 pour les applications
apiVersion: v1
kind: Service
metadata:
name: garage-s3
namespace: garage
spec:
selector:
app: garage
ports:
- name: s3
port: 3900
targetPort: 3900
- name: admin
port: 3903
targetPort: 3903
5. Initialisation du cluster (après déploiement)
# Vérifier le statut
kubectl exec -n garage garage-0 -- ./garage status
# Assigner chaque nœud à une zone et une capacité
kubectl exec -n garage garage-0 -- ./garage layout assign \
-z zone1 -c 50G <node-id-0>
kubectl exec -n garage garage-0 -- ./garage layout assign \
-z zone2 -c 50G <node-id-1>
kubectl exec -n garage garage-0 -- ./garage layout assign \
-z zone3 -c 50G <node-id-2>
# Appliquer le layout
kubectl exec -n garage garage-0 -- ./garage layout apply --version 1
# Créer un bucket
kubectl exec -n garage garage-0 -- ./garage bucket create mon-bucket
# Créer une clé d'accès
kubectl exec -n garage garage-0 -- ./garage key create ma-cle
# Lier la clé au bucket
kubectl exec -n garage garage-0 -- ./garage bucket allow \
--read --write --owner mon-bucket --key ma-cle
6. Utilisation depuis un Pod applicatif
env:
- name: S3_ENDPOINT
value: "http://garage-s3.garage.svc.cluster.local:3900"
- name: S3_REGION
value: "garage"
- name: S3_BUCKET
value: "mon-bucket"
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: garage-app-secret
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-app-secret
key: secret-key
Architecture résultante :
Pods applicatifs
│ SDK S3 standard
▼
garage-s3 (Service ClusterIP :3900)
│
├── garage-0 (zone1) ──┐
├── garage-1 (zone2) ├── Réplication x3
└── garage-2 (zone3) ──┘
│ │
PVC PVC
(meta) (data)
Différences clés vs MinIO :
| Garage | MinIO | |
|---|---|---|
| Licence | AGPL v3 (100% libre) | AGPL v3 / commerciale |
| Multi-zones | Natif | Complexe |
| Légèreté | Très léger (Rust) | Plus lourd |
| Gestion des clés | Entités indépendantes assignables aux buckets | Liées aux utilisateurs IAM |
| Maturité | Plus récent | Plus mature |
La méthode recommandée par l’équipe Garage est d’utiliser leur chart Helm officiel, qui simplifie grandement le déploiement :
helm install --create-namespace --namespace garage \
garage ./garage -f values.override.yaml