Chapitre 9 : Observabilité et dépannage¶
!!! abstract "Objectifs du chapitre" - Exploiter les logs et les événements pour comprendre l'état d'une application - Configurer les trois types de sondes (liveness, readiness, startup) et expliquer leurs effets - Distinguer metrics-server de la chaîne Prometheus, Grafana et Alertmanager - Appliquer une méthodologie de diagnostic structurée - Acquis d'apprentissage visés : AA5, AA6
1. Observer pour comprendre¶
Un système en production tombe en panne, ralentit ou se comporte de façon inattendue. Observer consiste à disposer des informations qui permettent de le comprendre sans deviner.
| Signal | Question | Exemples dans Kubernetes |
|---|---|---|
| Logs | Que s'est-il passé ? | kubectl logs, sortie standard des conteneurs |
| Événements | Que fait le cluster à propos de cet objet ? | kubectl describe, kubectl get events |
| Sondes | L'application est-elle vivante, prête à servir ? | liveness, readiness, startup |
| Métriques | Quelle est la tendance (charge, erreurs, mémoire) ? | kubectl top, Prometheus, Grafana |
Les traces distribuées (suivre une requête à travers plusieurs services) complètent ces signaux dans les architectures en microservices ; elles dépassent le cadre de ce chapitre.
2. Les logs¶
Un conteneur doit écrire ses logs sur la sortie standard (stdout) et la sortie d'erreur : c'est ce que collecte kubectl logs.
| Besoin | Commande |
|---|---|
| Logs d'un Pod | kubectl logs <pod> |
| Suivre en direct | kubectl logs -f <pod> |
| Dernières lignes / dernières minutes | kubectl logs --tail=50 <pod> · kubectl logs --since=10m <pod> |
| Conteneur précis d'un Pod | kubectl logs <pod> -c <conteneur> |
| Conteneur précédent (après un crash) | kubectl logs <pod> --previous |
| Plusieurs Pods par label | kubectl logs -l app=web --all-containers |
Points à connaître :
- les logs sont stockés sur le nœud et font l'objet d'une rotation ; ils sont perdus quand le Pod ou le nœud disparaît ;
- en production, on les centralise dans un outil dédié (par exemple Loki ou la pile Elasticsearch) pour les conserver et les interroger ;
- après un redémarrage de conteneur,
--previousdonne les logs de l'exécution qui a planté.
3. Les événements¶
Kubernetes enregistre des événements à chaque décision ou incident : planification, téléchargement d'image, échec d'une sonde, quota refusé.
kubectl describe pod <pod> # section Events, en bas
kubectl get events --sort-by=.metadata.creationTimestamp # tout le namespace, du plus ancien au plus récent
kubectl get events --field-selector type=Warning # uniquement les avertissements
Ils sont de type Normal ou Warning. Ils sont conservés peu de temps (environ une heure par défaut) : consultez-les tôt, avant qu'ils ne disparaissent.
4. Les sondes (probes)¶
Par défaut, Kubernetes considère un conteneur comme sain tant que son processus tourne. Or une application peut être démarrée mais bloquée, ou en cours de démarrage et incapable de servir. Les sondes permettent au kubelet de le vérifier.
| Sonde | Question posée | Si elle échoue |
|---|---|---|
| liveness | L'application est-elle vivante ? | Le conteneur est redémarré |
| readiness | L'application est-elle prête à recevoir du trafic ? | Le Pod est retiré des endpoints du Service (pas de redémarrage) |
| startup | L'application a-t-elle fini de démarrer ? | Tant qu'elle n'a pas réussi, les deux autres sondes sont suspendues ; si elle échoue trop longtemps, le conteneur est redémarré |
flowchart LR
K["kubelet"] -- "sonde readiness" --> A["Application"]
A -- "échec" --> E["Pod retiré<br/>des endpoints du Service"]
K -- "sonde liveness" --> A
A -- "échec répété" --> R["Conteneur redémarré"]
Trois mécanismes de vérification :
| Mécanisme | Principe | Exemple |
|---|---|---|
httpGet |
Requête HTTP ; code 200 à 399 = succès | /health, /ready |
tcpSocket |
Ouverture d'une connexion TCP | Port 5432 d'une base |
exec |
Exécution d'une commande dans le conteneur ; code 0 = succès | pg_isready -U appuser |
containers:
- name: api
image: monimage:1.0.0
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
| Paramètre | Rôle | Valeur par défaut |
|---|---|---|
initialDelaySeconds |
Attente avant la première vérification | 0 |
periodSeconds |
Intervalle entre deux vérifications | 10 |
timeoutSeconds |
Délai maximal de réponse | 1 |
failureThreshold |
Échecs consécutifs avant de considérer la sonde en échec | 3 |
successThreshold |
Succès consécutifs pour repasser en succès | 1 |
Les sondes sont liées à ce que vous avez déjà vu :
- la disponibilité d'un Pod (colonne
READYdekubectl get pods) dépend de sa sonde readiness ; - les endpoints d'un Service (chapitre 3) ne contiennent que les Pods prêts ;
- un rolling update (chapitre 2) n'avance que lorsque les nouveaux Pods sont prêts : sans sonde readiness, Kubernetes considère un Pod prêt dès que son conteneur démarre.
!!! warning "Erreurs fréquentes" - Une sonde liveness trop stricte ou qui dépend d'un service externe (la base de données) provoque des redémarrages en boucle quand ce service est indisponible. La liveness doit seulement dire « le processus répond ». - C'est la sonde readiness qui peut vérifier les dépendances : un Pod sans accès à la base cesse de recevoir du trafic, sans être redémarré. - Un délai trop court pour une application lente à démarrer provoque des redémarrages permanents : utilisez initialDelaySeconds ou une sonde startup.
5. Les métriques¶
metrics-server : la vue instantanée¶
Fourni par k3s, metrics-server donne la consommation actuelle de CPU et de mémoire des nœuds et des Pods :
Il ne garde aucun historique et ne mesure que le CPU et la mémoire. Il alimente aussi l'autoscaling (chapitre 10).
Prometheus, Grafana et Alertmanager¶
Pour suivre l'évolution dans le temps et déclencher des alertes, on utilise un outil dédié. La pile la plus répandue :
flowchart LR
T1["Nœuds<br/>(node-exporter)"] --> P
T2["Conteneurs<br/>(kubelet / cAdvisor)"] --> P
T3["Objets Kubernetes<br/>(kube-state-metrics)"] --> P
T4["Applications<br/>(/metrics)"] --> P
P[("Prometheus<br/>collecte et stocke")] --> G["Grafana<br/>tableaux de bord"]
P -- "règles d'alerte" --> AM["Alertmanager"]
AM --> N["Notification<br/>(courriel, messagerie)"]
| Composant | Rôle |
|---|---|
| Prometheus | Interroge régulièrement (modèle pull) les points d'accès /metrics des cibles, stocke des séries temporelles, évalue des règles d'alerte |
| Exporters | Exposent des métriques : node-exporter (machines), kube-state-metrics (état des objets Kubernetes) ; le kubelet expose les métriques des conteneurs |
| Grafana | Affiche des tableaux de bord à partir de Prometheus |
| Alertmanager | Reçoit les alertes de Prometheus, les regroupe, et les envoie vers un canal de notification |
Prometheus s'interroge avec le langage PromQL :
up # 1 si la cible répond, 0 sinon
sum by (pod) (rate(container_cpu_usage_seconds_total{namespace="tp-k8s"}[5m])) # CPU par Pod
container_memory_working_set_bytes{namespace="tp-k8s"} # mémoire par conteneur
Une pile de supervision consomme des ressources : sur des VMs modestes, on limite la durée de rétention et le nombre de cibles.
Que surveiller ?¶
Quatre « signaux d'or » résument la santé d'un service :
| Signal | Question |
|---|---|
| Latence | En combien de temps répond-il ? |
| Trafic | Combien de requêtes reçoit-il ? |
| Erreurs | Quelle proportion de requêtes échoue ? |
| Saturation | Quelle part de ses ressources (CPU, mémoire, disque) utilise-t-il ? |
Une bonne alerte signale un symptôme qui touche les utilisateurs et appelle une action. Trop d'alertes fait ignorer les vraies.
6. Méthodologie de diagnostic¶
Face à un incident, on ne corrige pas au hasard : on suit une démarche.
- Observer : que voit-on exactement ? Depuis quand ? Qu'est-ce qui a changé ?
- Formuler une hypothèse sur la cause, couche par couche.
- Tester l'hypothèse avec une commande, sans modifier l'état si possible.
- Corriger, une seule modification à la fois.
- Vérifier que le symptôme a disparu, et noter la cause racine.
On progresse du plus visible au plus profond :
flowchart TB
A["kubectl get pods<br/>quel statut ?"] --> B{"Statut"}
B -- "Pending" --> C["describe : ressources, quota,<br/>volume, affinité"]
B -- "ImagePullBackOff" --> D["describe : nom ou tag d'image,<br/>accès au registre"]
B -- "CrashLoopBackOff" --> E["logs --previous :<br/>erreur applicative, config, OOMKilled"]
B -- "Running mais 0/1" --> F["describe : sonde readiness,<br/>dépendances"]
B -- "Running 1/1,<br/>service injoignable" --> G["Service : endpoints, sélecteur,<br/>DNS, NetworkPolicy, Ingress"]
| Symptôme | Premières vérifications |
|---|---|
Pending |
Événements du Pod : Insufficient cpu/memory, quota, PVC non lié, nœud non planifiable |
ImagePullBackOff |
Nom et tag de l'image, accès Internet, registre privé |
CrashLoopBackOff |
logs --previous, code de sortie, variables et Secrets manquants, OOMKilled |
CreateContainerConfigError |
ConfigMap, Secret ou clé introuvable, runAsNonRoot incompatible avec l'image |
Running mais 0/1 |
Sonde readiness en échec : describe, dépendance indisponible |
| Service injoignable | get endpoints vide ? sélecteur, port, DNS, NetworkPolicy |
| Application lente | kubectl top, limites de CPU (ralentissement), mémoire |
Nœud NotReady |
describe node, journalctl -u k3s, ressources, réseau |
Outils complémentaires :
| Besoin | Commande |
|---|---|
| Tester depuis l'intérieur du cluster | kubectl run test --rm -it --image=busybox:1.36 --restart=Never -- sh |
| Accéder à un Service depuis votre poste | kubectl port-forward service/<nom> 8080:80 |
| Déboguer un conteneur sans shell | kubectl debug -it <pod> --image=busybox:1.36 --target=<conteneur> |
| Voir la définition réelle d'un objet | kubectl get <objet> -o yaml |
| Journaux de k3s sur un nœud | sudo journalctl -u k3s (ou k3s-agent) |
!!! tip "À retenir" - Les conteneurs écrivent sur stdout ; kubectl logs (avec --previous après un crash) et describe (section Events) sont les premiers outils. - Les événements sont éphémères (environ une heure) : consultez-les vite. - readiness retire le Pod du Service ; liveness redémarre le conteneur ; startup protège un démarrage lent. - La liveness ne doit pas dépendre de services externes ; la readiness peut le faire. - metrics-server : instantané CPU et mémoire (kubectl top). Prometheus : historique et alertes ; Grafana : tableaux de bord ; Alertmanager : notifications. - Diagnostic : observer, hypothèse, test, une correction, vérification ; du statut du Pod vers le réseau et le nœud.
Pour vérifier votre compréhension¶
Un conteneur plante au démarrage et kubectl logs ne montre rien d'utile. Que faites-vous ?
Utiliser kubectl logs <pod> --previous pour lire les logs de l'exécution précédente, et kubectl describe pod pour le code de sortie (par exemple 137 pour OOMKilled) et les événements.
Quelle différence d'effet entre l'échec d'une sonde liveness et celui d'une sonde readiness ?
La liveness redémarre le conteneur. La readiness retire le Pod des endpoints du Service, sans le redémarrer : il ne reçoit plus de trafic jusqu'à ce qu'il soit de nouveau prêt.
Pourquoi ne faut-il pas faire dépendre une sonde liveness de la base de données ?
Si la base tombe, toutes les instances de l'application seraient redémarrées en boucle, sans aucun bénéfice (le redémarrage ne répare pas la base). C'est la readiness qui doit exprimer cette dépendance.
Comment une sonde readiness améliore-t-elle un rolling update ?
Un nouveau Pod n'est compté comme prêt, et donc l'ancien n'est supprimé, que lorsque sa sonde réussit. Sans sonde, un Pod est considéré prêt dès que son conteneur démarre, même si l'application ne sert pas encore.
Quelle limite de kubectl top motive l'usage de Prometheus ?
kubectl top ne donne que la valeur actuelle du CPU et de la mémoire, sans historique ni alerte. Prometheus conserve des séries temporelles et déclenche des alertes.
Un Service ne répond pas alors que les Pods sont Running 1/1. Quelles vérifications faites-vous, dans l'ordre ?
kubectl get endpoints (liste vide : sélecteur ou labels), le port et le targetPort, la résolution DNS depuis un Pod de test, les NetworkPolicies, puis l'Ingress si l'accès passe par lui.