Kubernetes prend en charge plusieurs clusters virtuels présents sur le même cluster physique. Ces clusters virtuels sont appelés namespaces.
Les objets Kubernetes comme les pods et les containers sont dans un namespace. Un namespace est un moyen de séparer et organiser les objets dans un cluster.
Afin d'afficher tous les namespaces existants, on effectue la commande suivante :
$ kubectl get namespace
NAME STATUS AGE
default Active 42h
kube-node-lease Active 42h
kube-public Active 42h
kube-system Active 42h
De base on a toujours un namespace default. Kubeadm lui, créer les namespaces kube-*
Lors de l'utilisation de kubectl on peut spécifier un namespace via le paramètre --namespace.
Si aucun namespace n'est spécifié, le namespace par default sera sélectionné.
$ kubectl get pods --namespace my-namespace
# Soit :
$ kubectl get pods --namespace kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-78d6f96c7b-tqk4m 1/1 Running 1 42h
calico-node-5hzhz 1/1 Running 1 42h
calico-node-7k2jb 1/1 Running 1 42h
calico-node-wdmxr 1/1 Running 1 42h
coredns-558bd4d5db-2t4d9 1/1 Running 1 42h
coredns-558bd4d5db-2x58z 1/1 Running 1 42h
etcd-k8s-control 1/1 Running 1 43h
kube-apiserver-k8s-control 1/1 Running 1 43h
kube-controller-manager-k8s-control 1/1 Running 1 43h
kube-proxy-98vh2 1/1 Running 1 42h
kube-proxy-dr9jb 1/1 Running 1 42h
kube-proxy-jsmr9 1/1 Running 1 42h
kube-scheduler-k8s-control 1/1 Running 1 43h
On peut spécifier tous les namespaces :
$ kubectl get pods --all-namespaces
On peut évidemment créer un namespace :
$ kubectl create namespace my-namespace
namespace/my-namespace created
K8S permet de rendre les applications hautement disponible. Mais il est aussi conçu pour être lui même hautement disponible. Pour cela il faut ajouter des "control plane". Il faut un nombre impair de noeud afin de faire un cluster hautement disponible.
On aura donc une architecture du style :
Il existe 2 manières d'inclure etcd (permet le stockage des clés/valeurs pour tout le cluster) dans un cluster :
Architecture : etcd est déployé sur les mêmes nœuds que les autres composants du control plane (API server, scheduler, controller-manager).
Avantages :
Inconvénients :
Configuration typique :
# Configuration kubeadm pour stacked etcd (première control plane)
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.32.3
controlPlaneEndpoint: "loadbalancer.example.com:6443"
etcd:
local:
dataDir: /var/lib/etcd
Architecture : etcd est déployé sur des machines dédiées, séparées des nœuds control plane.
Avantages :
Inconvénients :
Configuration typique :
# Configuration kubeadm pour external etcd
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.32.3
controlPlaneEndpoint: "loadbalancer.example.com:6443"
etcd:
external:
endpoints:
- https://etcd1.example.com:2379
- https://etcd2.example.com:2379
- https://etcd3.example.com:2379
caFile: /etc/kubernetes/pki/etcd/ca.crt
certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
Rôle : Distribuer les requêtes API entre les multiples control plane nodes.
Configurations possibles :
Exemple de configuration HAProxy :
frontend kubernetes-frontend
bind *:6443
mode tcp
option tcplog
default_backend kubernetes-backend
backend kubernetes-backend
mode tcp
option tcp-check
balance roundrobin
server master1 192.168.1.10:6443 check fall 3 rise 2
server master2 192.168.1.11:6443 check fall 3 rise 2
server master3 192.168.1.12:6443 check fall 3 rise 2
Quorum etcd :
quorum = (n/2) + 1 (si ,5 prendre valeur inférieur)Important : Toujours utiliser un nombre impair de membres etcd (3, 5, 7). Passer de 3 à 4 n'augmente pas la tolérance aux pannes mais augmente la latence.
Leader Election des composants :
# Vérifier le leader du controller-manager
kubectl get lease -n kube-system kube-controller-manager -o yaml
# Vérifier le leader du scheduler
kubectl get lease -n kube-system kube-scheduler -o yaml
Voici une animation très intéressante du fonctionnement de RAFT Protocol : https://thesecretlivesofdata.com/raft/
Il existe différents outils afin de gérer kubernetes, il est intéressant de les connaitre :
| Outil | Utilité |
|---|---|
| kubectl | Outil officiel de kubernetes permettant d'exécuter des commandes Kubernetes. |
| kubeadm | Outil permettant la mise en place facile et rapide d'un cluster Kubernetes |
| minikube | Permet de créer un cluster Kubernetes sur une seule machine. Pratique pour développer. |
| helm | Fournit un système de templating et de gestion des paquets (package manager) pour les objets k8s. Grâce à helm on peut créer nos propres templates (charte) ou les télécharger (wordpress chart par exemple). |
| kompose | Permet de transformer un fichier docker compose en objets kubernetes. |
| Kustomize | Gestion de la configuration des objets kubernetes (comme helm). Permet de partager et ré-utiliser des configurations Kubernetes via un template |
Les Admission Controllers sont des plugins qui interceptent les requêtes à l'API Kubernetes après l'authentification et l'autorisation, mais avant la persistance de l'objet dans etcd. Ils permettent de valider, modifier ou rejeter les requêtes selon des règles définies.
Les admission controllers sont activés via le flag --enable-admission-plugins du kube-apiserver.
# Voir les admission controllers activés
kubectl exec -n kube-system kube-apiserver-<node> -- kube-apiserver -h | grep enable-admission-plugins
# Configuration dans le manifeste API Server (/etc/kubernetes/manifests/kube-apiserver.yaml)
spec:
containers:
- command:
- kube-apiserver
- --enable-admission-plugins=NodeRestriction,PodSecurity,LimitRanger,ResourceQuota
Voici quelques exemples d'admission controllers. La plupart sont activés par défaut et n'ont pas besoin d'être ajouté au flag --enable-admission-plugins.
Rôle : Applique les contraintes définies dans les objets LimitRange (limites par défaut, min, max pour CPU/mémoire).
Activé par défaut : Oui
Exemple :
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
namespace: default
spec:
limits:
- default:
memory: 512Mi
cpu: 500m
defaultRequest:
memory: 256Mi
cpu: 250m
max:
memory: 1Gi
cpu: 1
type: Container
Rôle : Applique les quotas de ressources définis dans les objets ResourceQuota.
Activé par défaut : Oui
Exemple :
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: dev
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "20"
Rôle : Applique les Pod Security Standards (privileged, baseline, restricted) au niveau namespace.
Activé par défaut : Oui (depuis Kubernetes 1.25)
Exemple :
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Niveaux de sécurité :
allowPrivilegeEscalation: false, drop de toutes les capabilities (sauf NET_BIND_SERVICE), seccomp RuntimeDefault, etc. C'est la cible recommandée pour du workload applicatif en production.enforce vs audit vs warn :
enforce: restricted : bloque la création de tout pod qui ne respecte pas le standard restricted (le plus strict : non-root, pas de privileges escalation, seccomp, capabilities drop, etc.). Le pod est rejeté.audit: restricted : n'empêche rien, mais journalise dans l'audit log toute violation du niveau restricted.warn: restricted : n'empêche rien non plus, mais renvoie un warning à l'utilisateur (visible via kubectl) en cas de violation.En clair : ici tout est calé sur restricted, donc les pods non conformes sont refusés, tout en produisant en plus des logs d'audit et des avertissements client.
Ce fichier sera accepté :
apiVersion: v1
kind: Pod
metadata:
name: pod-autorise
namespace: production
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: nginx
image: nginx:stable
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
restartPolicy: Never
Tandis que celui-là non :
apiVersion: v1
kind: Pod
metadata:
name: pod-refuse
namespace: production
spec:
containers:
- name: nginx
image: nginx:stable
securityContext:
privileged: true
restartPolicy: Never
Les webhooks permettent d'ajouter une logique d'admission personnalisée sans recompiler Kubernetes.
Permet de modifier dynamiquement les objets avant leur création. L'outil Kyverno facilite ces mutations.
Cas d'usage :
Valide les objets selon une logique personnalisée (accepte ou rejette).
Cas d'usage :
audit ou warn pour PodSecuritytimeoutSeconds pour éviter les blocages# Vérifier les rejets par admission controllers
kubectl get events --all-namespaces | grep -i "admission"
La création de ce genre de Webhook ne fait pas partie du CKA, la connaissance théorique par contre oui.
Qu'est-ce que "drainer un noeud" ?
Lorsque l'on fait fait une maintenance sur un noeud, on peut parfois avoir besoin de retirer ce noeud du cluster (redémarrage nécessaire, mise à jour de packages etc...). Il faut donc "drainer" ce noeud afin de retirer tous les pods présents. Cette méthode permet de retirer les pods correctement et les reschedule sur un autre noeud.
Afin de drainer un noeud, on va utiliser la commande suivante :
$ kubectl drain <nom noeud>
Si l'on a des DaemonSets sur le noeud (Un DaemonSet garantit que tous les nœuds (ou certains d'entre eux) exécutent une copie d'un pod) il va falloir les ignorer grâce au paramètre --ignore-daemonsets. Sans ça, la commande retournera une erreur car un DaemonSet ne peut pas être envoyé sur un autre noeud.
$ kubectl drain <nom noeud> --ignore-daemonsets
Une fois la maintenance finie, on veut remettre le noeud en service. Pour se faire :
$ kubectl uncordon <nom noeud>
Nous allons créer un pod simple (nginx) et un déploiement simple (nginx aussi) :
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx:1.29.4
ports:
- containerPort: 80
restartPolicy: OnFailure
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
app: my-deployment
spec:
replicas: 2
selector:
matchLabels:
app: my-deployment
template:
metadata:
labels:
app: my-deployment
spec:
containers:
- name: nginx
image: nginx:1.29.4
ports:
- containerPort: 80
On va maintenant appliquer ces 2 configurations :
$ kubectl apply -f pod.yml
pod/my-pod created
$ kubectl apply -f deployment.yml
deployment.apps/my-deployment created
On affiche maintenant les pods et on applique le paramètre -o wide afin de voir sur quel noeud tourne les pods :
# Sans précision de namespace, on regarde donc "default"
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-deployment-5f85c44867-8shmp 1/1 Running 0 7s 192.168.126.3 k8s-worker2 <none> <none>
my-deployment-5f85c44867-k86nl 1/1 Running 0 7s 192.168.126.2 k8s-worker2 <none> <none>
my-pod 1/1 Running 0 2m51s 192.168.126.1 k8s-worker2 <none> <none>
On voit que tous les pods tournent sur "k8s-worker2" (parfait), on va drain ce noeud afin de voir ce qu'il se passe avec le pod et le déploiement :
$ kubectl drain k8s-worker2
node/k8s-worker2 cordoned
error: unable to drain node "k8s-worker2", aborting command...
There are pending nodes to be drained:
k8s-worker2
cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override): default/my-pod
cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-5hzhz, kube-system/kube-proxy-dr9jb
Comme dit plus haut, nous devons ignorer les DaemonSets. Calico étant un DaemonSet nous allons passer le paramètre --ignore-daemonsets.
Concernant le pod, nous ne pouvons pas supprimer un pod qui n'est pas géré par un ReplicationController, ReplicaSet, Job, DaemonSet ou StatefulSet, on va donc utiliser le paramètre --force afin de supprimer le pod. Note : L'utilisation de --force doit être faite avec précaution car elle supprime immédiatement les pods sans attendre une terminaison gracieuse.
$ kubectl drain k8s-worker2 --ignore-daemonsets --force
node/k8s-worker2 already cordoned
WARNING: deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: default/my-pod; ignoring DaemonSet-managed Pods: kube-system/calico-node-5hzhz, kube-system/kube-proxy-dr9jb
evicting pod default/my-pod
evicting pod default/my-deployment-5f85c44867-8shmp
evicting pod default/my-deployment-5f85c44867-k86nl
pod/my-deployment-5f85c44867-k86nl evicted
pod/my-pod evicted
pod/my-deployment-5f85c44867-8shmp evicted
node/k8s-worker2 evicted
On va maintenant voir ou sont les pods :
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-deployment-5f85c44867-sgzcg 1/1 Running 0 78s 192.168.194.68 k8s-worker1 <none> <none>
my-deployment-5f85c44867-zfmt8 1/1 Running 0 78s 192.168.194.67 k8s-worker1 <none> <none>
Le déploiement a bien été bougé de worker. Le pod à lui été supprimé.
Si on regarde l'état des noeuds :
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-control Ready control-plane,master 5d13h v1.32.1
k8s-worker1 Ready <none> 5d13h v1.32.1
k8s-worker2 Ready,SchedulingDisabled <none> 5d13h v1.32.1
On voit que le "k8s-worker2" est dans un état ou il ne peut recevoir de pod "SchedulingDisabled".
On a fini la maintenance, on va maintenant rendre disponible le noeud :
$ kubectl uncordon k8s-worker2
node/k8s-worker2 uncordoned
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-control Ready control-plane,master 5d13h v1.32.1
k8s-worker1 Ready <none> 5d13h v1.32.1
k8s-worker2 Ready <none> 5d13h v1.32.1
Tout est rentré dans l'ordre. Les pods restent cependant sur le worker1, ils ne sont pas reschedule.
On peut supprimer le déploiement :
$ kubectl delete -f deployment.yml
deployment.apps "my-deployment" deleted
# ou
$ kubectl delete deployment my-deployment
Il est toujours préférable de garder à jour Kubernetes. Kubeadm facilite la tâche.
$ kubectl drain k8s-control --ignore-daemonsets
# --allow-change-held-packages car on avait lock la version auparavant
sudo apt-get update && \
sudo apt-get install -y --allow-change-held-packages kubeadm=1.32.3-1.1
$ sudo kubeadm upgrade plan v1.32.3
$ sudo kubeadm upgrade apply v1.32.3
# --allow-change-held-packages car on avait lock la version auparavant
sudo apt-get update && \
sudo apt-get install -y --allow-change-held-packages kubelet=1.32.3-1.1 kubectl=1.32.3-1.1
$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet
$ kubectl uncordon k8s-control
node/k8s-control uncordoned
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-control Ready control-plane,master 5d16h v1.32.3
k8s-worker1 Ready <none> 5d16h v1.32.1
k8s-worker2 Ready <none> 5d16h v1.32.1
$ kubectl drain k8s-worker1 --ignore-daemonsets --force
$ sudo apt-get update && \
sudo apt-get install -y --allow-change-held-packages kubeadm=1.32.3-1.1
$ sudo kubeadm upgrade node
$ sudo apt-get update && \
sudo apt-get install -y --allow-change-held-packages kubelet=1.32.3-1.1 kubectl=1.32.3-1.1
$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet
$ kubectl uncordon k8s-worker1
node/k8s-worker1 uncordoned
Pourquoi ?
etcd contient le stockage backend de kubernetes. C'est-à-dire tous les objets Kubernetes, les applications, les configurations.
Il est donc essentiel de sauvegarder etcd.
On peut backup etcd grâce à la CLI etcdctl :
$ ETCDCTL_API=3 etcdctl --endpoints=https://[ENDPOINT]:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save /backup/etcd-snapshot-$(date +%Y-%m-%d-%H-%M-%S).db
Pour vérifier l'état du backup :
controlplane:~$ ETCDCTL_API=3 etcdctl snapshot status /etcd-snapshot-XXXX.db --write-out=table
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| 2aedcadd | 5850 | 1370 | 6.9 MB |
+----------+----------+------------+------------+
Pour le restaurer :
$ ETCDCTL_API=3 etcdctl snapshot restore /etcd-snapshot-XXXX.db
etcdctl et etcdutl sont des outils en ligne de commande utilisés pour interagir avec etcd :
etcdctl : C'est le client principal pour interagir avec un cluster etcd en fonctionnement. Il permet de gérer les clés/valeurs (get, put, delete), vérifier la santé du cluster, gérer les membres, etc. C'est l'outil pour les opérations "en ligne".etcdutl : C'est un utilitaire plus récent (disponible dans les versions etcd 3.4+) conçu pour les opérations d'administration hors ligne, directement sur les fichiers de données etcd. Il est particulièrement utile pour la restauration de snapshots, la défragmentation, la migration de données et la validation de la cohérence des données sans nécessiter un cluster actif. Les commandes etcdctl snapshot save/restore/status (comme utilisées ci-dessus) remplissent une fonction similaire pour la gestion des snapshots et sont souvent utilisées car etcdctl est généralement déjà installé.