Aller au contenu
Conteneurs & Orchestration medium

Popeye : scanner de santé pour clusters Kubernetes

21 min de lecture

logo popeye

Popeye scanne votre cluster Kubernetes en temps réel et génère un rapport de santé avec un score de 0 à 100. L'outil détecte les mauvaises configurations courantes : images taguées latest, absence de limites CPU/mémoire, pods sans probes, conteneurs root, secrets inutilisés. En 30 secondes, vous savez ce qui doit être corrigé.

Ce guide vous montre comment :

  • Installer Popeye via mise, Homebrew ou binaire
  • Lancer un scan et interpréter les résultats
  • Filtrer l'analyse par namespace ou type de ressource
  • Personnaliser les règles avec spinach.yaml
  • Intégrer dans une CI/CD avec seuil de score minimal

Kubernetes accepte de déployer des manifestes même s'ils violent les bonnes pratiques. Un Deployment sans limites de ressources ? Accepté. Une image nginx:latest ? Déployée. Un pod qui tourne en root ? Aucune erreur.

Ces configurations fonctionnent... jusqu'au jour où :

  • Un pod consomme toute la mémoire du node et fait crasher les autres
  • Une mise à jour automatique de latest casse l'application
  • Une vulnérabilité exploite les privilèges root du conteneur

Popeye détecte ces problèmes avant qu'ils ne surviennent en analysant ce qui tourne réellement dans le cluster (pas les fichiers YAML sur disque).

Ce que Popeye vérifieExemple de problème détecté
ImagesTag latest, image sans tag
RessourcesPas de requests/limits CPU/mémoire
ProbesPas de liveness/readiness probe
SécuritéContainer root, pas de NetworkPolicy
RBACClusterRoles/Roles inutilisés
Ressources orphelinesConfigMaps, Secrets non référencés

Popeye est un binaire Go statique, sans dépendance ni démon à démarrer. Il s'installe sur votre poste et se connecte au cluster avec votre kubeconfig courant, exactement comme kubectl. Trois voies existent selon vos habitudes : un gestionnaire de versions, un gestionnaire de paquets système, ou le binaire téléchargé directement depuis les releases GitHub.

mise épingle la version outil par outil et la restaure automatiquement dans chaque projet, ce qui évite qu'un collègue et la CI n'obtiennent pas le même rapport.

Fenêtre de terminal
mise install popeye@latest
mise use popeye@latest

Sur macOS et Linux, le tap de l'auteur fournit la formule officielle. La version installée suit alors le rythme de brew upgrade, sans épinglage possible par projet.

Fenêtre de terminal
brew install derailed/popeye/popeye

C'est la méthode à retenir pour un runner de CI, où l'on veut une version figée et aucun gestionnaire de paquets à installer. L'archive est téléchargée par son numéro de version explicite, jamais par un tag mouvant.

Fenêtre de terminal
# Télécharger depuis GitHub
curl -LO https://github.com/derailed/popeye/releases/download/v0.22.1/popeye_linux_amd64.tar.gz
tar -xzf popeye_linux_amd64.tar.gz
sudo mv popeye /usr/local/bin/

Vérification :

Fenêtre de terminal
popeye version
Version: 0.22.1
Commit: 35b554961dcdabfa7aba6bd070673c6c24c70884
Date: 2025-01-28T15:31:22Z

Popeye utilise votre kubeconfig actif (comme kubectl). Lancez un scan complet :

Fenêtre de terminal
popeye

Résultat sur un cluster Kind fraîchement créé :

GENERAL [KIND-POPEYE-TEST]
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
· Connectivity.............................✅
· MetricServer.............................💥
CLUSTER (1 SCANNED) 💥 0 😱 0 🔊 0 ✅ 1 100٪
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
· Version..................................✅
✅ [POP-406] K8s version OK.
DEPLOYMENTS (2 SCANNED) 💥 0 😱 1 🔊 1 ✅ 0 50٪
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
· demo/nginx-bad...........................😱
🐳 nginx
😱 [POP-101] Image tagged "latest" in use.
😱 [POP-106] No resources requests/limits defined.
· demo/nginx-good..........................🔊
🐳 nginx
🔊 [POP-108] Unnamed port 8080.
SUMMARY
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Your cluster score: A (91)

Le rapport se lit à deux vitesses. Le score global en fin de sortie donne la tendance et sert de garde-fou en CI ; les codes POP-xxx ligne par ligne indiquent quoi corriger et où. Un piège classique consiste à ne regarder que le score : un cluster noté A peut très bien héberger un pod privilégié, parce qu'un seul problème sur des dizaines de ressources conformes pèse peu dans la moyenne.

Popeye classe chaque constat sur quatre niveaux, du simple conseil au problème bloquant. C'est ce niveau, et non le nombre de lignes, qui détermine l'impact sur le score et ce que vous devez traiter en priorité.

IcôneNiveauSignificationAction
OKRessource conformeRien à faire
🔊InfoRecommandationAmélioration optionnelle
😱WarnProblème potentielÀ corriger
💥ErrorProblème critiqueAction immédiate requise

Chaque règle porte un identifiant stable de la forme POP-xxx, regroupé par familles : la série 100 rassemble les constats sur les conteneurs (image, probes, ressources), la série 300 ceux liés aux ServiceAccounts et aux privilèges, la série 400 les références manquantes. Ces codes sont la clé de la personnalisation, puisque c'est par eux que l'on exclut une règle dans spinach.yaml.

CodeMessageExplicationSolution
POP-101Image tagged "latest"L'image peut changer sans préavisSpécifier une version : nginx:1.27.0
POP-102No probes definedKubernetes ne sait pas si l'app est saineAjouter liveness/readiness probes
POP-106No resources requests/limitsLe pod peut consommer toutes les ressourcesDéfinir requests et limits
POP-107No resource limitsLimits absentes (requests présentes)Ajouter limits CPU/memory
POP-300Uses "default" ServiceAccountRisque de permissions excessivesCréer un ServiceAccount dédié
POP-302Pod running as rootRisque de sécuritéAjouter runAsNonRoot: true
POP-400Unable to locate referenceConfigMap/Secret inutiliséSupprimer ou utiliser la ressource
POP-1204Not secured by NetworkPolicyTrafic non filtréAjouter une NetworkPolicy

Le score (0-100) est calculé par type de ressource, puis agrégé. Chaque problème réduit le score selon sa sévérité :

  • Error : forte pénalité
  • Warn : pénalité moyenne
  • Info : faible impact

Un cluster de production devrait viser 80+ (note B minimum).

ScoreNoteInterprétation
90-100AExcellent, peu de corrections
80-89BBon, quelques améliorations
70-79CAcceptable, corrections recommandées
60-69DInsuffisant, action requise
0-59FCritique, problèmes majeurs

Un scan complet sur un cluster de production produit des centaines de lignes, dont une bonne partie concerne des composants système que vous ne maîtrisez pas. Les filtres servent donc à deux choses : rendre le rapport lisible, et cibler le périmètre dont votre équipe est réellement responsable. Ils se combinent librement.

Sans option, Popeye n'analyse que le namespace courant de votre contexte kubeconfig. -n en cible un autre, -A bascule sur l'ensemble du cluster.

Fenêtre de terminal
# Un seul namespace
popeye -n demo
# Tous les namespaces
popeye -A

Utilisez l'option -s (sections) avec les alias de ressources :

Fenêtre de terminal
# Pods et Deployments uniquement
popeye -n demo -s po,deploy
RessourceAlias
Podpo
Deploymentdp, deploy
Servicesvc
ConfigMapcm
Secretsec
ServiceAccountsa
Ingressing
StatefulSetsts
DaemonSetds
Jobjob
CronJobcj
PersistentVolumeClaimpvc
NetworkPolicynp
HorizontalPodAutoscalerhpa

Afficher uniquement les problèmes warn et plus :

Fenêtre de terminal
popeye -l warn

Valeurs possibles : ok, info, warn, error

Le format se choisit avec -o et détermine à qui s'adresse le rapport. La console convient à une inspection manuelle, le JSON à un traitement automatisé, le HTML à un partage avec une équipe qui n'a pas accès au cluster. Ce choix est aussi ce qui rend Popeye utilisable en CI, où une sortie colorée avec des émojis n'a aucun intérêt.

Sortie interactive avec couleurs et icônes, destinée à la lecture directe dans un terminal.

Fenêtre de terminal
popeye

Le JSON contient l'intégralité des constats avec leur code et leur niveau, ce qui permet de les rejouer dans un script ou de les archiver pour suivre l'évolution.

Fenêtre de terminal
popeye -o json > rapport.json

Le rendu HTML produit un fichier autonome, à envoyer à une équipe applicative ou à joindre à une revue d'architecture. L'option --save écrit le rapport dans le répertoire désigné par POPEYE_REPORT_DIR.

Fenêtre de terminal
POPEYE_REPORT_DIR=\$(pwd) popeye --save --out html --output-file rapport.html

Ce format n'émet que le score numérique, ce qui facilite sa récupération dans une variable de pipeline ou son envoi vers un tableau de bord.

Fenêtre de terminal
popeye -o score
# Retourne uniquement le chiffre, ex: 91

Quatre formats complémentaires couvrent des besoins plus spécifiques, notamment l'affichage dans l'onglet de tests d'un serveur d'intégration ou la remontée vers une solution de métriques :

  • yaml : rapport YAML
  • junit : format JUnit pour intégration CI
  • jurassic : texte sans couleurs ni icônes
  • prometheus : métriques Prometheus

Créez un fichier spinach.yaml pour adapter les règles à votre contexte :

spinach.yaml
# Configuration Popeye
popeye:
# Seuils d'utilisation des ressources
allocations:
cpu:
underPercUtilization: 200 # Alerte si CPU sous-utilisé > 200%
overPercUtilization: 50 # Alerte si CPU sur-utilisé > 50%
memory:
underPercUtilization: 200
overPercUtilization: 50
# Exclusions
excludes:
# Exclusions globales (tous les linters)
global:
fqns: [rx:^kube-] # Ignorer kube-system, kube-public...
codes: ["300"] # Ignorer le code POP-300
# Exclusions par linter
linters:
pods:
instances:
- fqns: [kube-system/coredns]
codes: ["302"] # Ignorer "running as root" pour CoreDNS
namespaces:
codes: ["100"] # Ignorer namespace inactif
# Seuils pour les pods
resources:
pod:
restarts: 5 # Alerte si > 5 restarts
limits:
cpu: 80
memory: 75
# Registries autorisés (tout autre registre = erreur)
registries:
- docker.io
- ghcr.io
- quay.io
- registry.k8s.io
# Modifier la sévérité d'un code
overrides:
- code: "206" # PodDisruptionBudget manquant
severity: 1 # Réduire à Info (1=info, 2=warn, 3=error)

Lancez Popeye avec ce fichier :

Fenêtre de terminal
popeye -f spinach.yaml

Popeye analysant un cluster vivant et non des fichiers, il ne se branche pas sur une pull request comme un linter de manifestes : il s'exécute après le déploiement, ou sur un déclencheur planifié. Le job a besoin d'un kubeconfig valide, donc de credentials stockés en secret, avec des droits en lecture seule. L'option --min-score transforme ensuite le rapport en garde-fou, en faisant échouer le job sous un seuil que vous fixez.

Le workflow ci-dessous combine un déclenchement sur main et un scan quotidien planifié, puis conserve le rapport JSON en artefact pour pouvoir comparer d'un jour sur l'autre.

.github/workflows/popeye.yaml
name: Kubernetes Lint
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * *' # Tous les jours à 6h
jobs:
popeye:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "\${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
- name: Install Popeye
run: |
curl -LO https://github.com/derailed/popeye/releases/download/v0.22.1/popeye_linux_amd64.tar.gz
tar -xzf popeye_linux_amd64.tar.gz
sudo mv popeye /usr/local/bin/
- name: Run Popeye
run: |
popeye -A --min-score 80 -o json > popeye-report.json
- name: Upload Report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: popeye-report
path: popeye-report.json

L'option --min-score 80 fait échouer le job si le score est inférieur à 80.

Ici l'image officielle évite l'étape d'installation, et la règle limite l'exécution aux pipelines planifiés. Le seuil est plus bas et --force-exit-zero neutralise l'échec : c'est le réglage à adopter au démarrage, tant que le rapport sert à mesurer plutôt qu'à bloquer.

.gitlab-ci.yml
popeye:
stage: validate
image: quay.io/derailed/popeye:v0.22.1@sha256:ee3e66d023e736eec64edd6ac86a36e33b6a389ff508994ba356cc20b756e5c4
script:
- popeye -A --min-score 75 --force-exit-zero -o yaml
rules:
- if: \$CI_PIPELINE_SOURCE == "schedule"

Popeye peut aussi tourner dans le cluster via un CronJob, ce qui supprime le besoin d'exporter un kubeconfig vers un runner externe. L'identité utilisée devient alors un ServiceAccount dédié, auquel un ClusterRole n'accorde que les verbes get et list : Popeye ne modifie jamais rien, il n'a donc aucune raison d'obtenir davantage. Le manifeste ci-dessous crée le namespace, l'identité, les droits et la tâche planifiée en une seule fois.

popeye-cronjob.yaml
apiVersion: v1
kind: Namespace
metadata:
name: popeye
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: popeye
namespace: popeye
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: popeye
rules:
- apiGroups: [""]
resources: [configmaps, endpoints, namespaces, nodes, persistentvolumes,
persistentvolumeclaims, pods, secrets, serviceaccounts, services]
verbs: [get, list]
- apiGroups: [apps]
resources: [daemonsets, deployments, statefulsets, replicasets]
verbs: [get, list]
- apiGroups: [networking.k8s.io]
resources: [ingresses, networkpolicies]
verbs: [get, list]
- apiGroups: [autoscaling]
resources: [horizontalpodautoscalers]
verbs: [get, list]
- apiGroups: [policy]
resources: [poddisruptionbudgets]
verbs: [get, list]
- apiGroups: [rbac.authorization.k8s.io]
resources: [clusterroles, clusterrolebindings, roles, rolebindings]
verbs: [get, list]
- apiGroups: [metrics.k8s.io]
resources: [pods, nodes]
verbs: [get, list]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: popeye
subjects:
- kind: ServiceAccount
name: popeye
namespace: popeye
roleRef:
kind: ClusterRole
name: popeye
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: popeye
namespace: popeye
spec:
schedule: "0 */6 * * *" # Toutes les 6 heures
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
serviceAccountName: popeye
restartPolicy: Never
containers:
- name: popeye
image: quay.io/derailed/popeye:v0.22.1
args:
- -A
- -o
- yaml
- --force-exit-zero
resources:
limits:
cpu: 500m
memory: 256Mi
Fenêtre de terminal
kubectl apply -f popeye-cronjob.yaml

Un scan Popeye ne coûte presque rien, ce qui rend l'outil facile à adopter mais aussi facile à laisser dériver. Les quatre réflexes qui suivent visent le même objectif : obtenir un rapport que l'équipe lit vraiment, plutôt qu'une liste de centaines de constats que plus personne n'ouvre.

Planifiez un scan quotidien ou après chaque déploiement majeur. Les problèmes s'accumulent vite.

Sur un grand cluster, analysez d'abord un namespace critique :

Fenêtre de terminal
popeye -n production -l warn

Si vous excluez des règles dans spinach.yaml, documentez pourquoi :

excludes:
linters:
pods:
instances:
# CoreDNS tourne en root par design (upstream)
# Voir: https://github.com/coredns/coredns/issues/...
- fqns: [kube-system/coredns]
codes: ["302"]

Exportez les rapports JSON et suivez l'évolution du score. Un score qui baisse indique une dette technique croissante.

Les incidents rencontrés avec Popeye tiennent rarement à l'outil lui-même : ce sont surtout des questions de droits RBAC, de composants absents du cluster ou de volume de ressources à parcourir. Le tableau associe chaque symptôme à sa cause la plus fréquente.

ProblèmeCause probableSolution
MetricServer 💥metrics-server non installéInstaller metrics-server ou ignorer
Timeout sur grand clusterTrop de ressources à scannerFiltrer par namespace -n
Permissions insuffisantesRBAC manquantVérifier les droits get/list
Score très bas sur kube-systemComposants système non optimisésExclure via spinach.yaml
  • Popeye analyse le cluster en temps réel (pas les fichiers YAML)
  • Le score de 0 à 100 donne une vue d'ensemble rapide
  • Les codes POP-xxx identifient précisément chaque problème
  • spinach.yaml permet d'adapter les règles à votre contexte
  • Intégrez dans la CI/CD avec --min-score pour bloquer les régressions
  • Popeye est en lecture seule : il ne modifie jamais vos ressources

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn