Introduction
Lorsqu’un pod échoue avec CreateContainerConfigError ou qu’un conteneur se termine immédiatement avec operation not permitted, la cause est souvent cachée dans le securityContext du pod. Ce guide transforme ces échecs opaques en un processus de dépannage reproductible. Il s’adresse aux développeurs, aux ingénieurs de plateforme, aux consultants DevOps et à toute personne qui exploite des clusters Kubernetes et souhaite résoudre les problèmes de contexte de sécurité sans deviner.
L’article parcourt des scénarios de défaillance réalistes, montre les commandes exactes pour les diagnostiquer et fournit des correctifs minimaux qui maintiennent la sécurité du cluster. Chaque étape de récupération est réversible là où Kubernetes le permet, et chaque section inclut une commande de vérification pour s’assurer que le problème est réellement résolu.
Avant d’effectuer des modifications, observez toujours l’état actuel : vérifiez la version de Kubernetes, la configuration de sécurité du pod et les politiques de sécurité du nœud. Capturez les horodatages et les sorties de commande. Limitez tout changement à un seul champ ciblé et préparez un plan de retour en arrière. Cette discipline opérationnelle évite de transformer une petite erreur de configuration en une panne plus large.
Inventaire de la version et de l’environnement
Commencez par noter la version de Kubernetes, le runtime de conteneur et le système d’exploitation du nœud. Le comportement du contexte de sécurité diffère selon les versions, notamment en ce qui concerne seccomp et AppArmor. Utilisez les commandes en lecture seule suivantes pour recueillir les détails :
kubectl version --short
kubectl get nodes -o wide
kubectl describe node <nom-du-nœud> | grep -A5 "System Info"
La sortie attendue pour kubectl version --short ressemble à :
Client Version: v1.28.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.28.2
Si la version du serveur est antérieure à la v1.19, le champ seccompProfile dans le contexte de sécurité du pod peut ne pas être disponible en tant qu’API stable. Cela réduit immédiatement le chemin de dépannage.
Ensuite, obtenez une vue d’ensemble de tous les pods et de leurs champs liés à la sécurité :
kubectl get pods -o custom-columns='NAME:.metadata.name,STATUS:.status.phase,RUNASUSER:.spec.containers[*].securityContext.runAsUser,RUNASGROUP:.spec.containers[*].securityContext.runAsGroup,PRIVILEGED:.spec.containers[*].securityContext.privileged'
Exemple de sortie :
NAME STATUS RUNASUSER RUNASGROUP PRIVILEGED
nginx-6799fc88d8-abcde Running 1000 <none> false
api-server-7b58f5c9-x Error <none> <none> false
Le second pod est dans l’état Error et n’a pas de runAsUser défini. C’est un indice, mais pas encore un diagnostic.
Pour un pod défaillant spécifique, inspectez ses événements :
kubectl describe pod api-server-7b58f5c9-x
Recherchez des événements comme :
Warning Failed 5s (x2 over 10s) kubelet Error: container has runAsNonRoot and image will run as root
Cet événement vous indique exactement quel champ du contexte de sécurité est en conflit. Avant de modifier quoi que ce soit, capturez le manifeste actuel :
kubectl get pod api-server-7b58f5c9-x -o yaml > failing-pod-backup.yaml
Vous disposez maintenant d’une référence et d’une sauvegarde. L’étape suivante consiste à isoler le plus petit changement qui résout le problème sans affaiblir la sécurité.
Chemin de configuration sûr
Le chemin sûr pour modifier un contexte de sécurité consiste à tester les modifications d’abord dans un espace de noms hors production, ou sur un seul pod avec une étiquette canari. Ne modifiez jamais directement le modèle de pod d’un déploiement en cours sans test ; créez plutôt une copie du manifeste, modifiez-la et appliquez-la à un espace de noms de test.
Voici un pod minimal qui s’exécute en tant qu’utilisateur non root :
apiVersion: v1
kind: Pod
metadata:
name: security-test
namespace: test
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: app
image: nginx:1.25
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
Appliquez ceci à l’espace de noms de test, puis vérifiez que le pod fonctionne :
kubectl apply -f security-test.yaml
kubectl get pod security-test -n test -o wide
Si le pod s’exécute avec succès, les champs du contexte de sécurité sont acceptables pour le cluster. S’il échoue, décrivez-le pour voir l’événement exact :
kubectl describe pod security-test -n test
Les échecs courants incluent :
runAsNonRootest vrai mais l’utilisateur par défaut de l’image est root (UID 0). Le runtime de conteneur refuse de démarrer. Correctif : spécifiezrunAsUseravec un UID non nul qui existe dans l’image.readOnlyRootFilesystem: truefait échouer l’application lorsqu’elle tente d’écrire dans/tmpou/var/log. Correctif : montez un volumeemptyDirsur ces chemins, ou ajustez l’application pour écrire ailleurs.- Supprimer toutes les capacités avec
drop: ["ALL"]casse les applications qui ont besoin deNET_BIND_SERVICEpour se lier à des ports inférieurs à 1024. Correctif : rajoutez uniquement les capacités requises avecadd: ["NET_BIND_SERVICE"].
Essayez toujours le plus petit changement en premier. Par exemple, si le conteneur échoue à cause de readOnlyRootFilesystem, ne modifiez pas aussi runAsUser ; changez un seul champ, testez, puis continuez.
Vérification et diagnostics
Le diagnostic des problèmes de contexte de sécurité nécessite d’inspecter l’état d’exécution réel du conteneur, pas seulement la spécification du pod. Les commandes suivantes offrent une visibilité approfondie.
Vérifiez les paramètres de sécurité effectifs du pod en interrogeant le serveur d’API avec kubectl get pod -o yaml ; la section status peut inclure une entrée containerStatuses avec un lastState ou une reason.
Affichez les journaux du conteneur, y compris l’instance précédente qui a planté :
kubectl logs security-test -n test --previous
Si le conteneur n’a jamais démarré, les journaux peuvent être vides. Dans ce cas, utilisez kubectl describe pod pour lire les événements.
Inspectez directement le runtime de conteneur si vous avez accès au nœud. Pour containerd :
crictl ps -a | grep security-test
crictl inspect <container-id> | jq '.info.runtimeSpec.linux.securityContext'
La sortie affiche le contexte de sécurité Linux exact appliqué au conteneur, y compris runAsUser, capabilities et seccomp.
Vérifiez le profil seccomp appliqué au conteneur :
crictl inspect <container-id> | jq '.info.runtimeSpec.linux.seccomp'
Si le profil est localhost/default mais que le nœud ne possède pas ce fichier de profil, le conteneur échoue avec cannot load seccomp profile. Le correctif consiste à utiliser RuntimeDefault ou à fournir le profil.
Validez les profils AppArmor ou SELinux lorsqu’ils sont utilisés. Pour SELinux, vérifiez le seLinuxOptions du contexte de sécurité du pod et le mode d’application actuel du nœud :
getenforce
Si le nœud est en mode enforcing et que le type SELinux du conteneur n’est pas autorisé, le conteneur sera refusé. Vous pouvez temporairement passer en mode permissif pour les tests (avec prudence), puis affiner la politique.
Vérifiez les avertissements de Pod Security Admission. Si l’espace de noms applique une politique restreinte, un pod qui la viole peut être rejeté ou averti. Affichez les étiquettes de l’espace de noms :
kubectl get namespace test -o yaml | grep pod-security
Si pod-security.kubernetes.io/enforce=restricted est défini, alors runAsNonRoot: true est obligatoire et les capacités doivent être supprimées. Ajustez le contexte de sécurité du pod pour qu’il soit conforme.
Un tableau de diagnostic structuré peut aider à organiser les constatations. Voici un exemple de ce qu’il faut enregistrer :
| Vérification | Commande | Attendu | Observé | Étape suivante |
|---|---|---|---|---|
| Statut du pod | kubectl get pod | Running | Error | Décrire le pod, vérifier les événements |
| Événements | kubectl describe pod | Aucun avertissement | Conflit runAsNonRoot | Définir runAsUser sur une valeur non nulle |
| Journaux | kubectl logs --previous | Application démarrée | Permission refusée en écriture | Monter emptyDir ou ajuster les permissions |
| Seccomp | crictl inspect | RuntimeDefault | localhost/default fichier manquant | Changer seccompProfile en RuntimeDefault |
| SELinux | getenforce | Enforcing | Enforcing | Vérifier seLinuxOptions dans la spec du pod |
Remplissez les valeurs observées réelles à chaque fois ; le tableau ci-dessus est illustratif.
Modes de défaillance et récupération
Plusieurs modes de défaillance sont courants avec les contextes de sécurité. Les comprendre accélère la récupération.
Échec 1 : le conteneur se termine avec operation not permitted
Cause : le conteneur tente une opération privilégiée qui est bloquée par le contexte de sécurité, comme changer la propriété d’un fichier, se lier à un port bas ou utiliser un module du noyau.
Diagnostic : consultez les journaux du conteneur et le code de sortie. Par exemple :
Error: listen EACCES: permission denied 0.0.0.0:80
Cela indique que le conteneur n’a pas la capacité NET_BIND_SERVICE et tente de se lier au port 80.
Récupération : ajoutez explicitement la capacité dans le contexte de sécurité du conteneur :
securityContext:
capabilities:
add: ["NET_BIND_SERVICE"]
Vous pouvez également modifier l’application pour qu’elle se lie à un port supérieur à 1024, par exemple 8080. C’est plus sûr.
Échec 2 : pod bloqué dans CreateContainerConfigError
Cause : le contexte de sécurité référence un profil seccomp, un profil AppArmor ou une étiquette SELinux inexistant.
Diagnostic : kubectl describe pod affiche un événement comme :
Error: cannot load seccomp profile "/var/lib/kubelet/seccomp/custom.json": no such file or directory
Récupération : fournissez le fichier de profil sur le nœud, ou modifiez la spec du pod pour utiliser seccompProfile: type: RuntimeDefault.
Échec 3 : pod bloqué par Pod Security Admission
Cause : l’espace de noms applique un niveau de politique (baseline ou restricted) que le pod viole.
Diagnostic : kubectl describe pod peut afficher un événement d’avertissement, ou le pod n’est pas créé du tout. Vérifiez les étiquettes de l’espace de noms :
kubectl get namespace myapp -o jsonpath='{.metadata.labels}'
Si pod-security.kubernetes.io/enforce=restricted est défini, le pod doit avoir runAsNonRoot: true, supprimer toutes les capacités et définir seccompProfile sur RuntimeDefault.
Récupération : modifiez le pod pour qu’il respecte la politique, ou changez l’application de l’espace de noms à un niveau inférieur si l’activité l’exige (non recommandé en production).
Échec 4 : problèmes de permissions de volume avec fsGroup
Cause : le pod a fsGroup défini, mais le type de volume ne le prend pas en charge, ou l’ID de groupe n’existe pas dans /etc/group du conteneur.
Diagnostic : les journaux du pod affichent mkdir: cannot create directory '/data': Permission denied.
Récupération : assurez-vous que le volume est d’un type qui prend en charge fsGroup (par exemple, emptyDir, la plupart des volumes CSI). Si l’application crée des fichiers nécessitant un accès de groupe, définissez fsGroup sur un GID qui existe dans l’image ou utilisez un initContainer pour ajuster les permissions.
Chaque récupération doit être suivie d’une étape de vérification. Par exemple, après avoir ajouté NET_BIND_SERVICE, redémarrez le pod et vérifiez que l’application est accessible :
kubectl port-forward pod/myapp 8080:80
curl localhost:8080
Si vous devez revenir en arrière, appliquez le manifeste de sauvegarde :
kubectl apply -f failing-pod-backup.yaml
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle pour normaliser le dépannage des contextes de sécurité. Attribuez un responsable unique à chaque élément et révisez la liste chaque trimestre ou après toute mise à niveau du cluster.
- Avant tout changement : capturez le manifeste actuel du pod avec
kubectl get pod <nom> -o yaml > backup.yaml. Responsable : ingénieur d’astreinte. - Vérification de version : enregistrez la version du serveur Kubernetes avec
kubectl version --short. Assurez-vous que seccomp et les autres fonctionnalités sont prises en charge. Responsable : responsable de plateforme. - Diagnostics en lecture seule : exécutez
kubectl describe pod,kubectl logs --previousetkubectl get events. Documentez les résultats dans un ticket partagé. Responsable : ingénieur de dépannage. - Isolation du changement : modifiez un champ du contexte de sécurité à la fois dans un espace de noms de test. Responsable : développeur.
- Vérification : après avoir appliqué le correctif, vérifiez que le pod passe à
Runninget que l’application répond correctement. Utilisezkubectl port-forwardet curl. Responsable : ingénieur QA ou développeur. - Plan de retour en arrière : conservez le manifeste de sauvegarde et une commande de retour écrite dans le ticket. Responsable : ingénieur d’astreinte.
- Revue de politique : si le problème a été causé par Pod Security Admission, examinez la politique de l’espace de noms et décidez d’ajuster le pod ou la politique. Responsable : responsable sécurité et responsable de plateforme, revue mensuelle.
- Documentation : mettez à jour les runbooks ou la documentation de l’équipe avec le mode de défaillance et son correctif. Responsable : rédacteur technique ou responsable DevOps.
- Revue post-incident : pour les problèmes de gravité élevée, planifiez une post-mortem sans blâme dans les 5 jours ouvrables. Responsable : responsable d’ingénierie.
Pièges courants
Voici les pièges qui font souvent trébucher les opérateurs lorsqu’ils travaillent avec les contextes de sécurité, et comment les éviter.
Piège 1 : définir runAsNonRoot: true sans vérifier l’utilisateur par défaut de l’image
De nombreuses images s’exécutent en tant que root par défaut. Lorsque vous ajoutez runAsNonRoot: true, le conteneur échoue immédiatement avec Error: container has runAsNonRoot and image will run as root. Évitez cela en vérifiant d’abord l’utilisateur de l’image avec docker inspect ou en lisant sa documentation. Spécifiez toujours runAsUser avec un UID durci.
Piège 2 : supprimer toutes les capacités sans tester l’application
capabilities: drop: ["ALL"] est une bonne pratique de sécurité, mais de nombreuses applications ont besoin d’au moins une capacité. Par exemple, un serveur web qui se lie au port 80 a besoin de NET_BIND_SERVICE. Testez l’application avec toutes les capacités supprimées dans un environnement de préproduction, et ne rajoutez que les capacités exactes requises.
Piège 3 : utiliser readOnlyRootFilesystem: true sans fournir de volumes inscriptibles
Les applications ont souvent besoin d’écrire des fichiers temporaires, des journaux ou des caches. Avec un système de fichiers racine en lecture seule, elles échouent avec Permission denied. Planifiez à l’avance : montez des volumes emptyDir sur /tmp, /var/log et d’autres chemins inscriptibles. Utilisez un initContainer si l’application doit pré-peupler des répertoires.
Piège 4 : mal comprendre les types de profil seccomp
Définir seccompProfile: type: Localhost nécessite que le fichier de profil soit présent sur le nœud à un chemin spécifique sous /var/lib/kubelet/seccomp/. Si le fichier est manquant, le pod échoue avec CreateContainerConfigError. Utiliser RuntimeDefault est plus sûr et fonctionne partout. N’utilisez des profils personnalisés que lorsque vous contrôlez le système de fichiers du nœud et disposez d’un processus de déploiement.
Piège 5 : utiliser une image de conteneur qui n’inclut pas l’UID runAsUser spécifié
Si vous définissez runAsUser: 1000 mais que le /etc/passwd de l’image n’a pas d’UID 1000, le conteneur s’exécute quand même avec l’UID 1000 (le noyau n’a pas besoin que l’utilisateur existe), mais l’application peut rencontrer des problèmes de permission lorsqu’elle accède à des fichiers appartenant à d’autres utilisateurs. Assurez-vous que l’image est construite avec l’utilisateur attendu, ou utilisez fsGroup et runAsGroup pour aligner la propriété des fichiers.
Piège 6 : appliquer les contextes de sécurité uniquement au niveau du pod et oublier les remplacements au niveau du conteneur
Un securityContext au niveau du pod définit des valeurs par défaut pour tous les conteneurs, mais chaque conteneur peut le remplacer par son propre securityContext. Si un conteneur nécessite des paramètres différents, vérifiez que les champs au niveau du conteneur sont corrects. Une erreur courante consiste à définir runAsUser au niveau du pod, puis à le remplacer par inadvertance dans un conteneur.
Conclusion
Le dépannage des contextes de sécurité Kubernetes est un processus méthodique, pas un jeu de devinettes. En suivant la séquence de ce guide, vous pouvez résoudre la plupart des échecs rapidement sans affaiblir la posture de sécurité du cluster. Observez toujours avant de modifier, faites un changement ciblé à la fois, vérifiez le résultat et ayez un plan de retour en arrière.
La prochaine fois que vous serez confronté à une erreur CreateContainerConfigError ou operation not permitted, commencez par l’inventaire des versions et les diagnostics en lecture seule, puis appliquez le correctif minimal de la section des modes de défaillance. Conservez ce guide comme référence de terrain et mettez-le à jour au fur et à mesure que vos clusters évoluent.
Une exploitation fiable de Kubernetes exige que chaque décision de sécurité soit intentionnelle. Documentez vos contextes de sécurité, testez-les en préproduction et révisez-les régulièrement. Cette discipline transforme la sécurité d’une source d’incidents en une partie prévisible de votre pipeline de déploiement.