Les cours sont maintenus et donnés par : https://www.alterway.fr/formation

kubectl : Le CLI officiel indispensable.k9s : Interface TUI ultra-rapide pour explorer et déboguer le cluster en temps réel.helm & kustomize : Pour packager et spécialiser les manifestes YAML.kubectx / kubens : Pour switcher de cluster et de namespace en 1 seconde.# Alias incontournable pour les pros :
alias k=kubectl
complete -o default -F __start_kubectl k
[ Sandbox ] ──► [ Incubating ] ──► [ Graduated ]
(Expérimentation) (Adoption active) (Production Ready &
Gouvernance éprouvée)
Question flash : Dans quel statut CNCF se situent ces projets incontournables ?
Question flash : Dans quel statut CNCF se situent ces projets incontournables ?
Réponse : Kubernetes, ArgoCD et Cilium sont Graduated ! OpenTelemetry est Incubating en voie rapide vers la graduation.
┌─────────────────────────────────────────────────────────────┐
│ CONTROL PLANE (Cerveau) │
│ [ etcd ] ◄──► [ kube-apiserver ] ◄──► [ Controllers ] │
│ ▲ │
│ │ (Reconcile loop & Scheduler) │
└────────────────────────┼────────────────────────────────────┘
|
│ HTTPS / TLS (Port 6443)
|
┌────────────────────────┼────────────────────────────────────┐
│ ▼ │
│ WORKER NODE 1 WORKER NODE 2 │
│ [ kubelet ] ──► [ CRI ] [ kubelet ] ──► [ CRI ] │
│ [ kube-proxy / eBPF ] [ kube-proxy / eBPF ] │
│ [ Pods / Containers ] [ Pods / Containers ] │
└─────────────────────────────────────────────────────────────┘
etcd : Le Cœur Persistantkube-apiserver dialogue avec etcd. Aucun worker ni utilisateur n'y accède directement.kube-apiserver : L'Unique Porte d'EntréeToute action (kubectl, CI/CD, Kubelet) passe par un pipeline strict en 4 étapes :
Requête HTTP ──► [1. Authentication] ──► [2. Authorization (RBAC)]
│
▼
[3. Mutation & Validation (Admission Webhooks)]
│
▼
[4. Sauvegarde dans etcd]
watch) vers tous les autres contrôleurs.
kube-controller-manager : La Boucle de RéconciliationLa promesse fondamentale de Kubernetes : Déclaratif vs Réel.
┌───────────────────────────────────────────────┐
│ LA BOUCLE INFINIE DE CONTRÔLE │
│ │
│ 1. Observer l'état réel actuel │
│ 2. Comparer avec l'état désiré (YAML) │
│ 3. Agir pour résorber l'écart (Reconcile)│
└───────────────────────────────────────────────┘
kube-scheduler : L'Aiguilleur IntelligentComment un Pod trouve-t-il son Node d'hébergement ?
[ Nouveau Pod non assigné (nodeName="") ]
│
▼
1. Filtrage (Predicates / Filtering)
(Élimine les nœuds sans CPU/RAM, taints incompatibles...)
│
▼
2. Notation (Priorities / Scoring)
(Classe les nœuds restants selon affinité, étalement des charges...)
│
▼
[ Assignation au nœud avec le meilleur score ]
kubelet : L'agent de bord du nœud. Reçoit les ordres de l'APIServer, pilote le runtime conteneur et remonte la santé du nœud.CRI (Container Runtime Interface) : Le moteur bas niveau (containerd, CRI-O) qui instancie les namespaces Linux et cgroups.kube-proxy / eBPF : Configure le routage réseau local et le load balancing entre Pods.kube-proxy, routage directement dans le noyau sans surcoût iptables !Kubernetes impose 3 règles absolues à tous les plugins réseau (CNI) :
Exemples de CNI majeurs : Cilium (eBPF & haute performance), Calico (politiques de sécurité BGP/IP-in-IP), Flannel (overlay simple).
Scénario 1 : Si le kube-apiserver devient inaccessible pendant 10 minutes, que deviennent les Pods applicatifs en cours d'exécution ?
Scénario 2 : Si le kube-scheduler crashe, que se passe-t-il lors d'un kubectl apply d'un nouveau Deployment ?
Scénario 1 : Si le kube-apiserver devient inaccessible pendant 10 minutes, que deviennent les Pods applicatifs en cours d'exécution ?
Réponse : Ils continuent de tourner normalement ! Seules les opérations de modification et le redémarrage en cas de crash sont gelés.
Scénario 2 : Si le kube-scheduler crashe, que se passe-t-il lors d'un kubectl apply d'un nouveau Deployment ?
Réponse : Le Deployment et les Pods sont créés dans etcd, mais les Pods restent bloqués en état Pending.
| Environnement | Outils Recommandés | Cas d'Usage Principal |
|---|---|---|
| Dev Local & CI/CD | KinD, k3d / k3s, Minikube |
Tester des manifests, reproduire un cluster multi-nœuds en 30s |
| On-Premise / Bare Metal | kubeadm, kubespray (Ansible), Cluster API |
Contrôle total du matériel, isolation souveraine |
| Cloud Managé (Public Cloud) | GKE, EKS, AKS, Kapsule |
Délégation du Control Plane, autoscaling nœuds cloud natif |
# Cluster multi-nœuds en local en 1 commande avec KinD
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOFkubeadmkubeadm est l'outil officiel standard pour initialiser un cluster de production :
[ Étape 1 : Control Plane ]
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
|
│
▼ (Génère certificats TLS, etcd, apiserver, et le token de join)
[ Étape 2 : Plugin Réseau (CNI) ]
$ kubectl apply -f https://docs.cilium.io/...
|
│
▼ (Les nœuds passent de NotReady à Ready)
[ Étape 3 : Raccordement des Workers ]
$ sudo kubeadm join 192.168.1.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:...
┌─────────────────────────────────────────────────────────────┐
| |
│ Fournisseur Cloud (GCP/AWS/Azure/Scaleway/OVH...) │
│ ► etcd, kube-apiserver, scheduler, controller-manager │
│ ► Sauvegardes etcd, patchs de sécurité, haute dispo │
| |
├─────────────────────────────────────────────────────────────┤
| |
│ Votre Responsabilité (DevOps / SRE) │
│ ► Gestion des versions de Worker Nodes (Node Pools) │
│ ► Sécurité réseau, RBAC, ingress, dimensionnement (HPA) │
│ ► Vos manifests applicatifs et vos données │
| |
└─────────────────────────────────────────────────────────────┘
Avant de mettre un cluster en production, auditez sa conformité :
kube-bench : Vérifie la stricte conformité avec le CIS Kubernetes Benchmark (droits des fichiers de config, arguments TLS).popeye : Scanner assainisseur qui détecte les ressources orphelines, allocations manquantes et erreurs de config.sonobuoy : Outil officiel de validation de conformité CNCF.# Scanner son cluster avec Popeye en 1 commande :
popeye ___ ___ _____ _____ K .-'-.
| _ \___| _ \ __\ \ / / __| 8 __| `\
| _/ _ \ _/ _| \ V /| _| s `-,-`--._ `\
|_| \___/_| |___| |_| |___| [] .->' a `|-'
Biffs`em and Buffs`em! `=/ (__/_ /
\_, ` _)
`----; |
GENERAL [NEXTERWAY] (2026-08-27T08:43:48+02:00)
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
· Connectivity...................................................................................✅
· MetricServer...................................................................................✅
CILIUMENDPOINTS (0 SCANNED) 💥 0 😱 0 🔊 0 ✅ 0 100٪
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
· Nothing to report.
...
Contexte : Votre entreprise veut monter une chaîne CI/CD GitHub Actions pour tester le déploiement d'opérateurs K8s multi-nœuds à chaque Pull Request.
Question : Quelle solution privilégiez-vous ? 1. Un cluster EKS dédié allumé 24/7. 2. Un cluster local KinD instancié dynamiquement dans le runner de la CI. 3. Une VM avec Minikube.
Contexte : Votre entreprise veut monter une chaîne CI/CD GitHub Actions pour tester le déploiement d'opérateurs K8s multi-nœuds à chaque Pull Request.
Question : Quelle solution privilégiez-vous ? 1. Un cluster EKS dédié allumé 24/7. 2. Un cluster local KinD instancié dynamiquement dans le runner de la CI. 3. Une VM avec Minikube.
Réponse : Option 2 (KinD) ! Démarrage en ~20s, destruction automatique à la fin du job, coût quasi nul et isolation parfaite.
kubectl & Kubeconfigkubectlkube-apiserver.~/.kube/config ou via $KUBECONFIG.# L'indispensable autocomplétion shell (Bash / Zsh)
source <(kubectl completion zsh)
alias k=kubectl
complete -o default -F __start_kubectl kkubeconfigUn kubeconfig regroupe 3 entités distinctes :
CLUSTERS USERS
(URL API + CA TLS) (Certificats / Tokens)
│ │
│ │
└─────────────┬──────────────┘
│
▼
CONTEXTS
(Association Cluster + User + Namespace)
│
│
▼
CURRENT-CONTEXT
(Le contexte actif ciblé)
Au lieu de jongler manuellement avec des fichiers dispersés :
# 1. Fusionner plusieurs kubeconfigs en une seule vue
export KUBECONFIG=~/.kube/config:~/.kube/eks-prod:~/.kube/gke-dev
kubectl config view --flatten > ~/.kube/config.all
mv ~/.kube/config.all ~/.kube/config
# 2. Utiliser kubectx et kubens pour changer en 1 clic
kubectx prod-cluster
kubens billing-appkubectl api-resources : Découvre toutes les ressources supportées (CRDs, versions, abréviations, portée namespace).kubectl explain : La documentation intégrée directement dans votre terminal.# Trouver la structure exacte d'un champ
kubectl explain pod.spec.containers.livenessProbe
# Explorer toute l'arborescence
kubectl explain deployment.spec.strategy --recursive# 1. Vue enrichie immédiate
kubectl get pods -o wide
# 2. Colonnes sur mesure (Custom Columns)
kubectl get pods -o custom-columns=NAME:.metadata.name,IP:.status.podIP,NODE:.spec.nodeName
# 3. Tri avancé (ex: trier les pods par nombre de redémarrages)
kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'
# 4. JSONPath chirurgical (extraire toutes les images utilisées)
kubectl get pods -A -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -uNe cherchez jamais vos pods un par un :
# Ciblage par égalité ou ensemble
kubectl get pods -l 'app=frontend,environment in (production, staging)'
# Afficher les labels directement dans les colonnes
kubectl get pods -L app,version,tierkrewkrew est le gestionnaire de paquets officiel pour étendre kubectl :
kubectl krew install ctx ns neat who-can tree
# kubectl-tree : Visualise toute la hiérarchie d'objets créés par un Deployment
kubectl tree deploy web-app
# kubectl-who-can : Qui a le droit de supprimer des namespaces ?
kubectl who-can delete namespacesObjectif : Vous suspectez une fuite de mémoire sur un cluster de 150 pods.
Quelle commande en une ligne permet de lister le nom de tous les pods ayant déjà redémarré au moins 1 fois, triés par leur nœud ?
Objectif : Vous suspectez une fuite de mémoire sur un cluster de 150 pods.
Quelle commande en une ligne permet de lister le nom de tous les pods ayant déjà redémarré au moins 1 fois, triés par leur nœud ?
# Solution experte :
kubectl get pods --field-selector status.phase=Running \
-o custom-columns=POD:.metadata.name,NODE:.spec.nodeName,RESTARTS:.status.containerStatuses[0].restartCount \
--sort-by='.spec.nodeName' APPROCHE IMPÉRATIVE APPROCHE DÉCLARATIVE
"Fais ceci, puis cela maintenant !" "Voici l'état final que je veux."
(Exécution directe) (GitOps / Versionné)
│ │
▼ ▼
kubectl run / kubectl create kubectl apply -f app.yaml
Rapide en lab / CLI / Urgence Reproductible, auditable, CI/CD
| Critère | Impératif (kubectl create/run/scale) |
Déclaratif (kubectl apply / GitOps) |
|---|---|---|
| Idéal pour | Examen CKA/CKAD, tests jetables, dépannage à chaud | Production, revue de code, déploiements automatisés |
| Historique | Non tracé (hors audit logs) | Versionné dans Git (Source of Truth) |
| Gestion des Dérives | Écrasement partiel sans contexte | Réconciliation automatique (apply 3-way merge) |
Ne tapez jamais un squelette YAML à la main ! Utilisez l'impératif pour générer du déclaratif propre :
# 1. Générer le squelette d'un Pod multi-ports
kubectl run api-server --image=nginx:alpine --port=8080 --dry-run=client -o yaml > pod.yaml
# 2. Générer un Deployment 3 réplicas avec labels
kubectl create deployment web-backend --image=my-api:1.0 --replicas=3 --dry-run=client -o yaml > deploy.yaml
# 3. Générer un Service ClusterIP raccordé au port cible
kubectl expose deployment web-backend --port=80 --target-port=8080 --dry-run=client -o yaml > svc.yaml
# 4. Générer un ConfigMap à partir de variables
kubectl create configmap app-cfg --from-literal=DB_HOST=postgres --dry-run=client -o yaml > cm.yamlQuand vous faites kubectl apply -f manifest.yaml, Kubernetes compare 3 versions :
[ Fichier Local (Nouveau souhait) ]
│
▼
[ Last-Applied-Configuration (Annotation dans l'objet) ]
│
▼
[ État Actuel en Direct dans etcd ]
kubectl diffAvant d'appliquer une modification en production :
# Affiche le diff exact entre le cluster et vos fichiers locaux (couleurs git diff)
kubectl diff -f k8s/production/
# Appliquer avec Server-Side Apply (standard moderne K8s 1.22+)
kubectl apply --server-side -f k8s/production/Objectif : En moins de 30 secondes et sans ouvrir d'éditeur de texte, générez un fichier stack.yaml contenant à la fois un Deployment redis (image redis:alpine) et son Service redis-svc exposant le port TCP 6379.
Objectif : En moins de 30 secondes et sans ouvrir d'éditeur de texte, générez un fichier stack.yaml contenant à la fois un Deployment redis (image redis:alpine) et son Service redis-svc exposant le port TCP 6379.
# Solution en 2 lignes chainées :
kubectl create deployment redis --image=redis:alpine --dry-run=client -o yaml > stack.yaml
echo "---" >> stack.yaml
kubectl create service clusterip redis-svc --tcp=6379:6379 --dry-run=client -o yaml >> stack.yaml┌─────────────────────────────────────────────────────────────┐
│ NAMESPACE │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DEPLOYMENT │ │
│ │ (Gère les Rolling Updates & l'historique) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ REPLICASET │ │
│ │ (Maintient le nombre exact de Pods) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ PODS │ │
│ │ [ App Container | Native Sidecar ] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
localhost, mêmes ports.Fini les bricolages pour attendre qu'un proxy démarre avant votre application principale :
apiVersion: v1
kind: Pod
metadata:
name: secured-webapp
spec:
initContainers:
- name: vault-agent-sidecar
image: hashicorp/vault:latest
restartPolicy: Always # <── Transforme l'initContainer en Sidecar natif !
containers:
- name: app
image: my-app:2.0 LABELS ANNOTATIONS
Utilisés par Kubernetes pour sélectionner Utilisés par des outils tiers
(Sélecteurs, Services, RBAC) (Monitoring, CI/CD, Git commit)
│ │
▼ ▼
- app: bookstore - git-commit: 7f8a92b
- env: production - prometheus.io/scrape: "true"
- tier: backend - ingress.class: nginx
Règle d'or :
Le Deployment orchestre des ReplicaSets pour garantir des mises à jour sans interruption :
[ Deployment v1 ] ──► [ ReplicaSet v1 (3 Pods) ] ◄── Traffic 100%
│
│
(Mise à jour d'image vers v2 : RollingUpdate)
│
│
├──► [ ReplicaSet v1 (1 Pod) ] ◄── Traffic 33%
└──► [ ReplicaSet v2 (2 Pods) ] ◄── Traffic 67%
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max 1 pod en plus pendant le déploiement (total 6)
maxUnavailable: 0 # 0 interruption tolérée : aucun pod détruit avant qu'un nouveau soit Ready# Surveiller en direct le déploiement
kubectl rollout status deployment/web-app
# Revenir en arrière en cas de bug en 1 seconde !
kubectl rollout undo deployment/web-appQuand vos applications ne sont pas jetables (bases de données, Kafka, ElasticSearch, postgress, mongo, redis, etc) :
[ StatefulSet: redis ]
|
├── redis-0 ──► Lié au PVC data-redis-0 (Disque SSD 1)
├── redis-1 ──► Lié au PVC data-redis-1 (Disque SSD 2)
└── redis-2 ──► Lié au PVC data-redis-2 (Disque SSD 3)
nom-0, nom-1...).Ready.| Objet | Rôle Principal | Cas d'Usage Typique |
|---|---|---|
| DaemonSet | Exécute exactement 1 copie du Pod sur chaque nœud | Collecteur de logs (Fluentd), agent de métriques (Prometheus node-exporter), CNI |
| Job | Exécute une tâche jusqu'à son terme (succès) puis s'arrête | Migration de schéma SQL, calcul de rapport ponctuel |
| CronJob | Planifie des Jobs selon une expression temporelle cron | Sauvegarde nocturne 0 2 * * *, nettoyage de logs |
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "0 3 * * *" # Tous les jours à 3h du matin
jobTemplate:
spec:
template:
spec:
containers:
- name: pg-dump
image: postgres:16-alpine
restartPolicy: OnFailure
Code Review : Pourquoi le Service ci-dessous ne reçoit aucun trafic bien que les 3 Pods du Deployment soient Running ?
# Deployment
spec:
template:
metadata:
labels:
app: backend-api
tier: core
---
# Service
spec:
selector:
app: backend-api
tier: apiCode Review : Pourquoi le Service ci-dessous ne reçoit aucun trafic bien que les 3 Pods du Deployment soient Running ?
# Deployment
spec:
template:
metadata:
labels:
app: backend-api
tier: core
---
# Service
spec:
selector:
app: backend-api
tier: apiRéponse : Le sélecteur du Service exige tier: api, alors que les Pods ont tier: core. Les labels doivent correspondre exactement !.
[ Image de Conteneur Immuable ] + [ ConfigMap / Secret ]
(my-app:1.0) (dev / staging / prod)
│ │
└──────────────────────┬────────────────┘
▼
[ Pod Exécutable ]
apiVersion: v1
kind: Pod
metadata:
name: app-config-demo
spec:
containers:
- name: app
image: my-app:1.0
# Mode A : Injecté comme Variable d'Environnement
env:
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: app-config
key: db_url
# Mode B : Monté comme Fichier de Configuration
volumeMounts:
- name: config-volume
mountPath: /etc/app/config.json
subPath: config.json # <── Ne masque pas les autres fichiers du dossier !
volumes:
- name: config-volume
configMap:
name: app-configenvFromPour injecter 50 variables d'un seul coup sans les déclarer une par une :
spec:
containers:
- name: app
image: my-app:1.0
envFrom:
- configMapRef:
name: app-environment-vars
- secretRef:
name: app-sensitive-credentialsAlerte Sécurité : Les Secrets Kubernetes natifs sont simplement encodés en Base64, ils ne sont PAS chiffrés par défaut dans etcd si l'EncryptionConfiguration n'est pas activée !
# Encoder une valeur
echo -n "SuperSecretPass123" | base64
# -> U3VwZXJTZWNyZXRQYXNzMTIz
# Décoder
echo "U3VwZXJTZWNyZXRQYXNzMTIz" | base64 -dOpaque : Clés/Valeurs personnalisées (mots de passe, tokens API).kubernetes.io/tls : Certificat (tls.crt) et clé privée (tls.key).kubernetes.io/dockerconfigjson : Identifiants de registre privé d'images (imagePullSecrets).Dans une démarche GitOps, interdiction de commiter des Secrets K8s en clair dans Git !
[ AWS Secrets Mgr / Vault ] ──► [ External Secrets Operator ] ──► [ Secret K8s local ]
# 1. Déclaration du SecretStore (Connexion à Vault)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-store
spec:
provider:
vault:
server: "https://vault.my-company.com"
path: "kv"
auth:
kubernetes:
role: "k8s-role"
# 2. Déclaration du ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: external-db-secret
spec:
secretStoreRef:
name: vault-store
kind: SecretStore
target:
name: db-secret-from-vault # Le nom du Secret K8s qui va être créé
data:
- secretKey: password
remoteRef:
key: my-org/prod/database
property: DB_PASSWORDIncident SRE : Vous modifiez une valeur dans un ConfigMap existant via kubectl edit cm app-config.
Exemple de ConfigMap :
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
spec:
data:
db_url: "postgresql://user:pass@db-service:5432/prod"Exemple d'utilisation de ce configmap :
apiVersion: v1
kind: Pod
metadata:
name: app-config-demo
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: app-config
key: db_urlPourtant, l'application continue d'utiliser l'ancienne valeur. Pourquoi ?
Incident SRE : Vous modifiez une valeur dans un ConfigMap existant via kubectl edit cm app-config.
Exemple de ConfigMap :
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
spec:
data:
db_url: "postgresql://user:pass@db-service:5432/prod"Exemple d'utilisation de ce configmap :
apiVersion: v1
kind: Pod
metadata:
name: app-config-demo
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: app-config
key: db_urlPourtant, l'application continue d'utiliser l'ancienne valeur. Pourquoi ?
Réponse : Les variables d'environnement (env / envFrom) sont injectées au moment du démarrage du processus dans le conteneur. Elles ne se mettent pas à jour à chaud ! Il faut redémarrer le Pod (kubectl rollout restart deployment/mon-app) ou monter le ConfigMap en tant que Volume.
SUR-ALLOCATION (Gaspillage) SOUS-ALLOCATION (Instabilité)
Requests trop hautes = Nœuds vides Requests trop basses = Noisy neighbors,
Facture Cloud x3 inutile ! OOMKilled & CPU Throttling !
kube-scheduler réserve pour décider sur quel nœud placer le Pod.
| Ressource | Nature | Comportement en cas de Dépassement | Unité |
|---|---|---|---|
| CPU | Compressible | CPU Throttling : Le processus est bridé en temps CPU (latence p99 explose, mais le pod reste vivant). | Millicores (500m = 0.5 vCPU) |
| Mémoire (RAM) | Incompressible | OOMKilled (Code 137) : Le Kernel Linux tue instantanément le conteneur via un SIGKILL. |
Mébibytes (256Mi, 1Gi) |
Un mébibyte (MiB ) est également une unité d'information numérique, mais il est basé sur le système binaire. Un mébibyte équivaut à 1024 x 1024 octets = 1 048 576 octets, soit 2^20 octets. Le terme "mébibyte" a été introduit par la Commission électrotechnique internationale (CEI) en 1998 pour établir une distinction claire avec le mégaoctet basé sur le système décimal. Malheureusement, le terme mébibyte n'a pas encore été adopté par le grand public, de sorte que vous verrez régulièrement "mégaoctet" ou simplement MB, se référant soit à 1 000 000 d'octets, soit à 1 048 576 octets, sans savoir lequel est le bon.
resources:
requests:
cpu: "200m" # 0.2 vCPU garanti
memory: "256Mi" # 256 MiB réservés
limits:
cpu: "1000m" # Max 1 vCPU
memory: "512Mi" # Au-delà de 512MiB -> Crash OOMKilled !Kubernetes classe automatiquement chaque Pod pour savoir qui sacrifier en premier en cas de saturation d'un nœud :
1. Guaranteed (Protégé) ──► requests == limits (CPU et RAM) pour tous les conteneurs
2. Burstable (Standard) ──► Au moins 1 request définie, requests < limits
3. BestEffort (Sacrifié) ──► Aucune request ni limit définie
En cas de pénurie de mémoire sur un Worker Node, le Kubelet évince et détruit en priorité les Pods BestEffort, puis Burstable, et enfin Guaranteed en tout dernier recours.
LIMITRANGE RESOURCEQUOTA
(S'applique à CHAQUE Pod) (S'applique à TOUT le Namespace)
│ │
▼ ▼
Injecte des requests par défaut Plafonne la consommation totale
et empêche les pods démesurés (ex: Max 32 vCPU et 64GiB au total)
apiVersion: v1
kind: LimitRange
metadata:
name: team-limit-range
spec:
limits:
- type: Pod
max:
cpu: "2"
memory: "512Mi"
min:
cpu: "100m"
memory: "128Mi"apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "30"Scénario d'incident : Vous déployez un pod avec requests.cpu: "4".
En regardant avec htop sur le Worker Node, le CPU réel consommé n'est que de 10%. Pourtant, le Pod reste bloqué en statut Pending. Pourquoi ?
Scénario d'incident : Vous déployez un pod avec requests.cpu: "4".
En regardant avec htop sur le Worker Node, le CPU réel consommé n'est que de 10%. Pourtant, le Pod reste bloqué en statut Pending. Pourquoi ?
Réponse : Le scheduler prend ses décisions sur la base de la somme des Requests réservées, et non sur la charge réelle du CPU à l'instant T ! Si les autres pods ont réservé tout le CPU théorique du nœud, le scheduler refuse d'en ajouter un nouveau.
ATTIRER des Pods REPOUSSER des Pods
(Affinité & Sélecteurs de Nœuds) (Taints & Tolerations)
│ │
▼ ▼
"Ce pod IA DOIT tourner sur "Ce nœud GPU est réservé,
des machines équipées de GPU" aucun pod standard n'y entre !"
nodeSelector à la nodeAffinity# Méthode 1 : nodeSelector basique
spec:
nodeSelector:
disktype: ssd
# Méthode 2 : nodeAffinity Avancée (Hard vs Soft)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # Hard (Obligatoire)
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: [eu-west-1a, eu-west-1b]
preferredDuringSchedulingIgnoredDuringExecution: # Soft (Préférentiel)
- weight: 80
preference:
matchExpressions:
- key: instance-type
operator: In
values: [c5.xlarge]podAntiAffinity & topologySpreadConstraintsPour éviter que tous vos pods ne tombent si une seule zone de disponibilité (AZ) du Cloud crashe :
spec:
topologySpreadConstraints:
- maxSkew: 1 # Tolérance max de déséquilibre : 1 pod d'écart
topologyKey: topology.kubernetes.io/zone # Répartir entre les datacenters / AZs
whenUnsatisfiable: DoNotSchedule # Bloquer si la règle n'est pas respectée
labelSelector:
matchLabels:
app: bookstore-apitopologySpreadConstraints est le standard moderne recommandé pour équilibrer parfaitement les charges.
kubectl taint nodes node-gpu dedicated=gpu:NoScheduleNoSchedule : Empêche le placement des nouveaux pods non tolérants.PreferNoSchedule : Évite si possible, mais accepte en dernier recours.NoExecute : Expulse immédiatement les pods déjà présents s'ils ne tolèrent pas le taint !| Mécanisme | Portée | Type de Règle | Cas d'Usage Typique |
|---|---|---|---|
nodeSelector |
Nœud | Binaire (Strict) | Cibler un type d'instance simple (ex: SSD) |
nodeAffinity |
Nœud | Hard (required) / Soft (preferred) |
Ciblage multi-zones & architectures CPU |
podAntiAffinity |
Pod ↔ Pod | Hard / Soft | Répartir les réplicas sur des hôtes distincts |
topologySpread |
Nœuds / Zones | Étalement uniforme | Équilibrer les pods sur plusieurs AZs |
Taints / Tolerations |
Nœud ↔ Pod | Répulsion (NoSchedule / NoExecute) |
Nœuds GPU, isolation Control Plane, maintenance |
Question flash : Vous exécutez la commande suivante sur un worker node hébergeant 10 pods de production : kubectl taint node worker-1 maintenance=true:NoExecute
Que se passe-t-il pour ces 10 pods ?
Question flash : Vous exécutez la commande suivante sur un worker node hébergeant 10 pods de production : kubectl taint node worker-1 maintenance=true:NoExecute
Que se passe-t-il pour ces 10 pods ?
Réponse : S'ils n'ont pas de tolerations explicite pour maintenance=true, ils sont immédiatement tués et expulsés (NoExecute). Le Deployment Controller recréera de nouveaux pods sur les autres nœuds sains.
ÉPHÉMÈRE PERSISTANT
(Meurt avec le Pod) (Survit à la destruction du Pod)
│ │
▼ ▼
emptyDir / configMap / secret PersistentVolumeClaim (PVC)
Cache local, scratchpad, RAM Bases de données, fichiers partagés
emptyDir :medium: Memory) pour des performances maximales.hostPath :/var/log). 1. L'Administrateur déclare le fournisseur
[ StorageClass: "fast-ssd" ] (AWS gp3, GCP pd-ssd, Ceph)
│
▼ (Provisionnement Dynamique automatique)
2. Le Développeur demande du stockage
[ PersistentVolumeClaim (PVC) : 50Gi, ReadWriteOnce ]
│
▼ (Liaison / Binding automatique)
3. Kubernetes crée le volume physique
[ PersistentVolume (PV) : 50Gi alloué sur le Cloud/SAN ]
│
▼
4. Monté dans le Pod : /var/lib/postgresql/data
| Mode d'Accès | Abréviation | Description | Exemples Typiques |
|---|---|---|---|
| ReadWriteOnce | RWO |
Montable en lecture/écriture par 1 seul Nœud à la fois | Disques Blocs Cloud (AWS EBS, GCP PD, Azure Disk) |
| ReadOnlyMany | ROX |
Montable en lecture seule par plusieurs Nœuds | Dépôt de binaires, modèles IA statiques |
| ReadWriteMany | RWX |
Montable en lecture/écriture par plusieurs Nœuds simultanément | NFS, CephFS, AWS EFS, Azure Files |
| ReadWriteOncePod | RWOP |
Montable en lecture/écriture par 1 seul Pod unique | Verrouillage exclusif haute sécurité |
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: premium-ssd-csi
resources:
requests:
storage: 50GiQue devient le disque physique (PV) quand le développeur supprime le PVC ?
Delete (par défaut) : Le volume physique dans le Cloud/SAN est immédiatement détruit.Retain (recommandé en production) : Le volume physique est conservé avec ses données pour permettre une récupération manuelle en cas d'erreur humaine.Scénario d'incident : Vous créez un PVC avec storageClassName: standard et accessModes: [ReadWriteMany] sur un cluster EKS avec disques EBS.
Le PVC reste indéfiniment en statut Pending. Pourquoi ?
Scénario d'incident : Vous créez un PVC avec storageClassName: standard et accessModes: [ReadWriteMany] sur un cluster EKS avec disques EBS.
Le PVC reste indéfiniment en statut Pending. Pourquoi ?
Réponse : Les disques blocs EBS d'AWS ne supportent que le mode ReadWriteOnce (RWO). Le provisionneur CSI refuse d'allouer un disque EBS pour une demande de type ReadWriteMany (RWX), qui nécessite un système de fichiers distribué comme EFS / NFS
[ Pod Web 1 (10.244.1.42) ] ──► (Meurt) ──► [ Nouveau Pod (10.244.2.89) ]
▲
│
"Comment le Frontend peut-il joindre le Backend si son IP change sans cesse ?"
Chaque Service enregistré reçoit automatiquement un nom DNS FQDN :
┌───────────────────────────────────────────────────────┐
│ mon-service.mon-namespace.svc.cluster.local │
└─────────────────────────┬─────────────────────────────┘
│
┌─────────────────────┼────────────────────────┐
▼ ▼ ▼
[ Mêmes Namespace ] [ Autre Namespace ] [ FQDN Complet ]
"curl mon-service" "curl mon-service.prod". "curl mon-service.prod.svc..."
[ LoadBalancer ] (Cloud Public : AWS NLB / GCP LB)
│
▼
[ NodePort ] (Port 30000-32767 sur chaque Nœud)
│
▼
[ ClusterIP ] (IP Virtuelle interne au Cluster)
│
▼
[ Pod 1 ] [ Pod 2 ] [ Pod 3 ]
| Type de Service | Portée | Adresse IP | Cas d'Usage |
|---|---|---|---|
ClusterIP (Défaut) |
Interne au cluster | IP Virtuelle interne | Communication Est-Ouest (API ↔ BDD) |
Headless (clusterIP: None) |
Interne | Aucune VIP (DNS direct vers les IPs des Pods) | StatefulSets, Kafka, Elasticsearch |
NodePort |
Accessible depuis l'extérieur | Port élevé (30000-32767) |
Dev local, passerelles bare-metal |
LoadBalancer |
Public Internet | IP Publique Cloud managée | Exposition externe principale d'un cluster Cloud |
ExternalName |
Interne → Externe | Alias CNAME DNS | Rediriger vers une BDD managée (ex: RDS AWS) |
Endpoints & EndpointSliceComment le Service sait-il où envoyer le trafic ?
[ Service (port: 80) ] ──► [ EndpointSlice ] ──► [ Pod 1: 10.244.1.15:8080 ]
(Sélecteur: app=web) [ Pod 2: 10.244.2.20:8080 ]
# Vérifier la liste des pods réellement connectés au service
kubectl get endpoints mon-service
kubectl get endpointslices -l kubernetes.io/service-name=mon-servicekubectl get endpoints renvoie <none>, aucun pod ne recevra jamais de trafic !apiVersion: v1
kind: Service
metadata:
name: bookstore-backend
spec:
type: ClusterIP
selector:
app: bookstore
tier: backend
ports:
- name: http
protocol: TCP
port: 80 # Le port exposé par le Service
targetPort: 8080 # Le port réel écouté dans le conteneur du PodIncident SRE : Vous créez un Service auth-service pour exposer 3 Pods. Lorsque vous faites curl auth-service depuis un autre Pod, la requête renvoie instantanément : Connection refused.
Quand vous tapez kubectl get endpoints auth-service, la colonne ENDPOINTS affiche <none>.
Quelles sont les 2 causes les plus probables ?
Incident SRE : Vous créez un Service auth-service pour exposer 3 Pods. Lorsque vous faites curl auth-service depuis un autre Pod, la requête renvoie instantanément : Connection refused.
Quand vous tapez kubectl get endpoints auth-service, la colonne ENDPOINTS affiche <none>.
Quelles sont les 2 causes les plus probables ?
Réponses :
Ready), donc Kubernetes les a retirés des Endpoints. APPROCHE NAÏVE (L4) APPROCHE INGRESS / L7
50 Services type LoadBalancer 1 SEUL Load Balancer Cloud Public
= 50 IP publiques à payer = 1 seule IP, routage L7 intelligent
= Facture Cloud x50 ! = Répartition par nom de domaine & URL !
ingress-nginx).IngressClass pour indiquer quel contrôleur doit interpréter vos règles.Ingress applicatifs.apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bookstore-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # TLS automatique !
spec:
ingressClassName: nginx
tls:
- hosts:
- store.formation.com
secretName: bookstore-tls-cert
rules:
- host: store.formation.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-api-svc
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-ui-svc
port:
number: 80cert-managerFini les certificats SSL expirés un dimanche soir à 23h !
[ Ingress avec annotation ] ──► [ cert-manager ] ──► [ Let's Encrypt API ]
|
│ (Challenge HTTP-01 / DNS-01)
▼
[ Secret TLS auto-renouvelé ]
La Gateway API est le successeur officiel de l'Ingress standard, conçu pour les architectures modernes et la séparation des responsabilités :
┌─────────────────────────────────────────────────────────────┐
│ │
│ ÉQUIPE INFRA / CLOUD (Cluster Admin) │
│ ► GatewayClass : Définit le fournisseur (Envoy, Cilium...) │
│ ► Gateway : Déclare le point d'entrée, IP et ports 80/443 │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ÉQUIPE DÉVELOPPEMENT / APPLICATION │
│ ► HTTPRoute : Règle de routage autonome par namespace │
│ ► GRPCRoute / TCPRoute : Support natif au-delà du HTTP ! │
│ │
└─────────────────────────────────────────────────────────────┘
Incident SRE : Un utilisateur essaie de joindre https://store.formation.com/api et obtient une erreur 502 Bad Gateway de la part d'Ingress Nginx.
Quelle est la démarche méthodique d'investigation ?
Incident SRE : Un utilisateur essaie de joindre https://store.formation.com/api et obtient une erreur 502 Bad Gateway de la part d'Ingress Nginx.
Quelle est la démarche méthodique d'investigation ?
# 1. Vérifier si l'Ingress pointe vers un service existant
kubectl describe ingress bookstore-ingress
# 2. Vérifier si le Service a des Pods Endpoints actifs
kubectl get endpoints backend-api-svc
# 3. Vérifier les logs du contrôleur Ingress en temps réel
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=50 STRATÉGIE RECREATE STRATÉGIE ROLLING UPDATE
[ Ancien Pod ] ──► (Détruit) [ V1 ] [ V1 ] ──► [ V1 ] [ V2 ]
│ (Downtime !) │
▼ ▼
[ Nouveau Pod ] ──► (Démarré) [ V2 ] [ V2 ] (0 Downtime)
| Stratégie | Downtime | Coût Infrastructure | Risque | Cas d'Usage |
|---|---|---|---|---|
RollingUpdate |
0s | Faible (+1 pod de maxSurge) |
Moyen | Applications stateless standard |
Recreate |
Oui (10s-2min) | Nul | Faible | Incompatibilité de schéma de BDD entre versions |
Blue/Green |
0s | Élevé (+100% de ressources) | Faible | Bascule atomique instantanée après tests réels |
Canary |
0s | Modéré (+5-10%) | Très faible | Envoi de 5% du vrai trafic sur la v2 (Argo Rollouts) |
Sans Probes bien configurées, Kubernetes envoie du trafic utilisateur à des conteneurs qui n'ont même pas fini d'initialiser leur serveur web !
1. StartupProbe ──► "Est-ce que l'application a fini de charger ses dépendances ?"
│ (Si OK)
▼
2. ReadinessProbe ──► "Est-ce que l'application est prête à recevoir du trafic ?"
│ (Si KO : Retiré des Endpoints du Service, 0 trafic envoyé !)
▼
3. LivenessProbe ──► "Est-ce que l'application est toujours vivante ou en deadlock ?"
(Si KO : Le Kubelet redémarre le conteneur !)
spec:
containers:
- name: api
image: my-app:2.0
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2 # Laisse jusqu'à 60s pour démarrer
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /live
port: 8080
periodSeconds: 10Lorsqu'un Pod est arrêté (scale down ou rolling update) :
1. Le Pod passe en statut Terminating
2. L'IP du Pod est retirée des Endpoints (les nouvelles requêtes ne vont plus vers lui)
3. Exécution du Hook preStop (sleep 5s pour laisser le temps aux iptables de se synchroniser)
4. Envoi du signal SIGTERM au processus applicatif (pour finir les requêtes en cours)
5. Attente jusqu'à terminationGracePeriodSeconds (défaut: 30s)
6. Envoi du SIGKILL destructeur si le processus n'a pas quitté proprement
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]Code Review : Vous observez qu'en pic de charge, vos Pods se mettent à redémarrer en boucle de façon incontrôlable.
En inspectant la config :
livenessProbe:
httpGet:
path: /api/v1/check-database-and-heavy-query # <── Requête SQL complexe en BDD !Pourquoi cette configuration est-elle une bombe à retardement en production ?
Code Review : Vous observez qu'en pic de charge, vos Pods se mettent à redémarrer en boucle de façon incontrôlable.
En inspectant la config :
livenessProbe:
httpGet:
path: /api/v1/check-database-and-heavy-query # <── Requête SQL complexe en BDD !Pourquoi cette configuration est-elle une bombe à retardement en production ?
Réponse : Si la base de données ralentit ou sature, la liveness probe échoue en timeout. Kubernetes croit à tort que le Pod web est mort et le tue, provoquant une cascade de redémarrages catastrophique (thundering herd). La Liveness Probe doit être locale et ultra-légère !.
┌─────────────────────────────────────────────────────────────┐
| |
│ 1. HPA (Horizontal Pod Autoscaler) : Varie le NOMBRE de Pods│
│ [ Pod ] ──► [ Pod ] [ Pod ] [ Pod ] [ Pod ] |
| |
├─────────────────────────────────────────────────────────────┤
| |
| 2. VPA (Vertical Pod Autoscaler) : Varie la TAILLE du Pod |
│ [ Petit Pod (1 CPU) ] ──► [ GROS Pod (4 CPU) ] |
| |
├─────────────────────────────────────────────────────────────┤
| |
| 3. Cluster Autoscaler / Karpenter : Varie le NOMBRE de NŒUDS|
│ [ Node 1 ] ──► [ Node 1 ] [ Node 2 ] [ Node 3 ] |
| |
└─────────────────────────────────────────────────────────────┘
Le HPA interroge metrics-server toutes les 15 secondes pour adapter le nombre de réplicas :
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: bookstore-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: bookstore-api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Vise 70% de la Request CPU
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 400MiLe CPU/RAM ne suffit plus : comment réagir AVANT que le CPU ne sature ?
[ File RabbitMQ / Kafka ] ──► [ KEDA Controller ] ──► [ HPA K8s ] ──► [ Scale Pods ]
(10 000 messages en attente)
Quand 50 nouveaux Pods passent en statut Pending faute de place :
Incident SRE : Vous configurez un HPA sur un Deployment. En tapant kubectl get hpa, la colonne TARGETS affiche obstinément : <unknown>/70%.
Quelles sont les 2 causes directes de ce blocage ?
Incident SRE : Vous configurez un HPA sur un Deployment. En tapant kubectl get hpa, la colonne TARGETS affiche obstinément : <unknown>/70%.
Quelles sont les 2 causes directes de ce blocage ?
# Diagnostic :
kubectl describe hpa bookstore-api-hpaRéponses :
resources.requests.cpu définie (le HPA ne sait pas sur quelle base calculer le pourcentage !).metrics-server n'est pas installé ou ne parvient pas à joindre les Kubelets.┌───────────────────────────────────────────────┐
│ OBSERVABILITÉ │
│ │
│ [ MÉTRIQUES ] [ LOGS ] [ TRACES ] │
│ Prometheus Loki OpenTelemetry │
│ (Tendances) (Détails) (Parcours) │
└───────┬─────────────┬──────────────┬──────────┘
└─────────────┼──────────────┘
▼
[ GRAFANA DASHBOARDS ]
Prometheus collecte les métriques par Pull (Scraping HTTP) à intervalles réguliers :
[ Pod Applicatif ] ──► Expose /metrics (format Prometheus)
▲
│ (Scrape toutes les 15s)
│
[ Prometheus Operator ] ─────┴──────► Enregistre dans la base TSDB
│
│
▼
[ Grafana Dashboard ] ◄─────────── (Requêtes PromQL)
kube-state-metrics : Génère des métriques sur l'état des objets K8s (nombre de pods en Pending, réplicas désirés vs réels).node-exporter : Métriques matérielles des nœuds (CPU, RAM, I/O disque, réseau).Ne configurez plus Prometheus à la main ! Déclarez le scraping en YAML :
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: bookstore-monitor
labels:
release: prometheus
spec:
selector:
matchLabels:
app: bookstore
endpoints:
- port: metrics
interval: 15s
path: /metricsComment identifier quel microservice ralentit une transaction utilisateur sur 15 étapes ?
[ Client ] ──► [ Frontend ] (20ms)
│
▼
[ Auth API ] (15ms)
│
▼
[ Payment Gateway ] ──► (Bloqué 3.2s sur la banque !)
traceparent) et les stocke dans Jaeger ou Grafana Tempo.k9s & kubectl top# Vérifier la consommation réelle instantanée
kubectl top nodes
kubectl top pods -A --sort-by=memory
# Ouvrir k9s (TUI interactif pour naviguer dans les pods, logs et métriques)
k9sQuestion SRE : Vous devez écrire une règle d'alerte pour être réveillé uniquement si le taux d'erreur 5xx de votre API dépasse 5% pendant 5 minutes.
Question SRE : Vous devez écrire une règle d'alerte pour être réveillé uniquement si le taux d'erreur 5xx de votre API dépasse 5% pendant 5 minutes.
# Requête PromQL experte :
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100 > 5
[ Pod Container ] ──► Écrit sur stdout / stderr
│
▼ (Géré par le CRI : containerd)
Fichier JSON local : /var/log/pods/<namespace>_<pod>_<uid>/...
│
▼ (Scrapé par un DaemonSet sur le Nœud)
[ Fluent Bit / Promtail / Vector ]
│
▼ (Envoi compressé)
[ Grafana Loki / OpenSearch ]
stdout) et la sortie d'erreur standard (stderr).containerd capture ces flux, gère la rotation automatique des fichiers sur le disque du nœud hôte et les expose via kubectl logs.# Consulter les logs en direct d'un Pod
kubectl logs -f deployment/bookstore-api
# Voir les logs d'un conteneur spécifique dans un Pod multi-conteneurs
kubectl logs pod-avec-sidecar -c mon-application
# Consulter les logs du conteneur AVANT son dernier crash (Indispensable !)
kubectl logs mon-pod -p| Modèle | Consommation Ressources | Complexité | Cas d'Usage Recommandé |
|---|---|---|---|
| DaemonSet (Node-level) | Très faible (1 seul agent par serveur physique) | Simple et transparent pour les devs | 95% des cas en production (Fluent Bit, Vector) |
| Sidecar (Pod-level) | Élevée (1 conteneur en plus par Pod) | Spécifique | Applications legacy écrivant dans un fichier rigide /var/log/app.log |
Contrairement à ElasticSearch qui indexe l'intégralité du texte des logs (gourmand en RAM/Disque), Grafana Loki indexe uniquement les métadonnées (labels K8s) :
# Requête LogQL pour filtrer les erreurs 500 sur le namespace production
{namespace="production", app="bookstore"} |= "ERROR" | json | status_code >= 500
Incident SRE : Un Pod s'est crashé il y a 30 secondes et vient d'être recréé par son ReplicaSet. Quand vous tapez kubectl logs mon-pod, vous ne voyez que 2 lignes de démarrage, les logs de l'erreur qui a causé la mort du pod ont disparu.
Quelle option magique de kubectl logs permet de voir les derniers instants du conteneur décédé ?
Incident SRE : Un Pod s'est crashé il y a 30 secondes et vient d'être recréé par son ReplicaSet. Quand vous tapez kubectl logs mon-pod, vous ne voyez que 2 lignes de démarrage, les logs de l'erreur qui a causé la mort du pod ont disparu.
Quelle option magique de kubectl logs permet de voir les derniers instants du conteneur décédé ?
Réponse : kubectl logs mon-pod --previous ou kubectl logs mon-pod -p.
[ 1. ÉTAT GLOBAL ] ──► kubectl get pods -o wide (Identifier les coupables)
│
▼
[ 2. AUTOPSIE ] ──► kubectl describe pod <nom> (Section Events & Last State)
│
▼
[ 3. DERNIERS MOTS ] ──► kubectl logs <nom> --previous (Pourquoi a-t-il crashé ?)
│
▼
[ 4. INFILTRATION ] ──► kubectl debug -it <nom> --image=busybox (Debug éphémère)
| Statut Affiché | Signification Réelle | Cause Racine Typique |
|---|---|---|
ImagePullBackOff |
Impossible de télécharger l'image | Erreur de nom de tag, registre privé sans imagePullSecrets |
CrashLoopBackOff |
L'app démarre puis s'arrête en erreur | Variable d'environnement manquante, erreur de config, exit code ≠0 |
OOMKilled (Exit 137) |
Tué par le noyau Linux | L'application a dépassé sa resources.limits.memory |
Pending |
Le scheduler ne trouve aucun nœud | Pas assez de CPU/RAM libre, Taints sans Tolerations, PVC non lié |
CreateContainerConfigError |
Configuration invalide | ConfigMap ou Secret référencé introuvable |
Terminating (Bloqué) |
Le pod refuse de mourir | Finalizer bloqué, ou I/O disque NFS gelé |
kubectl debug)Comment déboguer une image de production moderne ultra-sécurisée (Distroless / Scratch) qui ne contient ni sh, ni curl, ni ps ?
# Injecter un conteneur de debug avec tous vos outils sans redémarrer le pod !
kubectl debug -it mon-pod-distroless --image=nicolaka/netshoot --target=mon-appConstat : Un conteneur Java SpringBoot redémarre toutes les 2 minutes sans aucun message d'erreur dans les logs applicatifs.
En tapant kubectl describe pod java-app :
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Diagnostic : La JVM a alloué son heap sans tenir compte de la limite cgroups K8s. Le noyau Linux a envoyé un SIGKILL (Code 137 = 128 + 9).
Correction : Ajouter l'option JVM -XX:MaxRAMPercentage=75.0 ou augmenter la limite mémoire dans le YAML.
Terminating ÉternelConstat : Vous tapez kubectl delete pod mon-pod --force, mais le Pod reste bloqué indéfiniment en statut Terminating.
# 1. Inspecter les finalizers qui retiennent la destruction
kubectl get pod mon-pod -o jsonpath='{.metadata.finalizers}'
# 2. Débloquer la situation en supprimant le finalizer bloquant
kubectl patch pod mon-pod -p '{"metadata":{"finalizers":null}}'Incident SRE : Vous lancez un pod avec l'image ubuntu:latest sans argument particulier : kubectl run test --image=ubuntu.
Le pod passe immédiatement en Completed puis en CrashLoopBackOff.
Pourquoi ce conteneur refuse-t-il de rester en vie ?
Incident SRE : Vous lancez un pod avec l'image ubuntu:latest sans argument particulier : kubectl run test --image=ubuntu.
Le pod passe immédiatement en Completed puis en CrashLoopBackOff.
Pourquoi ce conteneur refuse-t-il de rester en vie ?
Réponse : Le processus principal par défaut d'une image Ubuntu est /bin/bash. Sans terminal interactif (-it) ni commande longue (sleep infinity), le shell termine son exécution immédiatement (PID 1 s'arrête avec code 0), ce qui éteint le Pod.
┌─────────────────────────────────────────────────────────────┐
│ OPÉRATIONS EN DIRECT │
│ │
│ ► Tunneling Sécurisé : kubectl port-forward │
│ ► Transfert Fichiers : kubectl cp │
│ ► Automatisation CI/CD: kubectl wait --for=condition=... │
│ ► Débogage Live : kubectl exec / kubectl attach │
└─────────────────────────────────────────────────────────────┘
kubectl port-forwardAccéder à une base de données PostgreSQL ou une UI interne sans jamais exposer de port public sur Internet :
# Rediriger le port local 5432 vers le port 5432 du Pod postgres
kubectl port-forward pod/postgres-0 5432:5432
# Rediriger vers un Service ou un Deployment
kubectl port-forward svc/prometheus-k8s 9090:9090 -n monitoringkubectl waitFini les boucles sleep 30 fragiles et hasardeuses dans vos pipelines CI/CD !
# Attendre que le déploiement soit 100% opérationnel avec un timeout de 2 minutes
kubectl rollout status deployment/bookstore-api --timeout=120s
# Attendre qu'une condition spécifique soit validée
kubectl wait --for=condition=ready pod -l app=bookstore-api --timeout=60s
# Attendre la fin d'un Job de migration de base de données
kubectl wait --for=condition=complete job/db-migration --timeout=180skubectl cp# Copier un fichier de log local vers un conteneur
kubectl cp dump.sql default/postgres-0:/tmp/dump.sql
# Récupérer un fichier généré à l'intérieur d'un pod
kubectl cp default/reporting-pod:/app/report.pdf ./report.pdf -c app-containerObjectif : Vous écrivez un script de déploiement qui applique un manifest, attend que l'application soit prête, et effectue un rollback automatique immédiat si le déploiement échoue au bout de 90 secondes.
Objectif : Vous écrivez un script de déploiement qui applique un manifest, attend que l'application soit prête, et effectue un rollback automatique immédiat si le déploiement échoue au bout de 90 secondes.
# Le script SRE parfait :
kubectl apply -f app-deployment.yaml
if ! kubectl rollout status deployment/web-app --timeout=90s; then
echo "Déploiement échoué ! Rollback immédiat..."
kubectl rollout undo deployment/web-app
exit 1
fi
echo "Déploiement réussi avec succès !"[ Nœud Actif avec Pods ] ──► [ 1. CORDON ] ──► Aucun nouveau pod n'est admis
│
▼
[ 2. DRAIN ] ──► Évacue les pods vers d'autres nœuds
│ (En respectant les PDB !)
▼
[ 3. REBOOT / UPGRADE OS ]
│
▼
[ 4. UNCORDON ] ──► Nœud réactivé et sain !
# Étape 1 : Empêcher le scheduling sur le nœud
kubectl cordon worker-01
# Étape 2 : Évacuer tous les pods proprement
kubectl drain worker-01 --ignore-daemonsets --delete-emptydir-data
# Étape 3 : Une fois la maintenance terminée (reboot, kernel patch...)
kubectl uncordon worker-01--ignore-daemonsets : Indispensable pour ne pas bloquer sur les agents locaux de logs/monitoring.--delete-emptydir-data : Accepte la suppression des données locales temporaires.PodDisruptionBudget (PDB)Sans PDB, un drain peut tuer tous les réplicas d'un service en même temps et créer une coupure de service !
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: bookstore-pdb
spec:
minAvailable: 2 # Garantit qu'au moins 2 pods restent vivants en permanence !
selector:
matchLabels:
app: bookstorekubectl drain sera mis en attente et évincera les pods un par un, en attendant que le nouveau pod soit Ready avant de tuer le suivant.kubeadm) 1. Mettre à niveau le Control Plane Master 1
$ sudo apt update && sudo apt install -y kubeadm=1.31.0-1.1
$ sudo kubeadm upgrade plan
$ sudo kubeadm upgrade apply v1.31.0
$ sudo apt install -y kubelet=1.31.0-1.1 kubectl=1.31.0-1.1
$ sudo systemctl daemon-reload && sudo systemctl restart kubelet
│
▼
2. Mettre à niveau les Worker Nodes (Un par Un !)
$ kubectl drain worker-01 ...
$ sudo kubeadm upgrade node
$ sudo apt install -y kubelet=1.31.0-1.1
$ sudo systemctl restart kubelet
$ kubectl uncordon worker-01# Sauvegarder tout le namespace production en 1 commande
velero backup create prod-backup-$(date +%F) --include-namespaces production
# Restaurer l'environnement complet en cas de désastre
velero restore create --from-backup prod-backup-2026-08-25Incident SRE : Vous lancez kubectl drain worker-02, mais la commande reste bloquée indéfiniment avec le message : Cannot evict pod: PodDisruptionBudget "api-pdb" is currently violated.
Quel est le problème et comment le résoudre proprement ?
Incident SRE : Vous lancez kubectl drain worker-02, mais la commande reste bloquée indéfiniment avec le message : Cannot evict pod: PodDisruptionBudget "api-pdb" is currently violated.
Quel est le problème et comment le résoudre proprement ?
Réponse : L'application a un PDB exigeant minAvailable: 2, mais il n'y a plus assez de place sur les autres nœuds du cluster pour démarrer le second pod. Solution : Ajouter un nouveau worker node au cluster ou augmenter temporairement les ressources disponibles pour débloquer l'éviction.
┌───────────────────────────────────────────┐
│ CLOUD │
│ ┌───────────────────────────────────┐ │
│ │ CLUSTER │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ CONTAINER │ │ │
│ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ CODE │ │ │ │
│ │ │ └───────────────────┘ │ │ │
│ │ └───────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
└───────────────────────────────────────────┘
runAsNonRoot, drop des capabilities, profils Seccomp). SUJETS RÔLES PORTÉES
(Qui ?) (Quoi ?) (Où ?)
│ │ │
┌────┴────────────┐ ┌────┴────────────┐ ┌────┴──────────────┐
│ User / Group │ ────► │ Verbs : get, │ ──────────► │ RoleBinding │
│ ServiceAccount │ │ list, create... │ │ (Namespace local) │
└─────────────────┘ │ Resources: pods │ ├───────────────────┤
└─────────────────┘ │ ClusterRoleBinding│
│ (Tout le Cluster) │
└───────────────────┘
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-log-viewer
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: dev-view-logs
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-log-viewer
apiGroup: rbac.authorization.k8s.ioLes vieux PodSecurityPolicy (PSP) sont dépréciés. Le standard moderne est Pod Security Admission :
| Profil PSS | Niveau de Sécurité | Restrictions Imposées |
|---|---|---|
Privileged |
Insecure | Accès complet sans restriction (réservé aux CNI / CSI) |
Baseline |
Standard | Interdit les conteneurs privilégiés et accès hostPath critiques |
Restricted |
Durci (Prod) | Non-root obligatoire, capabilities supprimées, FS en lecture seule |
# Activer le mode Restricted sur un namespace en 1 commande :
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restrictedspec:
securityContext:
runAsNonRoot: true # Interdit l'exécution sous root (UID 0)
seccompProfile:
type: RuntimeDefault # Active le filtrage des appels système
containers:
- name: app
image: my-app:distroless
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # Empêche l'écriture de malware
capabilities:
drop: ["ALL"] # Supprime toutes les capacités LinuxPar défaut dans Kubernetes, tous les Pods peuvent parler à tous les Pods !
# Règle d'or : Bloquer tout le trafic entrant par défaut (Default Deny)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # S'applique à tous les pods du namespace
policyTypes:
- Ingress# N'autoriser QUE le Backend à parler à PostgreSQL sur le port 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-postgres
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: bookstore-backend
ports:
- protocol: TCP
port: 5432Question flash : Quelle commande kubectl permet de vérifier immédiatement si l'utilisateur alice a le droit de supprimer des secrets dans le namespace production ?
Question flash : Quelle commande kubectl permet de vérifier immédiatement si l'utilisateur alice a le droit de supprimer des secrets dans le namespace production ?
# La réponse instantanée :
kubectl auth can-i delete secrets -n production --as alice [ 20 Fichiers YAML Complexes ] ──► Packagés dans un ──► [ 1 Helm Chart ]
(Deployments, Services, RBAC, (my-app:1.2.0)
CRDs, Ingress, Secrets...) │
▼
Paramétré par un seul fichier : [ values.yaml ]
my-chart/
├── Chart.yaml # Métadonnées (Nom, version de l'app, version du chart)
├── values.yaml # Valeurs par défaut injectées dans les templates
├── templates/ # Gabarits YAML avec moteur Go Template
│ ├── deployment.yaml
│ ├── service.yaml
│ └── _helpers.tpl
└── charts/ # Sous-charts / Dépendances éventuelles
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-chart.fullname" . }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http# values.yaml (Dev vs Prod)
service:
type: ClusterIP
port: 8080# 1. Ajouter un dépôt officiel (ex: Bitnami)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 2. Installer ou Mettre à jour de façon idempotente
helm upgrade --install my-redis bitnami/redis \
--namespace databases --create-namespace \
--set auth.enabled=false
# 3. Tester le rendu YAML sans rien appliquer (Dry Run)
helm template my-release bitnami/redis -f my-values.yaml
# 4. Rollback immédiat vers la révision précédente
helm rollback my-redis 1Code Review : Que se passe-t-il si vous passez une chaîne de caractères contenant des deux-points (:) dans une valeur Helm sans guillemets dans votre values.yaml ?
message: Erreur: Connexion refusée # <── Erreur de parsing YAML !Code Review : Que se passe-t-il si vous passez une chaîne de caractères contenant des deux-points (:) dans une valeur Helm sans guillemets dans votre values.yaml ?
message: Erreur: Connexion refusée # <── Erreur de parsing YAML !Réponse : Le parseur YAML interprète la seconde partie comme une nouvelle clé imbriquée invalide et le helm install crashe. Il faut toujours quoter les chaînes : message: "Erreur: Connexion refusée" et utiliser la fonction {{ .Values.message | quote }} dans le template.
[ BASE (Manifests Communs) ]
(Deployment, Service, Config)
│
┌────────────────┴────────────────┐
▼ ▼
[ OVERLAY : STAGING ] [ OVERLAY : PRODUCTION ]
- 1 Réplica - 10 Réplicas
- Image : tag staging - Image : tag v2.1.0 (immuable)
- Petites ressources CPU/RAM - Grosses ressources + HPA
{{ .Values }} complexe. On part de manifests YAML purs et standards, et on applique des correctifs (patches) chirurgicaux par environnement.kubectl depuis la version 1.14 (kubectl apply -k).k8s/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── replica_patch.yaml
└── production/
├── kustomization.yaml
└── resources_patch.yaml
kustomization.yaml en Pratique# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
namePrefix: prod-
resources:
- ../../base
images:
- name: bookstore-api
newTag: v2.1.4
patches:
- path: resources_patch.yaml
target:
kind: Deployment
name: bookstore-apiconfigMapGenerator:
- name: app-config
files:
- config.propertiesFonctionnalité magique : Kustomize calcule un hash SHA du contenu du fichier (ex: app-config-7b2m8f4k9g).
Lorsque vous modifiez config.properties, le nom du ConfigMap change, ce qui force Kubernetes à déclencher automatiquement un Rolling Update propre de tous les Pods sans manipulation manuelle !
| Critère | Helm | Kustomize |
|---|---|---|
| Philosophie | Templating Go paramétré | Héritage & Patches déclaratifs |
| Dépendance externe | Binaire helm requis |
Intégré dans kubectl |
| Gestion des Secrets / CM | Manuelle | Hash automatique (Rolling update forcé) |
| Cas d'usage Idéal | Applications tierces CNCF (Ingress, Cert-Manager) | Vos propres microservices internes en GitOps |
# Quelle commande permet d'inspecter le rendu final de l'overlay production sans rien appliquer ?
kubectl kustomize overlays/production/
# Quelle commande permet d'appliquer directement l'overlay en production ?
kubectl apply -k overlays/production/ APPROCHE CI CLASSIQUE APPROCHE GITOPS (ArgoCD)
[ Runner CI / GitHub Actions ] [ Git Repository (Source of Truth) ]
│ (Push) │
▼ (Droits admin requis !) ▼ (Pull & Reconcile)
[ Cluster Kubernetes ] [ ArgoCD dans le Cluster ]
(Sécurité faible : tokens exposés) (Sécurité maximale : 0 port ouvert !)
Single Source of Truth).Self-Healing).Application d'ArgoCDapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: bookstore-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/mon-orga/k8s-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Supprime du cluster les objets supprimés dans Git !
selfHeal: true # Annule et réécrit toute modification manuelle directe faite via kubectl ! SYNC STATUS HEALTH STATUS
┌───────────────┐ ┌───────────────┐
│ Synced │ (Cluster == Git) │ Healthy │ (Tous les pods sont Ready)
│ OutOfSync │ (Git a changé) │ Progressing │ (Rolling update en cours)
│ Unknown │ (Erreur de parsing) │ Degraded │ (CrashLoopBackOff / Erreur)
└───────────────┘ └───────────────┘
kubectl edit en production, ArgoCD détecte l'écart en quelques secondes et rétablit immédiatement l'état déclaré dans Git !ApplicationSetPour gérer 100 applications réparties sur 10 clusters (Dev, Staging, Prod, Multi-Cloud) sans dupliquer 1000 lignes de YAML :
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-microservices
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/mon-orga/k8s-manifests.git
revision: main
directories:
- path: services/*
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/mon-orga/k8s-manifests.git
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: appsScénario de test : Vous avez configuré une Application ArgoCD avec syncPolicy.automated.selfHeal: true. Un administrateur supprime accidentellement un Deployment avec kubectl delete deployment bookstore-api.
Que se passe-t-il dans les 10 secondes qui suivent ?
Scénario de test : Vous avez configuré une Application ArgoCD avec syncPolicy.automated.selfHeal: true. Un administrateur supprime accidentellement un Deployment avec kubectl delete deployment bookstore-api.
Que se passe-t-il dans les 10 secondes qui suivent ?
Réponse : ArgoCD détecte que le Deployment manque dans le cluster par rapport à la branche main de Git, et le recrée instantanément à l'identique !.
KCNA (Fondamentaux) CKAD (Développeur) CKA (Administrateur) CKS (Sécurité)
QCM Théorique Cloud Native 100% Pratique Hands-on 100% Pratique Hands-on 100% Pratique Hands-on
Architecture & CNCF Pods, Deploy, Services, Control Plane, Upgrades, PSA, Falco, Trivy,
Jobs, ConfigMaps ETCD Backup, CNI, Troubleshoot RBAC, NetworkPolicy
Maîtriser la génération impérative : kubectl run ... --dry-run=client -o yaml (interdiction d'écrire du YAML de zéro !).
Configurer son environnement dès la 1ère minute : alias k=kubectl export do="--dry-run=client -o yaml" export now="--force --grace-period=0"
Optimiser sa configuration .vimrc : vim set tabstop=2 shiftwidth=2 expandtab autoindent number
S'entraîner sur Killer.sh : Deux sessions de simulation d'examen gratuites sont incluses avec l'inscription à l'examen.
┌─────────────────────────────────────────────────────────────┐
│ VOTRE NOUVEAU SOCLE OPS / SRE │
│ │
│ ✔ Maîtrise de l'architecture interne & du Control Plane │
│ ✔ Déploiements résilients & Zéro Downtime (Probes, PDB) │
│ ✔ Réseau L4/L7, Ingress, Gateway API & Découverte DNS │
│ ✔ Sécurité Zero-Trust (PSA Restricted, RBAC, NetPol) │
│ ✔ Observabilité complète (Prometheus, Grafana Loki, OTel) │
│ ✔ Industrialisation GitOps avec ArgoCD & Kustomize │
└─────────────────────────────────────────────────────────────┘
resources.requests et limits sont-elles définies sur tous les conteneurs ?readinessProbe et livenessProbe sont-elles en place et légères ?PodDisruptionBudget protège-t-il les services critiques ?runAsNonRoot: true avec le profil PSA Restricted ?
Comment Évolue Kubernetes ? Les SIGs
Le projet est piloté par des Special Interest Groups (SIGs) autonomes :