Exploitation
Operations
Supervision et alertes, sauvegarde et reprise, procédures pour les pannes courantes et les mises à jour.
Monitoring and alerts, backup and recovery, runbooks for common failures and upgrades.
SupervisionMonitoring
Prometheus + Alertmanager + Grafana (kube-prometheus-stack) : nœuds, pods, API, services. Les alertes sont aussi lues par le lab, qui les affiche en direct et peut les relayer vers Slack ou par e-mail.
Règles d'alerte : celles fournies par kube-prometheus-stack, plus des règles écrites pour le lab (nœud injoignable, CPU et mémoire des nœuds, pod en boucle de redémarrage, sauvegardes). Les alertes sont visibles dans Alertmanager, Grafana et sur la page Observabilité du lab, et Alertmanager les envoie par mail au propriétaire du lab, pour tous les namespaces : une alerte critical tout de suite puis toutes les 4 h tant qu'elle dure, les autres toutes les 24 h, et un mail à la fin. Restent sans envoi : Watchdog (toujours active, elle prouve que la chaîne fonctionne), les alertes info, et KubeCPUOvercommit / KubeMemoryOvercommit, vraies par construction avec un seul worker. Le mail part directement vers le serveur de messagerie du domaine, sans compte SMTP.
kube-proxy n'est plus collecté : Kubespray lie ses métriques à 127.0.0.1, sa cible était en panne depuis l'installation et ses deux alertes sonnaient sans fin.
La sauvegarde nocturne publie l'heure de son dernier succès, lue par node-exporter (collecteur textfile) : LabBackupStale se déclenche après 30 h sans sauvegarde réussie.
Le trafic d'entrée : ingress-nginx publie ses métriques (requêtes, codes de réponse, latence, débit, connexions, rechargements de configuration, expiration des certificats), lues par Prometheus. Le tableau de bord Ingress NGINX les présente par namespace, Ingress et hôte ; il est versionné et généré par un script, et le pipeline vérifie que les deux correspondent. Cinq alertes s'y ajoutent : métriques absentes, rechargement en échec, plus de 5 % de réponses 5xx, latence p95 au-delà de 2 s, certificat à moins de 14 jours de l'expiration.
Les logs : Grafana Alloy, un par nœud, lit les logs de tous les pods et le journal de kubelet et containerd ; une instance de plus envoie les événements Kubernetes. Tout part dans Loki, gardé 14 jours puis effacé par son compacteur, avec des limites d'ingestion. Alloy n'a que le droit de lire les pods, sans aucune capacité Linux et en lecture seule ; Loki n'accepte que Grafana, Prometheus et Alloy. Cinq tableaux de bord (vue d'ensemble, erreurs, journal d'accès de l'ingress, événements et nœuds, santé de la chaîne) sont réservés à l'administration : les logs peuvent contenir des données personnelles, ils ne sont pas dans la démo publique. Une alerte prévient quand un disque passe sous 20 % libres, bien avant que kubelet n'expulse des pods.
Prometheus + Alertmanager + Grafana (kube-prometheus-stack): nodes, pods, the API, the services. Alerts are also read by the lab, which shows them live and can relay them to Slack or by e-mail.
Alert rules: the ones shipped with kube-prometheus-stack, plus rules written for the lab (node down, node CPU and memory, crash-looping pod, backups). Alerts are visible in Alertmanager, Grafana and on the lab's Observability page, and Alertmanager sends them by mail to the lab owner, for every namespace: a critical alert at once then every 4 h while it lasts, the others every 24 h, and a mail when it ends. Not sent: Watchdog (always firing, it proves the chain works), info alerts, and KubeCPUOvercommit / KubeMemoryOvercommit, true by construction with a single worker. The mail goes straight to the domain's mail server, with no SMTP account.
kube-proxy is no longer scraped: Kubespray binds its metrics to 127.0.0.1, its target had been down since the install and its two alerts fired endlessly.
The nightly backup publishes the time of its last success, read by node-exporter (textfile collector): LabBackupStale fires after 30 h without a successful backup.
Incoming traffic: ingress-nginx publishes its metrics (requests, response codes, latency, throughput, connections, configuration reloads, certificate expiry), read by Prometheus. The Ingress NGINX dashboard shows them per namespace, Ingress and host; it is versioned and generated by a script, and the pipeline checks the two match. Five alerts come with it: metrics missing, failed reload, more than 5 % of 5xx responses, p95 latency over 2 s, certificate less than 14 days from expiry.
Logs: Grafana Alloy, one per node, reads the logs of every pod and the journal of kubelet and containerd; one more instance sends the Kubernetes events. Everything goes to Loki, kept for 14 days then deleted by its compactor, with ingestion limits. Alloy may only read pods, with no Linux capability and read-only; Loki only accepts Grafana, Prometheus and Alloy. Five dashboards (overview, errors, ingress access log, events and nodes, pipeline health) are for administration only: logs may hold personal data, so they are not in the public demo. An alert fires when a disk falls under 20 % free, well before kubelet evicts pods.
Sauvegarde et repriseBackup and recovery
Un timer systemd sur master1 lance chaque nuit un script versionné dans le dépôt infra. Chaque machine garde une copie des données de l'autre : perdre un nœud ne fait pas perdre ses sauvegardes.
A systemd timer on master1 runs a script, versioned in the infra repository, every night. Each machine keeps a copy of the other one's data: losing a node does not lose its backups.
| Donnée | Data | Méthode | Method | Copie gardée sur | Copy kept on |
|---|---|---|---|---|---|
| État du cluster | Cluster state | snapshot etcd + PKI du cluster | etcd snapshot + cluster PKI | master1 + worker1 | |
| GitLab | gitlab-backup (base, dépôts, artefacts) + gitlab-secrets.json | gitlab-backup (database, repositories, artifacts) + gitlab-secrets.json | master1 |
- Rétention : 7 copies de chaque. RPO : 24 h (fréquence quotidienne). RTO : non défini, aucune restauration complète n'a encore été chronométrée.
- Vérifié : snapshot etcd restauré dans un dossier jetable (1 804 clés), archive GitLab complète (base + 43 dépôts).
- Pas sauvegardé, volontairement : le registre d'images (chaque image se reconstruit en relançant son pipeline) et les métriques Prometheus (7 jours de rétention).
- Retention: 7 copies of each. RPO: 24 h (daily schedule). RTO: not defined, no full restore has been timed yet.
- Checked: etcd snapshot restored into a scratch directory (1,804 keys), complete GitLab archive (database + 43 repositories).
- Not backed up, on purpose: the image registry (each image is rebuilt by re-running its pipeline) and Prometheus metrics (7-day retention).
systemctl list-timers lab-backup.timer journalctl -u lab-backup --since yesterday sudo etcdutl snapshot status /var/backups/lab/etcd/etcd-<date>.db -w table
ProcéduresRunbook
Un site ne répond plus
A site stopped answering
kubectl get nodes kubectl get pods -A | grep -v Running kubectl -n ingress-nginx logs ds/ingress-nginx-controller --tail=50 kubectl get ingress -A
GitLab : 502 ou 504
GitLab: 502 or 504
L'ingress est vivant, c'est l'upstream sur la machine qui ne répond pas. Sur worker1 :
The ingress is alive; the upstream on the host is not answering. On worker1:
sudo gitlab-ctl status # GitLab : tous les services en "run"
ss -ltn | grep -E ':(8929|5443) '
Après un redémarrage de GitLab, la page « Waiting for GitLab to boot » (502) est normale pendant une à deux minutes.
After a GitLab restart, the “Waiting for GitLab to boot” page (502) is normal for a minute or two.
Un certificat ne se renouvelle pas
A certificate does not renew
kubectl get certificate,order,challenge -A kubectl -n cert-manager logs deploy/cert-manager --tail=50
Cause fréquente : le port 80 ne mène plus à l'ingress, ou l'enregistrement DNS ne pointe plus vers le worker.
Common cause: port 80 no longer reaches the ingress, or the DNS record no longer points to the worker.
Mettre GitLab à jour
Upgrade GitLab
GitLab ne se met pas à jour d'un bond : il faut passer par chaque arrêt obligatoire publié par GitLab et laisser finir ses migrations en arrière-plan avant le suivant. Un script le fait sur la machine : il lit la liste officielle des arrêts et la version minimale de PostgreSQL, sauvegarde GitLab, installe chaque version exacte, attend que GitLab réponde et que ses migrations soient terminées, monte PostgreSQL quand l'étape suivante l'exige, et garde le paquet bloqué entre deux lancements pour qu'une mise à jour système ne saute jamais un arrêt. À la moindre erreur, il s'arrête ; relancé, il reprend où il en était, après avoir redémarré les services qu'une étape ratée aurait laissés arrêtés.
GitLab does not upgrade in one jump: it has to go through each required upgrade stop GitLab publishes, and let its background migrations finish before the next one. A script does it on the host: it reads the official list of stops and the minimum PostgreSQL version, backs GitLab up, installs each exact version, waits until GitLab answers and its migrations are done, upgrades PostgreSQL when the next step needs it, and keeps the package on hold between runs so that a system update never jumps over a stop. At the first error it stops; run again, it resumes where it was, after starting the services a failed step may have left stopped.
sudo upgrade-gitlab.sh # le plan, sans rien changer / the plan, changes nothing
sudo systemd-run --unit=gitlab-upgrade upgrade-gitlab.sh --apply --yes
sudo tail -f /var/log/gitlab-upgrade.log
Revenir à la version précédente
Roll back to the previous version
Les images sont taguées par commit, donc l'ancienne version est toujours dans le registre. rollout undo remet le ReplicaSet précédent, avec le même déploiement progressif sans coupure. Le pipeline suivant sur main redéploiera le dernier commit : corriger ou annuler le commit fautif par une merge request.
Images are tagged by commit, so the previous version is still in the registry. rollout undo brings back the previous ReplicaSet, with the same zero-downtime rolling update. The next pipeline on main will redeploy the latest commit: fix or revert the faulty commit through a merge request.
kubectl -n portfolio rollout history deploy/portfolio-web kubectl -n portfolio rollout undo deploy/portfolio-web kubectl -n portfolio rollout status deploy/portfolio-web
Un pod redémarre en boucle ou est tué (OOMKilled)
A pod restarts in a loop or gets killed (OOMKilled)
kubectl get pods -A | awk '$5 > 0' # colonne RESTARTS / RESTARTS column
kubectl -n <ns> describe pod <pod> | grep -A3 'Last State'
kubectl top pod -n <ns> --containers
Reason: OOMKilled : le conteneur a dépassé sa limite mémoire. Comparer avec la consommation réelle avant de remonter la limite, et vérifier qu'il reste de la place sur le nœud (kubectl describe node, section Allocated resources).
Reason: OOMKilled: the container went past its memory limit. Compare with real usage before raising the limit, and check there is room left on the node (kubectl describe node, Allocated resources).
La connexion SSO échoue (Grafana, GitLab, notes, board)
SSO sign-in fails (Grafana, GitLab, notes, board)
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hoortrend.com/ # 200 attendu / expected
kubectl -n auth-lab get pods
kubectl -n auth-lab logs deploy/authserver --tail=50 # codes émis, refus, révocations / codes issued, refusals
kubectl -n monitoring logs deploy/monitoring-grafana -c grafana | grep -i oauth | tail
sudo grep -h openid_connect /var/log/gitlab/gitlab-rails/application_json.log | tail # sur worker1 / on worker1
invalid_client: le secret d'un client a changé d'un côté seulement (l'application et l'empreinte gardée par le serveur doivent changer ensemble).- Page « redirect_uri non enregistrée » : l'application envoie une adresse de retour différente de celle enregistrée (pour Grafana, elle dépend de son
root_url). - GitLab, « Invalid 'state' » : un onglet est resté ouvert sur une ancienne tentative ; recommencer la connexion.
- Changer la clé qui signe les jetons :
scripts/rotate-signing-key.sh(dépôt auth-lab), puis--prune15 minutes plus tard.
invalid_client: a client secret changed on one side only (the application and the hash kept by the server must change together).- “redirect_uri non enregistrée” page: the application sends a return address other than the registered one (for Grafana it depends on its
root_url). - GitLab, “Invalid 'state'”: a tab stayed open on an older attempt; start the sign-in again.
- Changing the key that signs tokens:
scripts/rotate-signing-key.sh(auth-lab repository), then--prune15 minutes later.
Exposer un nouveau sous-domaine
Expose a new subdomain
- Créer l'enregistrement DNS A vers l'IP du worker.
- Create the DNS A record to the worker's IP.
- Écrire un Ingress avec l'annotation
cert-manager.io/cluster-issuer: letsencrypt-prodet un bloctls. - Write an Ingress with the
cert-manager.io/cluster-issuer: letsencrypt-prodannotation and atlsblock. - Vérifier :
kubectl get certificatepasse àTrueen quelques secondes. - Check:
kubectl get certificateturnsTruewithin seconds.
Une alerte arrive par mail
An alert arrives by mail
- Lire l'alerte dans Alertmanager ou sur la page Observabilité : son nom, son namespace et sa description disent quoi regarder.
- Read the alert in Alertmanager or on the Observability page: its name, namespace and description say what to look at.
- Si elle est attendue (une maintenance, un test), la faire taire pour une durée limitée avec
amtool silence addet un commentaire ; si elle est vraie par construction, l'ajouter aux routes sans envoi desre-alert-routing, par une MR. - If it is expected (maintenance, a test), silence it for a limited time with
amtool silence addand a comment; if it is true by construction, add it to the no-send routes ofsre-alert-routing, through a merge request. - Tester la chaîne : une alerte factice envoyée avec
amtool alert addarrive en mail environ 30 s plus tard. - Test the chain: a fake alert sent with
amtool alert addarrives by mail about 30 s later.
Un service doit être joignable depuis Internet sur worker1
A service must be reachable from the Internet on worker1
- Préférer l'ingress : un sous-domaine en HTTPS ne demande aucun port de plus.
- Prefer the ingress: an HTTPS subdomain needs no extra port.
- Sinon, ajouter le port à
TCP_OPENouUDP_OPENdans le script du pare-feu (dépôt infra), par une MR, puis le réinstaller et lancersudo systemctl restart hoortrend-firewall. - Otherwise, add the port to
TCP_OPENorUDP_OPENin the firewall script (infra repository), through a merge request, then reinstall it and runsudo systemctl restart hoortrend-firewall. - Vérifier :
sudo hoortrend-firewall statusmontre le compteur de la nouvelle règle qui monte. - Check:
sudo hoortrend-firewall statusshows the new rule's counter going up.