Kubernetes Patterns
Modèles Kubernetes de qualité production pour déployer, gérer et déboguer des charges de travail de manière fiable.
Quand Activer
- Rédiger des manifests Kubernetes (Deployments, Services, Ingress, Jobs)
- Configurer les demandes/limites de ressources, les sondes liveness/readiness
- Mettre en place RBAC, namespaces ou ServiceAccounts
- Gérer la configuration et les secrets dans K8s
- Déboguer CrashLoopBackOff, OOMKilled, pods en attente ou erreurs de pull d'image
- Configurer HPA (Horizontal Pod Autoscaler) ou PodDisruptionBudgets
- Vérifier la sécurité ou la correction de YAML K8s
Quand Utiliser
Identique à Quand Activer ci-dessus. Cet alias satisfait aux conventions du format skill du dépôt. Utilisez cette compétence chaque fois que vous écrivez, révisez ou déboguez du YAML et des charges de travail Kubernetes.
Comment ça Marche
Cette compétence fournit des modèles YAML prêts à l'emploi, de qualité production et des commandes de débogage kubectl organisés par tâche :
- Modèle de Déploiement — Un
Deploymentde production entièrement configuré avec contexte de sécurité, stratégie de mise à jour progressive, les trois types de sondes, limites de ressources et injection d'environnement depuis ConfigMap/Secret. - Sondes — Tableau de décision pour startup vs liveness vs readiness, avec le calcul correct
failureThreshold × periodSeconds. - Services & Ingress — Modèles ClusterIP, LoadBalancer et Ingress TLS avec annotations cert-manager.
- ConfigMaps & Secrets — Conseils sur
envFrom, montage de fichiers et secrets externes. - Gestion des ressources — Règles empiriques pour demandes vs limites par type de charge de travail (API web, JVM, worker, sidecar).
- RBAC — Chaîne ServiceAccount → Role → RoleBinding au moindre privilège.
- HPA & PDB — Configurations pour l'autoscaling et la sécurité lors des drains de nœuds.
- Jobs & CronJobs — Modèles pour charges de travail ponctuelles et planifiées avec
restartPolicycorrect. - Aide-mémoire kubectl — Commandes pour logs, exec, rollback, port-forward, dry-run et diagnostic d'erreurs courantes.
- Anti-modèles & liste de contrôle — Ce qu'il NE PAS faire, et une checklist de sécurité/fiabilité/observabilité.
Exemples
Voir les sections ci-dessous pour des exemples complets et exécutables. Références rapides :
| Tâche | Aller à |
|---|---|
| YAML de Déploiement production complet | Modèles de Charge de Travail de Base |
| Configuration des sondes | Sondes |
| Configuration RBAC au moindre privilège | RBAC |
| Déboguer un CrashLoopBackOff | Aide-mémoire de Débogage kubectl |
| Autoscaling | HPA |
Modèles de Charge de Travail de Base
Déploiement — Modèle de Production
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Autorise 1 pod supplémentaire pendant la mise à jour
maxUnavailable: 0 # Ne jamais descendre en dessous du nombre souhaité
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Contexte de sécurité au niveau du pod
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# Arrêt gracieux
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # Ne jamais utiliser :latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# Les demandes ET les limites de ressources sont obligatoires
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# Contexte de sécurité du conteneur
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# Sondes (voir la section Sondes ci-dessous)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# Environnement depuis ConfigMap et Secret
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# Répertoire tmp accessible en écriture quand readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Sondes — Liveness, Readiness, Startup
Comprendre quand utiliser chaque sonde est crucial :
| Sonde | Action en cas d'échec | Utilisation |
|---|---|---|
startupProbe | Tue le conteneur si lent à démarrer | Applications à démarrage lent (JVM, Python) |
livenessProbe | Redémarre le conteneur | Détection d'interblocage / processus bloqué |
readinessProbe | Retire des points de terminaison du Service | Indisponibilité temporaire (reconnexion DB) |
# Modèle correct : startupProbe couvre le démarrage lent,
# puis liveness/readiness prennent le relais
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 150s de temps max de démarrage
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 90s avant redémarrage
readinessProbe:
httpGet:
path: /ready # Point de terminaison séparé : vérifie DB, cache, etc.
port: 8080
periodSeconds: 10
failureThreshold: 2
# INVALIDE : initialDelaySeconds sans startupProbe
# Si l'application met 60s à démarrer, utilisez plutôt une startupProbe
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # MAUVAIS : Attente arbitraire, condition de course
Services et Ingress
Types de Service
# ClusterIP (par défaut) — interne uniquement
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — trafic externe (fournisseurs de cloud)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
Ingress avec TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMaps et Secrets
ConfigMap — Configuration non sensible
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# Monté comme fichier pour une configuration complexe
app.yaml: |
server:
port: 8080
timeout: 30s
# Monter ConfigMap comme un fichier
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secrets — Données sensibles
# Créer un secret à partir de littéraux (CLI, puis stocker dans Vault/SOPS)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# Les valeurs sont encodées en base64 (PAS chiffrées — utilisez Sealed Secrets ou ESO pour un vrai chiffrement)
data:
db-password: czNjcjN0 # base64 de 's3cr3t'
Important : Les Secrets Kubernetes bruts ne sont qu'encodés en base64, pas chiffrés au repos sauf si votre cluster a un chiffrement configuré. Utilisez Sealed Secrets ou External Secrets Operator pour la production.
Demandes et Limites de Ressources
resources:
requests: # Le planificateur utilise ceci pour placer le pod
cpu: "100m" # 100 millicores = 0.1 CPU
memory: "128Mi"
limits: # Le conteneur est tué/limite au-dessus de ce seuil
cpu: "500m"
memory: "256Mi"
Règles empiriques :
| Type de Charge de Travail | Demande CPU | Demande Mémoire | Remarques |
|---|---|---|---|
| API Web | 100–250m | 128–256Mi | Définissez les limites 2 à 4 fois les demandes |
| Worker/consommateur | 250–500m | 256–512Mi | Limite mémoire = demande pour la prévisibilité |
| Application JVM | 500m–1 | 512Mi–2Gi | Laissez de la marge au-dessus de -Xmx pour la surcharge JVM |
| Sidecar | 10–50m | 32–64Mi | Maintenez au minimum |
# INVALIDE : Pas de demandes ni de limites — planification imprévisible, évictions OOM
containers:
- name: app
image: myapp:latest
# Missing resources: {} — dangereux en production
# INVALIDE : Limites sans demandes — les demandes prennent les valeurs des limites, sur-réserve de capacité
resources:
limits:
cpu: "2"
memory: "1Gi"
# demandes manquantes — prendront par défaut les valeurs des limites
RBAC — Rôles et ServiceAccounts
Principe du Moindre Privilège
Deux modèles selon si l'application appelle l'API Kubernetes :
Modèle A — L'application n'a PAS besoin de l'API Kubernetes (la plupart des applications)
Désactivez le montage automatique du token pour le ServiceAccount. Le Role/RoleBinding ne sont pas nécessaires.
# ServiceAccount avec token désactivé — valeur par défaut la plus sûre
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # Aucun token API K8s injecté dans les pods
# Référence dans le déploiement — pas de token, pas d'accès API
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # Double sécurité : également défini au niveau du pod
Modèle B — L'application a BESOIN de l'API Kubernetes (opérateurs, contrôleurs, observateurs de configuration)
Activez le token et n'accordez que les permissions réellement nécessaires.
# 1. ServiceAccount — activer le token pour ce SA
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # Token requis : l'application appelle l'API K8s
# 2. Role — n'accorder que ce dont l'application a besoin (scope namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # Lecture seule, ressource spécifique
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # Restreindre au secret spécifique par nom
verbs: ["get"]
# 3. Lier le Role au ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. Référencer SA dans le déploiement
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountToken par défaut à true depuis SA — le token est injecté
Horizontal Pod Autoscaler (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # Toujours au moins 2 pour la HA
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Monter en charge quand CPU moyen > 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPA nécessite que
resources.requestssoit défini sur tous les conteneurs — il calcule l'utilisation commeactuel / demande.
PodDisruptionBudget (PDB)
Empêcher que trop de pods ne tombent pendant les drains de nœuds ou les mises à jour progressives :
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # OU utiliser maxUnavailable: 1
selector:
matchLabels:
app: my-app
Namespaces et Multitenance
# Créer un namespace avec des quotas de ressources
kubectl create namespace my-namespace
# Appliquer ResourceQuota pour limiter la consommation du namespace
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
Jobs et CronJobs
# Job ponctuel (migration DB, traitement de données)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # Réessayer jusqu'à 3 fois en cas d'échec
ttlSecondsAfterFinished: 3600 # Auto-suppression après 1h
template:
spec:
restartPolicy: OnFailure # Jamais pour les Jobs (pas Always)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# CronJob (tâche planifiée)
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-job
namespace: my-namespace
spec:
schedule: "0 2 * * *" # Tous les jours à 2h du matin
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: ghcr.io/org/cleanup:1.0.0
command: ["cleanup", "--days=7"]
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
Aide-mémoire de Débogage kubectl
# Voir l'état des pods (descriptions complètes)
kubectl get pods -n my-namespace
kubectl describe pod <pod-name> -n my-namespace
# Logs
kubectl logs <pod-name> -n my-namespace
kubectl logs --tail=50 <pod-name> -n my-namespace # Dernières 50 lignes
kubectl logs -f <pod-name> -n my-namespace # Suivre les logs
kubectl logs <pod-name> -c <container-name> -n my-namespace # Conteneur spécifique
# Exécuter des commandes dans un conteneur
kubectl exec -it <pod-name> -n my-namespace -- /bin/sh
kubectl exec -it <pod-name> -c <container-name> -n my-namespace -- /bin/bash
# Port-Forward local (débogage sans Service)
kubectl port-forward pod/<pod-name> 8080:8080 -n my-namespace
kubectl port-forward deployment/my-app 8080:8080 -n my-namespace
# Voir les événements triés par heure
kubectl get events -n my-namespace --sort-by='.lastTimestamp'
kubectl get events -n my-namespace --field-selector involvedObject.name=<pod-name>
# Analyse YAML (dry-run en local)
kubectl run my-pod --image=nginx --dry-run=client -o yaml
kubectl create deployment my-app --image=my-app --dry-run=client -o yaml
# Rollback du déploiement
kubectl rollout undo deployment/my-app -n my-namespace
kubectl rollout history deployment/my-app -n my-namespace # Voir l'historique des révisions
# Test de connectivité du Service (pod éphémère dans le cluster)
kubectl run tmp --image=nicolaka/netshoot -it --rm -- curl http://service-name:8080
# Diagnostic des pods bloqués
kubectl describe pod <pending-pod>
kubectl get pod <pending-pod> -o yaml # Regarder status.conditions
# Voir l'utilisation des ressources des nœuds
kubectl top nodes
kubectl top pods -n my-namespace
Anti-modèles et Liste de Contrôle
Ce qu'il NE PAS Faire (Anti-modèles)
- Ne jamais utiliser d'image
:latest— déclarez toujours un tag sémantique ou un hash d'image. - Ne jamais exécuter en tant que root — définissez
runAsNonRoot: trueet définissezrunAsUser. - Ne jamais omettre les demandes de ressources — le planificateur et HPA en ont besoin.
- Ne pas définir de limites sans demandes — vous sur-réservez la capacité de la grappe.
- Ne pas utiliser de token ServiceAccount si votre application ne lit pas l'API K8s — désactivez-le.
- Ne pas utiliser
initialDelaySecondspour les démarrages lents — utilisez plutôtstartupProbe. - Ne pas monter le Secret entier si un seul champ est nécessaire — utilisez
keyRef. - Ne pas exposer les services sur NodePort en production — utilisez LoadBalancer ou Ingress.
- Ne pas ignorer les pods en CrashLoopBackOff — examinez
kubectl logsetkubectl describe. - Ne pas oublier de configurer PodDisruptionBudget pour les charges de travail avec état ou critiques.
Liste de Contrôle de Production
Sécurité :
- runAsNonRoot: true
- readOnlyRootFilesystem: true (avec volume tmp writable)
- capabilities: drop: [ALL]
- allowPrivilegeEscalation: false
- automountServiceAccountToken: false (sauf si l'application appelle l'API)
- Les rôles RBAC sont spécifiques à une ressource et en lecture seule si possible
Fiabilité :
- HPA configuré avec minReplicas >= 2
- PodDisruptionBudget configuré
- startupProbe pour les applications à démarrage lent
- readinessProbe avec un point de terminaison /ready séparé
- rollingUpdate maxSurge et maxUnavailable corrects
- backoffLimit sur les Jobs
Observabilité :
- readinessProbe connecte la base de données/cache
- Les logs sont stdout/stderr (pas de fichiers)
- Les métriques Prometheus sont exposées sur un port séparé
- terminationGracePeriodSeconds >= temps de vidange attendu
Contrôle des ressources :
- Les demandes CPU sont basées sur l'utilisation au repos
- Les limites CPU sont 2 à 4 fois les demandes
- Les demandes mémoire sont basées sur le pic d'utilisation
- Les limites mémoire = demandes (pour la prévisibilité, sauf JVM)