TP: Kubernetes Mise en application
Durée estimée : 5 à 7 heures Niveau : débutant à intermédiaire (notions de conteneurs et de Docker acquises) Environnement : cluster local kind (Kubernetes in Docker) sur Linux, macOS ou WSL2
Ce TP reprend l'ensemble des notions de base de Kubernetes dans l'ordre où on les utilise en pratique. Chaque partie contient des commandes à exécuter, le résultat attendu et quelques questions. Les réponses aux questions sont regroupées en annexe A.
Conseil : tapez les commandes plutôt que de les copier quand vous en avez le temps, et lisez systématiquement la sortie avant de passer à la suite.
Sommaire
- Mise en place de l'environnement
- Découverte du cluster
- Les Pods
- Labels et sélecteurs
- Deployments et ReplicaSets
- Services et DNS
- ConfigMaps et Secrets
- Probes et ressources
- Stockage persistant
- Jobs et CronJobs
- Helm et Ingress
- RBAC et ServiceAccounts
- Namespaces et quotas
- Atelier dépannage
- Mini-projet final
- Nettoyage
Annexes : A. Corrigé des questions · B. Solution du mini-projet · C. Aide-mémoire kubectl
Partie 0 : Mise en place de l'environnement
0.1 Prérequis
Docker doit être installé et fonctionnel :
docker version
docker run --rm hello-world
0.2 Installation des outils
kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -m 0755 kubectl /usr/local/bin/kubectl
rm kubectl
kubectl version --client
kind (vérifiez la dernière version sur https://github.com/kubernetes-sigs/kind/releases et ajustez la variable si besoin)
KIND_VERSION=v0.30.0
curl -Lo ./kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64"
sudo install -m 0755 kind /usr/local/bin/kind
rm kind
kind version
Helm
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
Sur macOS : brew install kubectl kind helm0.3 Création du cluster
Créez un dossier de travail et le fichier de configuration du cluster :
mkdir -p ~/tp-k8s && cd ~/tp-k8s
cat > kind-config.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: tp-k8s
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 30080
protocol: TCP
- role: worker
- role: worker
EOF
Ce cluster contient un nœud control-plane et deux nœuds workers. Le port 30080 de la machine hôte est redirigé vers le cluster, on s'en servira en partie 5.
kind create cluster --config kind-config.yaml
La création prend une à deux minutes.
0.4 Auto-complétion (recommandé)
# bash
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
source ~/.bashrc
# zsh
echo 'source <(kubectl completion zsh)' >> ~/.zshrc
echo 'alias k=kubectl' >> ~/.zshrc
source ~/.zshrc
Dans la suite du TP, on écrit kubectl en entier, mais vous pouvez utiliser k.
✅ Point de contrôle 0
kubectl get nodes
Résultat attendu : trois nœuds (tp-k8s-control-plane, tp-k8s-worker, tp-k8s-worker2) au statut Ready.
Partie 1 : Découverte du cluster
1.1 Informations générales
kubectl cluster-info
kubectl config get-contexts
kubectl config current-context
kubectl lit sa configuration dans ~/.kube/config. Un contexte associe un cluster, un utilisateur et un namespace par défaut.
1.2 Les nœuds
kubectl get nodes -o wide
kubectl describe node tp-k8s-worker
Dans la sortie de describe, repérez les sections Capacity, Allocatable, Conditions et Non-terminated Pods.
1.3 Les namespaces et les composants système
kubectl get namespaces
kubectl get pods -n kube-system -o wide
Vous devez voir notamment etcd, kube-apiserver, kube-controller-manager, kube-scheduler, kube-proxy et coredns.
1.4 Explorer l'API
kubectl api-resources | head -30
kubectl explain pod
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy --recursive
kubectl explain est la documentation intégrée : elle donne la structure exacte attendue dans les fichiers YAML.
1.5 Créer un namespace de travail
kubectl create namespace tp
kubectl config set-context --current --namespace=tp
kubectl config view --minify | grep namespace
Toutes les commandes suivantes s'exécutent dans le namespace tp sauf indication contraire.
❓ Questions partie 1
- Sur quel nœud tournent
etcdetkube-apiserver? Pourquoi ? - Combien de pods
kube-proxyy a-t-il, et pourquoi ce nombre ? - Quelle est la différence entre
kubectl getetkubectl describe?
Partie 2 : Les Pods
2.1 Créer un pod en mode impératif
kubectl run nginx --image=nginx:1.27
kubectl get pods
kubectl get pods -o wide
Attendez que le statut passe à Running. Vous pouvez suivre l'évolution en direct :
kubectl get pods -w
(Ctrl+C pour quitter.)
2.2 Inspecter le pod
kubectl describe pod nginx
kubectl logs nginx
kubectl get pod nginx -o yaml | less
Dans describe, lisez la section Events en bas : elle retrace la planification (Scheduled), le téléchargement de l'image (Pulling, Pulled) et le démarrage (Created, Started).
2.3 Entrer dans le conteneur
kubectl exec -it nginx -- bash
Dans le conteneur :
hostname
cat /etc/os-release
curl -s localhost | head -5 || echo "curl absent"
exit
Exécuter une commande unique sans shell interactif :
kubectl exec nginx -- nginx -v
2.4 Accéder au pod depuis votre poste
kubectl port-forward pod/nginx 8080:80
Dans un second terminal :
curl -s http://localhost:8080 | grep title
Arrêtez le port-forward avec Ctrl+C.
2.5 Générer un manifeste YAML
Plutôt que d'écrire le YAML de zéro, on le fait générer par kubectl :
kubectl run nginx-yaml --image=nginx:1.27 --port=80 --dry-run=client -o yaml > pod-nginx.yaml
cat pod-nginx.yaml
Nettoyez le fichier pour obtenir ceci :
apiVersion: v1
kind: Pod
metadata:
name: nginx-yaml
labels:
app: nginx-yaml
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
Appliquez-le :
kubectl apply -f pod-nginx.yaml
kubectl get pods --show-labels
2.6 Pod multi-conteneurs
Créez pod-multi.yaml :
apiVersion: v1
kind: Pod
metadata:
name: multi
spec:
volumes:
- name: partage
emptyDir: {}
containers:
- name: ecrivain
image: busybox:1.36
command: ["sh", "-c", "while true; do date >> /data/horodatage.txt; sleep 5; done"]
volumeMounts:
- name: partage
mountPath: /data
- name: lecteur
image: busybox:1.36
command: ["sh", "-c", "tail -f /data/horodatage.txt"]
volumeMounts:
- name: partage
mountPath: /data
kubectl apply -f pod-multi.yaml
kubectl get pod multi
kubectl logs multi -c lecteur -f
Les deux conteneurs partagent le volume emptyDir : le lecteur affiche ce qu'écrit l'écrivain.
2.7 Suppression
kubectl delete pod nginx nginx-yaml multi
kubectl get pods
❓ Questions partie 2
- Que signifie
READY 2/2sur le podmulti? - Que se passe-t-il si vous supprimez un pod créé avec
kubectl run? Est-il recréé ? - Les deux conteneurs du pod
multiont-ils la même adresse IP ?
Partie 3 : Labels et sélecteurs
3.1 Créer des pods étiquetés
kubectl run front-prod --image=nginx:1.27 --labels="app=front,env=prod"
kubectl run front-dev --image=nginx:1.27 --labels="app=front,env=dev"
kubectl run back-prod --image=nginx:1.27 --labels="app=back,env=prod"
kubectl get pods --show-labels
3.2 Filtrer avec des sélecteurs
kubectl get pods -l env=prod
kubectl get pods -l app=front,env=dev
kubectl get pods -l 'env in (prod,dev)'
kubectl get pods -l 'app!=back'
kubectl get pods -L app,env
3.3 Modifier les labels
kubectl label pod front-dev env=staging --overwrite
kubectl label pod back-prod tier=backend
kubectl label pod back-prod tier-
kubectl get pods --show-labels
Le tiret final (tier-) supprime le label.
3.4 Annotations
Les annotations stockent des métadonnées non utilisées pour la sélection :
kubectl annotate pod front-prod responsable="equipe-web"
kubectl describe pod front-prod | grep -A3 Annotations
3.5 Nettoyage
kubectl delete pods -l 'app in (front,back)'
❓ Questions partie 3
- Quelle est la différence d'usage entre un label et une annotation ?
- Quelle commande supprime tous les pods ayant
env=prod?
Partie 4 : Deployments et ReplicaSets
4.1 Créer un Deployment
kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl get deployments
kubectl get replicasets
kubectl get pods -o wide
Observez la hiérarchie des noms : web → web-<hash> (ReplicaSet) → web-<hash>-<id> (Pods). Notez aussi la répartition des pods sur les deux workers.
4.2 Auto-réparation
Supprimez un pod et observez :
kubectl delete pod $(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl get pods -l app=web
Un nouveau pod est créé immédiatement pour revenir à 3 réplicas.
4.3 Mise à l'échelle
kubectl scale deployment web --replicas=6
kubectl get pods -l app=web
kubectl scale deployment web --replicas=2
kubectl get pods -l app=web
4.4 Mise à jour progressive (rolling update)
Dans un second terminal, lancez une surveillance :
kubectl get pods -l app=web -w
Dans le premier terminal :
kubectl scale deployment web --replicas=4
kubectl set image deployment/web nginx=nginx:1.28
kubectl annotate deployment/web kubernetes.io/change-cause="passage en nginx 1.28"
kubectl rollout status deployment/web
Vérifiez la version :
kubectl get replicasets
kubectl describe deployment web | grep Image
Il existe maintenant deux ReplicaSets : l'ancien à 0 réplica, le nouveau à 4.
4.5 Historique et retour arrière
Provoquez une mise à jour ratée avec une image inexistante :
kubectl set image deployment/web nginx=nginx:9.99-inexistant
kubectl annotate deployment/web kubernetes.io/change-cause="version cassée" --overwrite
kubectl rollout status deployment/web --timeout=30s
kubectl get pods -l app=web
Certains pods sont en ErrImagePull ou ImagePullBackOff, mais les anciens pods continuent de tourner. Revenez en arrière :
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout status deployment/web
kubectl describe deployment web | grep Image
4.6 Passer au déclaratif
Exportez le Deployment dans un fichier propre :
kubectl create deployment web-decl --image=nginx:1.28 --replicas=2 --dry-run=client -o yaml > deploy-web.yaml
Éditez deploy-web.yaml pour qu'il ressemble à ceci :
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-decl
labels:
app: web-decl
spec:
replicas: 2
selector:
matchLabels:
app: web-decl
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web-decl
spec:
containers:
- name: nginx
image: nginx:1.28
ports:
- containerPort: 80
kubectl apply -f deploy-web.yaml
Modifiez replicas: 2 en replicas: 4 dans le fichier, puis :
kubectl diff -f deploy-web.yaml
kubectl apply -f deploy-web.yaml
kubectl get deployment web-decl
❓ Questions partie 4
- Pourquoi ne crée-t-on presque jamais de ReplicaSet directement ?
- Que garantissent
maxSurge: 1etmaxUnavailable: 0pendant une mise à jour ? - Que se passe-t-il si les labels de
selector.matchLabelsne correspondent pas à ceux dutemplate?
Partie 5 : Services et DNS
5.1 Une application qui affiche son nom
L'image traefik/whoami renvoie le nom du pod qui répond, ce qui permet de voir la répartition de charge.
kubectl create deployment whoami --image=traefik/whoami --replicas=3
kubectl rollout status deployment/whoami
5.2 Service ClusterIP
kubectl expose deployment whoami --port=80 --target-port=80
kubectl get service whoami
kubectl get endpointslices -l kubernetes.io/service-name=whoami
Les EndpointSlices listent les IP des pods derrière le Service. Comparez avec :
kubectl get pods -l app=whoami -o wide
5.3 Tester depuis l'intérieur du cluster
kubectl run test --rm -it --image=busybox:1.36 --restart=Never -- \
sh -c 'for i in 1 2 3 4 5 6 7 8; do wget -qO- http://whoami | grep Hostname; done'
Plusieurs noms de pods différents apparaissent : le Service répartit les requêtes.
5.4 Résolution DNS
kubectl run dns --rm -it --image=busybox:1.36 --restart=Never -- nslookup whoami.tp.svc.cluster.local
Formats de noms utilisables :
| Depuis | Nom |
|---|---|
| Le même namespace | whoami |
| Un autre namespace | whoami.tp |
| Nom complet | whoami.tp.svc.cluster.local |
5.5 Service NodePort
Créez svc-nodeport.yaml :
apiVersion: v1
kind: Service
metadata:
name: whoami-np
spec:
type: NodePort
selector:
app: whoami
ports:
- port: 80
targetPort: 80
nodePort: 30080
kubectl apply -f svc-nodeport.yaml
kubectl get service whoami-np
for i in 1 2 3 4 5; do curl -s http://localhost:30080 | grep Hostname; done
Ceci fonctionne grâce au extraPortMappings défini à la création du cluster.
5.6 Port-forward vers un Service
kubectl port-forward service/whoami 8081:80
Dans un autre terminal :
curl -s http://localhost:8081
5.7 Service headless (aperçu)
kubectl create service clusterip whoami-headless --clusterip=None --tcp=80:80
kubectl patch service whoami-headless -p '{"spec":{"selector":{"app":"whoami"}}}'
kubectl run dns2 --rm -it --image=busybox:1.36 --restart=Never -- nslookup whoami-headless
Le DNS renvoie directement les IP des pods, sans IP virtuelle. On s'en servira avec le StatefulSet du mini-projet.
❓ Questions partie 5
- Pourquoi ne faut-il pas contacter les pods directement par leur IP ?
- Dans quelle plage doit se trouver un
nodePortpar défaut ? - Que renvoie la résolution DNS d'un Service headless ?
Partie 6 : ConfigMaps et Secrets
6.1 ConfigMap en variables d'environnement
kubectl create configmap app-config \
--from-literal=APP_MODE=production \
--from-literal=APP_COULEUR=bleu
kubectl get configmap app-config -o yaml
Créez pod-env.yaml :
apiVersion: v1
kind: Pod
metadata:
name: env-demo
spec:
restartPolicy: Never
containers:
- name: demo
image: busybox:1.36
command: ["sh", "-c", "env | grep APP_; sleep 3600"]
envFrom:
- configMapRef:
name: app-config
kubectl apply -f pod-env.yaml
kubectl logs env-demo
6.2 ConfigMap montée en fichier
On remplace la page d'accueil de nginx par un fichier issu d'une ConfigMap.
cat > index.html <<'EOF'
<html><body><h1>Page servie depuis une ConfigMap</h1></body></html>
EOF
kubectl create configmap page-accueil --from-file=index.html
Créez deploy-page.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: site
spec:
replicas: 1
selector:
matchLabels:
app: site
template:
metadata:
labels:
app: site
spec:
containers:
- name: nginx
image: nginx:1.28
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: page-accueil
kubectl apply -f deploy-page.yaml
kubectl rollout status deployment/site
kubectl exec deploy/site -- cat /usr/share/nginx/html/index.html
Modifiez la ConfigMap :
kubectl create configmap page-accueil \
--from-literal=index.html='<html><body><h1>Version 2</h1></body></html>' \
--dry-run=client -o yaml | kubectl apply -f -
Attendez environ une minute puis relancez :
kubectl exec deploy/site -- cat /usr/share/nginx/html/index.html
Le fichier monté est mis à jour automatiquement (ce n'est pas le cas des variables d'environnement, qui nécessitent un redémarrage du pod).
6.3 Secrets
kubectl create secret generic db-credentials \
--from-literal=DB_USER=admin \
--from-literal=DB_PASSWORD='S3cr3t!'
kubectl get secret db-credentials -o yaml
Les valeurs sont encodées en base64, pas chiffrées :
kubectl get secret db-credentials -o jsonpath='{.data.DB_PASSWORD}' | base64 -d; echo
Utilisation dans un pod, pod-secret.yaml :
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
restartPolicy: Never
containers:
- name: demo
image: busybox:1.36
command: ["sh", "-c", "echo Utilisateur=$DB_USER; ls /secrets; sleep 3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_USER
volumeMounts:
- name: creds
mountPath: /secrets
readOnly: true
volumes:
- name: creds
secret:
secretName: db-credentials
kubectl apply -f pod-secret.yaml
kubectl logs secret-demo
kubectl exec secret-demo -- cat /secrets/DB_PASSWORD; echo
6.4 Nettoyage
kubectl delete pod env-demo secret-demo
❓ Questions partie 6
- Un Secret Kubernetes est-il chiffré par défaut ? Que faut-il mettre en place pour le protéger ?
- Pourquoi un changement de ConfigMap n'est-il pas visible dans une variable d'environnement déjà chargée ?
Partie 7 : Probes et ressources
7.1 Readiness probe
Créez deploy-probe.yaml avec une readiness probe volontairement fausse :
apiVersion: apps/v1
kind: Deployment
metadata:
name: probe-demo
spec:
replicas: 2
selector:
matchLabels:
app: probe-demo
template:
metadata:
labels:
app: probe-demo
spec:
containers:
- name: nginx
image: nginx:1.28
readinessProbe:
httpGet:
path: /inexistant
port: 80
initialDelaySeconds: 3
periodSeconds: 5
kubectl apply -f deploy-probe.yaml
kubectl expose deployment probe-demo --port=80
sleep 15
kubectl get pods -l app=probe-demo
kubectl get endpointslices -l kubernetes.io/service-name=probe-demo
kubectl describe pod -l app=probe-demo | grep -i readiness
Les pods sont Running mais READY 0/1, et le Service n'a aucun endpoint prêt. Corrigez path: /inexistant en path: /, puis :
kubectl apply -f deploy-probe.yaml
kubectl rollout status deployment/probe-demo
kubectl get endpointslices -l kubernetes.io/service-name=probe-demo
7.2 Liveness probe
Créez pod-liveness.yaml :
apiVersion: v1
kind: Pod
metadata:
name: liveness-demo
spec:
containers:
- name: demo
image: busybox:1.36
command: ["sh", "-c", "touch /tmp/sain; sleep 30; rm -f /tmp/sain; sleep 600"]
livenessProbe:
exec:
command: ["cat", "/tmp/sain"]
initialDelaySeconds: 5
periodSeconds: 5
kubectl apply -f pod-liveness.yaml
kubectl get pod liveness-demo -w
Au bout d'environ 45 secondes, la colonne RESTARTS augmente. Vérifiez :
kubectl describe pod liveness-demo | tail -15
7.3 Requests et limits
Créez pod-resources.yaml :
apiVersion: v1
kind: Pod
metadata:
name: resources-demo
spec:
containers:
- name: nginx
image: nginx:1.28
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "250m"
memory: "128Mi"
kubectl apply -f pod-resources.yaml
kubectl describe pod resources-demo | grep -A6 -E "Limits|Requests"
kubectl get pod resources-demo -o jsonpath='{.status.qosClass}'; echo
Maintenant une demande impossible à satisfaire :
kubectl run trop-gros --image=nginx:1.28 \
--overrides='{"spec":{"containers":[{"name":"trop-gros","image":"nginx:1.28","resources":{"requests":{"cpu":"64"}}}]}}'
kubectl get pod trop-gros
kubectl describe pod trop-gros | tail -5
Le pod reste Pending avec un message Insufficient cpu.
7.4 Dépassement mémoire
kubectl run oom --image=busybox:1.36 --restart=Never \
--overrides='{"spec":{"containers":[{"name":"oom","image":"busybox:1.36","command":["sh","-c","head -c 200m /dev/zero | tail"],"resources":{"limits":{"memory":"50Mi"}}}]}}'
sleep 10
kubectl get pod oom
kubectl get pod oom -o jsonpath='{.status.containerStatuses[0].state.terminated.reason}'; echo
Résultat attendu : OOMKilled.
7.5 Nettoyage
kubectl delete pod liveness-demo resources-demo trop-gros oom
❓ Questions partie 7
- Quelle est la différence de conséquence entre l'échec d'une readiness probe et l'échec d'une liveness probe ?
- Quelle ressource sert au scheduler pour placer un pod :
requestsoulimits? - Quelles sont les trois classes de QoS et à quoi correspond
resources-demo?
Partie 8 : Stockage persistant
8.1 StorageClass disponible
kubectl get storageclass
kubectl describe storageclass standard
kind fournit une StorageClass standard qui crée des volumes locaux sur le nœud. Notez le mode VolumeBindingMode: WaitForFirstConsumer.
8.2 Créer un PVC
Créez pvc.yaml :
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: donnees
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
kubectl apply -f pvc.yaml
kubectl get pvc
Le PVC est Pending : aucun pod ne l'utilise encore.
8.3 Utiliser le PVC
Créez pod-pvc.yaml :
apiVersion: v1
kind: Pod
metadata:
name: stockage
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: vol
mountPath: /data
volumes:
- name: vol
persistentVolumeClaim:
claimName: donnees
kubectl apply -f pod-pvc.yaml
kubectl wait --for=condition=Ready pod/stockage --timeout=60s
kubectl get pvc,pv
Le PVC est maintenant Bound et un PV a été créé dynamiquement.
8.4 Vérifier la persistance
kubectl exec stockage -- sh -c 'echo "donnee importante" > /data/fichier.txt'
kubectl delete pod stockage
kubectl apply -f pod-pvc.yaml
kubectl wait --for=condition=Ready pod/stockage --timeout=60s
kubectl exec stockage -- cat /data/fichier.txt
Le fichier a survécu à la suppression du pod.
8.5 Nettoyage
kubectl delete pod stockage
kubectl delete pvc donnees
kubectl get pv
❓ Questions partie 8
- Pourquoi le PVC restait-il
Pendingavant la création du pod ? - Que devient le PV après suppression du PVC, et de quel paramètre cela dépend-il ?
- Que signifie
ReadWriteOnce?
Partie 9 : Jobs et CronJobs
9.1 Job simple
kubectl create job bonjour --image=busybox:1.36 -- \
sh -c 'for i in 1 2 3; do echo "etape $i"; sleep 2; done; echo termine'
kubectl get jobs -w
Une fois COMPLETIONS 1/1 :
kubectl logs job/bonjour
kubectl get pods -l job-name=bonjour
9.2 Job parallèle
Créez job-parallele.yaml :
apiVersion: batch/v1
kind: Job
metadata:
name: calcul
spec:
completions: 6
parallelism: 2
backoffLimit: 2
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: Never
containers:
- name: calcul
image: busybox:1.36
command: ["sh", "-c", "echo traitement sur $(hostname); sleep 5"]
kubectl apply -f job-parallele.yaml
kubectl get pods -l job-name=calcul -w
Les pods s'exécutent deux par deux jusqu'à six complétions.
9.3 Job en échec
kubectl create job echec --image=busybox:1.36 -- sh -c 'echo erreur; exit 1'
sleep 60
kubectl get job echec
kubectl describe job echec | tail -8
Le Job réessaie jusqu'à backoffLimit (6 par défaut) avec un délai croissant.
9.4 CronJob
kubectl create cronjob horloge --image=busybox:1.36 --schedule="*/1 * * * *" -- date
kubectl get cronjob
Attendez deux ou trois minutes :
kubectl get jobs
kubectl logs job/$(kubectl get jobs -o name | grep horloge | tail -1 | cut -d/ -f2)
Suspendre puis supprimer le CronJob :
kubectl patch cronjob horloge -p '{"spec":{"suspend":true}}'
kubectl delete cronjob horloge
kubectl delete job bonjour echec calcul --ignore-not-found
❓ Questions partie 9
- Pourquoi
restartPolicy: Alwaysest-il interdit dans un Job ? - À quoi sert
ttlSecondsAfterFinished?
Partie 10 : Helm et Ingress
10.1 Installer Traefik avec Helm
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik --namespace traefik --create-namespace
kubectl -n traefik rollout status deployment/traefik
kubectl -n traefik get pods,svc
Le Service traefik est de type LoadBalancer. Sur kind, son EXTERNAL-IP reste <pending> car il n'y a pas de fournisseur de load balancer : on passera par un port-forward.
10.2 Commandes Helm utiles
helm list -A
helm status traefik -n traefik
helm get values traefik -n traefik
helm show values traefik/traefik | less
kubectl get ingressclass
10.3 Créer un Ingress
On réutilise le Service whoami de la partie 5. Créez ingress-whoami.yaml :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami
spec:
ingressClassName: traefik
rules:
- host: whoami.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
kubectl apply -f ingress-whoami.yaml
kubectl get ingress
10.4 Tester
Dans un terminal dédié :
kubectl -n traefik port-forward service/traefik 8080:80
Dans un autre terminal :
curl -s -H "Host: whoami.local" http://localhost:8080 | grep Hostname
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: autre.local" http://localhost:8080
La première requête est routée vers whoami. La seconde renvoie 404 car aucun Ingress ne correspond à cet hôte.
10.5 Routage par chemin
Ajoutez une seconde règle vers le Service site de la partie 6. Il faut d'abord l'exposer :
kubectl expose deployment site --port=80
Créez ingress-chemins.yaml :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: chemins
spec:
ingressClassName: traefik
rules:
- host: tp.local
http:
paths:
- path: /whoami
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: site
port:
number: 80
kubectl apply -f ingress-chemins.yaml
curl -s -H "Host: tp.local" http://localhost:8080/
curl -s -H "Host: tp.local" http://localhost:8080/whoami | grep Hostname
❓ Questions partie 10
- Quelle est la différence entre un Ingress et un Ingress Controller ?
- Que se passerait-il si aucun Ingress Controller n'était installé ?
Partie 11 : RBAC et ServiceAccounts
11.1 Créer un compte de service en lecture seule
kubectl create serviceaccount lecteur
kubectl create role lecture-pods --verb=get,list,watch --resource=pods,pods/log
kubectl create rolebinding lecteur-pods --role=lecture-pods --serviceaccount=tp:lecteur
11.2 Vérifier les droits
kubectl auth can-i list pods --as=system:serviceaccount:tp:lecteur
kubectl auth can-i delete pods --as=system:serviceaccount:tp:lecteur
kubectl auth can-i list secrets --as=system:serviceaccount:tp:lecteur
kubectl auth can-i list pods -n kube-system --as=system:serviceaccount:tp:lecteur
Résultats attendus : yes, no, no, no.
11.3 Utiliser le compte depuis un pod
Créez pod-rbac.yaml :
apiVersion: v1
kind: Pod
metadata:
name: kubectl-pod
spec:
serviceAccountName: lecteur
containers:
- name: kubectl
image: bitnami/kubectl:latest
command: ["sleep", "3600"]
kubectl apply -f pod-rbac.yaml
kubectl wait --for=condition=Ready pod/kubectl-pod --timeout=90s
kubectl exec kubectl-pod -- kubectl get pods
kubectl exec kubectl-pod -- kubectl delete pod site-xxx 2>&1 | tail -1
kubectl exec kubectl-pod -- kubectl get secrets 2>&1 | tail -1
Les deux dernières commandes renvoient Forbidden.
Si l'imagebitnami/kubectln'est pas disponible dans votre environnement, remplacez-la parregistry.k8s.io/kubectl:v1.34.0(ou la version correspondant à votre cluster, visible aveckubectl version).
11.4 Étendre les droits
Ajoutez la lecture des Deployments au rôle :
kubectl get role lecture-pods -o yaml > role-lecture.yaml
Éditez role-lecture.yaml pour ajouter une règle :
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
kubectl apply -f role-lecture.yaml
kubectl exec kubectl-pod -- kubectl get deployments
kubectl delete pod kubectl-pod
❓ Questions partie 11
- Quelle est la différence entre un Role et un ClusterRole ?
- Pourquoi la règle
deploymentsest-elle dans l'apiGroupappset non""?
Partie 12 : Namespaces et quotas
12.1 Créer un namespace limité
kubectl create namespace equipe-a
Créez quota.yaml :
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-equipe-a
namespace: equipe-a
spec:
hard:
pods: "3"
requests.cpu: "500m"
requests.memory: "512Mi"
limits.cpu: "1"
limits.memory: "1Gi"
---
apiVersion: v1
kind: LimitRange
metadata:
name: defauts
namespace: equipe-a
spec:
limits:
- type: Container
default:
cpu: "200m"
memory: "128Mi"
defaultRequest:
cpu: "100m"
memory: "64Mi"
kubectl apply -f quota.yaml
kubectl describe namespace equipe-a
12.2 Tester le quota
kubectl -n equipe-a create deployment quota-test --image=nginx:1.28 --replicas=5
sleep 10
kubectl -n equipe-a get pods
kubectl -n equipe-a describe resourcequota quota-equipe-a
kubectl -n equipe-a get events --sort-by=.lastTimestamp | tail -5
Seuls 3 pods sont créés. Les événements du ReplicaSet mentionnent exceeded quota.
Vérifiez que la LimitRange a injecté des valeurs par défaut :
kubectl -n equipe-a get pod -l app=quota-test -o jsonpath='{.items[0].spec.containers[0].resources}'; echo
12.3 Nettoyage
kubectl delete namespace equipe-a
Supprimer un namespace supprime tout ce qu'il contient.
❓ Questions partie 12
- Pourquoi une LimitRange est-elle presque indispensable quand un ResourceQuota porte sur le CPU et la mémoire ?
- Les nœuds et les PersistentVolumes appartiennent-ils à un namespace ?
Partie 13 : Atelier dépannage
Cette partie ne donne pas les solutions directement. Déployez les ressources cassées, trouvez la cause de chaque problème et corrigez-la.
Créez casse.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: casse-1
spec:
replicas: 1
selector:
matchLabels:
app: casse-1
template:
metadata:
labels:
app: casse-1
spec:
containers:
- name: web
image: nginx:1.99-nexistepas
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: casse-2
spec:
replicas: 1
selector:
matchLabels:
app: casse-2
template:
metadata:
labels:
app: casse-2
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo demarrage && cat /config/app.conf && sleep 3600"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: casse-3
spec:
replicas: 2
selector:
matchLabels:
app: casse-3
template:
metadata:
labels:
app: casse-3
spec:
containers:
- name: web
image: traefik/whoami
---
apiVersion: v1
kind: Service
metadata:
name: casse-3
spec:
selector:
app: casse3
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: casse-4
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo $MOT_DE_PASSE; sleep 3600"]
env:
- name: MOT_DE_PASSE
valueFrom:
secretKeyRef:
name: secret-absent
key: password
kubectl apply -f casse.yaml
sleep 30
kubectl get pods
Méthode de diagnostic
Pour chaque ressource, suivez cet ordre :
kubectl get pods
kubectl describe pod <nom>
kubectl logs <nom>
kubectl logs <nom> --previous
kubectl get events --sort-by=.lastTimestamp
kubectl get endpointslices -l kubernetes.io/service-name=<service>
Travail demandé
| Ressource | Symptôme à identifier | Cause | Correction |
|---|---|---|---|
| casse-1 | |||
| casse-2 | |||
| casse-3 | |||
| casse-4 |
Pour tester casse-3 une fois corrigé :
kubectl run t --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://casse-3
Les corrections sont en annexe A (questions 31 à 34). Nettoyez ensuite :
kubectl delete -f casse.yaml --ignore-not-found
Partie 14 : Mini-projet final
Objectif
Déployer dans un namespace projet une base PostgreSQL et l'interface web Adminer, accessible via l'Ingress sur http://adminer.local:8080.
Cahier des charges
- Un namespace
projet. - Un Secret
postgres-secretcontenantPOSTGRES_USER=tp,POSTGRES_PASSWORD(valeur de votre choix) etPOSTGRES_DB=tpdb. - Un StatefulSet
postgres:- image
postgres:16 - 1 réplica
- variables d'environnement issues du Secret
- variable
PGDATA=/var/lib/postgresql/data/pgdata - un volume persistant de 1 Gi via
volumeClaimTemplates, monté sur/var/lib/postgresql/data - une readiness probe de type
execavec la commandepg_isready -U tp -d tpdb - requests : 100m CPU, 256Mi mémoire ; limits : 500m CPU, 512Mi mémoire
- image
- Un Service headless
postgressur le port 5432. - Un Deployment
adminer:- image
adminer:latest - 2 réplicas
- variable
ADMINER_DEFAULT_SERVER=postgres - readiness probe HTTP sur le port 8080
- image
- Un Service ClusterIP
adminer(port 80 vers 8080). - Un Ingress
adminerpour l'hôteadminer.local, classetraefik.
Étapes suggérées
- Écrire chaque manifeste dans un dossier
projet/. - Appliquer le tout avec
kubectl apply -f projet/. - Vérifier chaque ressource avec
kubectl -n projet get all,pvc,ingress. - Ouvrir
http://adminer.local:8080dans un navigateur et se connecter (système PostgreSQL, serveurpostgres, utilisateurtp, basetpdb).
Lancer le port-forward Traefik si ce n'est plus le cas :
kubectl -n traefik port-forward service/traefik 8080:80
Ajouter l'hôte dans /etc/hosts :
echo "127.0.0.1 adminer.local" | sudo tee -a /etc/hosts
Validation
- [ ] Créer une table et insérer une ligne depuis Adminer.
- [ ] Supprimer le pod
postgres-0et vérifier que la table existe toujours après son redémarrage. - [ ] Passer Adminer à 3 réplicas sans interruption de service.
[ ] Se connecter en ligne de commande :
kubectl -n projet exec -it postgres-0 -- psql -U tp -d tpdb -c '\dt'
Bonus
- Ajouter un CronJob qui exécute
pg_dumptoutes les 5 minutes vers un PVC dédié. - Créer un ServiceAccount et un Role qui n'autorisent que la lecture des pods du namespace
projet. - Ajouter un ResourceQuota au namespace.
La solution complète est en annexe B.
Partie 15 : Nettoyage
kubectl config set-context --current --namespace=default
kind delete cluster --name tp-k8s
sudo sed -i '/adminer.local/d' /etc/hosts
Annexe A : Corrigé des questions
1. Sur tp-k8s-control-plane. Ce sont des composants du plan de contrôle, lancés comme pods statiques par le kubelet du nœud control-plane à partir des manifestes de /etc/kubernetes/manifests.
2. Trois, un par nœud. kube-proxy est déployé par un DaemonSet, qui place un pod sur chaque nœud.
3. get affiche une liste synthétique (ou l'objet complet avec -o yaml). describe produit une vue lisible et détaillée, incluant les événements liés à l'objet.
4. Deux conteneurs sur deux sont prêts dans le pod.
5. Non. Un pod nu n'est géré par aucun contrôleur : une fois supprimé, il disparaît.
6. Oui. Les conteneurs d'un même pod partagent le même espace réseau, donc la même IP. Ils communiquent entre eux via localhost.
7. Les labels servent à sélectionner et regrouper des objets (Services, Deployments, requêtes -l). Les annotations stockent des informations descriptives ou destinées à des outils, sans rôle dans la sélection.
8. kubectl delete pods -l env=prod
9. Le Deployment gère les ReplicaSets à notre place et ajoute les mises à jour progressives, l'historique et le retour arrière.
10. Au plus un pod supplémentaire est créé pendant la mise à jour, et aucun pod n'est retiré avant qu'un nouveau soit prêt. La capacité ne descend jamais sous le nombre de réplicas demandé.
11. L'API refuse le Deployment avec une erreur de validation (selector does not match template labels).
12. Les pods sont éphémères : leur IP change à chaque recréation. Le Service fournit une IP et un nom DNS stables, et répartit la charge.
13. 30000 à 32767.
14. Directement les IP des pods sélectionnés (un enregistrement A par pod prêt), sans IP de Service.
15. Non, il est seulement encodé en base64 et stocké tel quel dans etcd. Il faut activer le chiffrement au repos (EncryptionConfiguration), restreindre l'accès par RBAC, et idéalement utiliser un gestionnaire externe (Vault, External Secrets, Sealed Secrets).
16. Les variables d'environnement sont injectées au démarrage du processus. Il faut redémarrer les pods (kubectl rollout restart deployment/<nom>).
17. Readiness : le pod est retiré des endpoints du Service, il ne reçoit plus de trafic, mais n'est pas redémarré. Liveness : le kubelet redémarre le conteneur.
18. requests. Les limits sont appliquées à l'exécution par le kubelet et le runtime.
19. Guaranteed (requests égales aux limits pour tous les conteneurs), Burstable (au moins une request ou limit définie sans être Guaranteed), BestEffort (aucune). resources-demo est Burstable.
20. La StorageClass utilise WaitForFirstConsumer : le volume n'est créé qu'une fois le pod planifié, pour être placé sur le bon nœud.
21. Il dépend de la reclaimPolicy de la StorageClass (ou du PV). Avec Delete (cas de kind), le PV et les données sont supprimés. Avec Retain, le PV est conservé en état Released.
22. Le volume peut être monté en lecture-écriture par un seul nœud à la fois.
23. Un Job doit pouvoir se terminer. Avec Always, le conteneur serait relancé indéfiniment. Seuls Never et OnFailure sont acceptés.
24. À supprimer automatiquement le Job et ses pods un certain temps après sa fin.
25. L'Ingress est une ressource déclarative décrivant des règles de routage HTTP. L'Ingress Controller (Traefik ici) est le logiciel qui lit ces règles et route réellement le trafic.
26. L'objet Ingress serait créé mais n'aurait aucun effet : personne ne l'appliquerait.
27. Un Role s'applique à un namespace. Un ClusterRole s'applique à tout le cluster ou à des ressources non namespacées (nœuds, PV), et peut aussi être lié dans un seul namespace via un RoleBinding.
28. Les Deployments font partie du groupe d'API apps (apps/v1). Le groupe "" correspond au groupe core (v1) : pods, services, configmaps, secrets.
29. Quand un quota porte sur requests ou limits, tout pod qui ne les déclare pas est refusé. La LimitRange injecte des valeurs par défaut et évite ces refus.
30. Non, ce sont des ressources à portée cluster. On peut le vérifier avec kubectl api-resources --namespaced=false.
31. casse-1. Symptôme : ErrImagePull / ImagePullBackOff. Cause : le tag 1.99-nexistepas n'existe pas. Correction :
kubectl set image deployment/casse-1 web=nginx:1.28
32. casse-2. Symptôme : CrashLoopBackOff, les logs montrent cat: can't open '/config/app.conf'. Cause : le fichier /config/app.conf n'existe pas, cat échoue, le && empêche le sleep de s'exécuter et le conteneur se termine en erreur. Correction : fournir le fichier via une ConfigMap.
kubectl create configmap casse-2-config --from-literal=app.conf='parametre=valeur'
kubectl patch deployment casse-2 --type=json -p='[
{"op":"add","path":"/spec/template/spec/volumes","value":[{"name":"config","configMap":{"name":"casse-2-config"}}]},
{"op":"add","path":"/spec/template/spec/containers/0/volumeMounts","value":[{"name":"config","mountPath":"/config"}]}
]'
Variante à proposer : remplacer les&&par des;. Lesleeps'exécute alors malgré l'échec decatet le pod resteRunningalors que l'application est mal configurée. Cela montre qu'un statutRunningne suffit pas à valider un déploiement.
33. casse-3. Symptôme : les pods tournent mais le Service ne répond pas, et kubectl get endpointslices -l kubernetes.io/service-name=casse-3 ne montre aucune adresse. Cause : le sélecteur du Service (app: casse3) ne correspond pas au label des pods (app: casse-3). Correction :
kubectl patch service casse-3 -p '{"spec":{"selector":{"app":"casse-3"}}}'
34. casse-4. Symptôme : CreateContainerConfigError, describe indique secret "secret-absent" not found. Correction :
kubectl create secret generic secret-absent --from-literal=password=demo
kubectl get pod casse-4 -w
Le pod démarre automatiquement une fois le Secret présent.
Annexe B : Solution du mini-projet
Fichier projet/00-namespace.yaml :
apiVersion: v1
kind: Namespace
metadata:
name: projet
Fichier projet/10-secret.yaml :
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: projet
type: Opaque
stringData:
POSTGRES_USER: tp
POSTGRES_PASSWORD: ChangezMoi123
POSTGRES_DB: tpdb
Fichier projet/20-postgres.yaml :
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: projet
spec:
clusterIP: None
selector:
app: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: projet
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: postgres-secret
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
readinessProbe:
exec:
command: ["pg_isready", "-U", "tp", "-d", "tpdb"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
Fichier projet/30-adminer.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: adminer
namespace: projet
spec:
replicas: 2
selector:
matchLabels:
app: adminer
template:
metadata:
labels:
app: adminer
spec:
containers:
- name: adminer
image: adminer:latest
ports:
- containerPort: 8080
env:
- name: ADMINER_DEFAULT_SERVER
value: postgres
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "250m"
memory: "128Mi"
---
apiVersion: v1
kind: Service
metadata:
name: adminer
namespace: projet
spec:
selector:
app: adminer
ports:
- port: 80
targetPort: 8080
Fichier projet/40-ingress.yaml :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: adminer
namespace: projet
spec:
ingressClassName: traefik
rules:
- host: adminer.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: adminer
port:
number: 80
Déploiement et vérifications :
kubectl apply -f projet/00-namespace.yaml
kubectl apply -f projet/
kubectl -n projet rollout status statefulset/postgres
kubectl -n projet rollout status deployment/adminer
kubectl -n projet get all,pvc,ingress
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: adminer.local" http://localhost:8080
Test de persistance :
kubectl -n projet exec -it postgres-0 -- psql -U tp -d tpdb -c "CREATE TABLE IF NOT EXISTS test (id int); INSERT INTO test VALUES (1);"
kubectl -n projet delete pod postgres-0
kubectl -n projet wait --for=condition=Ready pod/postgres-0 --timeout=120s
kubectl -n projet exec -it postgres-0 -- psql -U tp -d tpdb -c "SELECT * FROM test;"
Mise à l'échelle d'Adminer :
kubectl -n projet scale deployment adminer --replicas=3
kubectl -n projet get pods -l app=adminer
Bonus sauvegarde, fichier projet/50-backup.yaml :
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backups
namespace: projet
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: pg-backup
namespace: projet
spec:
schedule: "*/5 * * * *"
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: postgres:16
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_PASSWORD
command:
- sh
- -c
- pg_dump -h postgres -U tp tpdb > /backups/tpdb-$(date +%Y%m%d-%H%M%S).sql && ls -l /backups
volumeMounts:
- name: backups
mountPath: /backups
volumes:
- name: backups
persistentVolumeClaim:
claimName: backups
kubectl apply -f projet/50-backup.yaml
kubectl -n projet create job backup-manuel --from=cronjob/pg-backup
kubectl -n projet logs job/backup-manuel -f
Annexe C : Aide-mémoire kubectl
Consultation
kubectl get <ressource> # lister
kubectl get <ressource> -o wide # plus de colonnes
kubectl get <ressource> -o yaml # objet complet
kubectl get all -n <ns> # principales ressources d'un namespace
kubectl get pods -A # tous les namespaces
kubectl get pods -l app=web # filtre par label
kubectl get pods -w # suivi en direct
kubectl describe <ressource> <nom> # détail + événements
kubectl get events --sort-by=.lastTimestamp # événements récents
kubectl explain <ressource>.<champ> # documentation intégrée
kubectl api-resources # types disponibles
Création et modification
kubectl apply -f <fichier|dossier> # créer ou mettre à jour
kubectl diff -f <fichier> # prévisualiser un changement
kubectl delete -f <fichier> # supprimer
kubectl create deployment <nom> --image=<img> --dry-run=client -o yaml
kubectl run <nom> --image=<img> --dry-run=client -o yaml
kubectl expose deployment <nom> --port=80 --dry-run=client -o yaml
kubectl edit <ressource> <nom> # édition directe
kubectl patch <ressource> <nom> -p '<json>'
kubectl label <ressource> <nom> cle=valeur
kubectl annotate <ressource> <nom> cle=valeur
Deployments
kubectl scale deployment <nom> --replicas=N
kubectl set image deployment/<nom> <conteneur>=<image>
kubectl rollout status deployment/<nom>
kubectl rollout history deployment/<nom>
kubectl rollout undo deployment/<nom>
kubectl rollout restart deployment/<nom>
Débogage
kubectl logs <pod> [-c <conteneur>] [-f] [--previous]
kubectl logs deploy/<nom>
kubectl exec -it <pod> -- sh
kubectl port-forward pod/<pod> 8080:80
kubectl port-forward service/<svc> 8080:80
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- sh
kubectl get endpointslices -l kubernetes.io/service-name=<svc>
kubectl auth can-i <verbe> <ressource> --as=<utilisateur>
kubectl top pods # nécessite metrics-server
Contexte et namespace
kubectl config get-contexts
kubectl config use-context <contexte>
kubectl config set-context --current --namespace=<ns>
Statuts de pod fréquents
| Statut | Signification habituelle | Premier réflexe |
|---|---|---|
Pending |
Pas de nœud disponible ou PVC non lié | describe pod, section Events |
ContainerCreating |
Téléchargement d'image ou montage de volume en cours | Attendre, puis describe pod |
ErrImagePull / ImagePullBackOff |
Image ou tag introuvable, registre privé sans identifiants | Vérifier le nom de l'image |
CrashLoopBackOff |
Le conteneur démarre puis s'arrête en boucle | logs --previous |
CreateContainerConfigError |
ConfigMap ou Secret référencé absent | describe pod |
OOMKilled |
Limite mémoire dépassée | Augmenter la limite ou corriger l'application |
Running mais 0/1 |
Readiness probe en échec | describe pod, vérifier la probe |
Completed |
Processus terminé avec le code 0 (normal pour un Job) | Aucun |