Cette quatrième série de travaux s'inscrit dans la continuité des trois premiers qui ont permis de découvrir les concepts fondamentaux de Kubernetes : les Pods, les Deployments, les Services et les ConfigMaps. Après avoir maîtrisé ces bases essentielles, nous allons maintenant explorer des fonctionnalités plus avancées de Kubernetes qui permettent de répondre à des besoins réels d'exploitation en production.
Il faut désormais augmenter les ressources allouées à la VM : 4 CPU, 8Gio de RAM
Je vous propose cinq thématiques différentes, chacune abordant un aspect spécifique de Kubernetes :
Ingress Controllers : Apprenez à gérer le routage du trafic HTTP entrant vers vos services. Cette fonctionnalité est essentielle pour exposer plusieurs applications web à travers une seule adresse IP, avec des règles de routage basées sur les noms d'hôtes ou les chemins d'URL.
Horizontal Pod Autoscaler (HPA) : Découvrez comment mettre en place le scaling automatique de vos applications en fonction de la charge. Cette capacité est cruciale pour assurer la disponibilité et les performances de vos services tout en optimisant l'utilisation des ressources.
StatefulSets : Explorez comment déployer et gérer des applications avec état, comme des bases de données, qui nécessitent une identité réseau stable et un stockage persistant. Contrairement aux Deployments qui sont adaptés aux applications sans état, les StatefulSets garantissent un ordre prévisible de déploiement et de scaling.
DaemonSets : Apprenez à déployer des agents qui doivent s'exécuter sur chaque nœud de votre cluster, comme des collecteurs de logs, des agents de monitoring ou des solutions de stockage distribuées.
Jobs et CronJobs : Découvrez comment planifier des tâches ponctuelles ou récurrentes dans votre cluster, parfaites pour les traitements par lots, les sauvegardes ou les tâches de maintenance.
Les Ingress Controllers permettent de gérer l'accès HTTP et HTTPS aux services dans un cluster Kubernetes. Ils offrent des fonctionnalités avancées comme le routage basé sur les URL, la terminaison TLS, et le load balancing.
# Démarrer Minikube avec suffisamment de ressources
minikube start --cpus=2 --memory=4096
# Activer l'addon Ingress
minikube addons enable ingress
# Vérifier que l'Ingress Controller est bien déployé
kubectl get pods -n ingress-nginx
# Créer un namespace pour nos applications
kubectl create namespace demo-apps
# Créer le fichier de configuration pour la première application
nano app1-deployment.yaml
Contenu du fichier app1-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app1
namespace: demo-apps
spec:
replicas: 2
selector:
matchLabels:
app: app1
template:
metadata:
labels:
app: app1
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: app1-html
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app1-html
namespace: demo-apps
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Application 1</title>
<style>
body {
background-color: #f0f8ff;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
h1 {
color: #0066cc;
}
</style>
</head>
<body>
<h1>Bienvenue sur l'Application 1!</h1>
</body>
</html>
---
apiVersion: v1
kind: Service
metadata:
name: app1-service
namespace: demo-apps
spec:
selector:
app: app1
ports:
- port: 80
targetPort: 80
# Appliquer la configuration de la première application
kubectl apply -f app1-deployment.yaml
# Créer le fichier de configuration pour la deuxième application
nano app2-deployment.yaml
Contenu du fichier app2-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app2
namespace: demo-apps
spec:
replicas: 2
selector:
matchLabels:
app: app2
template:
metadata:
labels:
app: app2
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: app2-html
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app2-html
namespace: demo-apps
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Application 2</title>
<style>
body {
background-color: #f5f5dc;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
h1 {
color: #8b4513;
}
</style>
</head>
<body>
<h1>Bienvenue sur l'Application 2!</h1>
</body>
</html>
---
apiVersion: v1
kind: Service
metadata:
name: app2-service
namespace: demo-apps
spec:
selector:
app: app2
ports:
- port: 80
targetPort: 80
# Appliquer la configuration de la deuxième application
kubectl apply -f app2-deployment.yaml
# Créer le fichier de configuration pour l'Ingress
nano demo-ingress.yaml
Contenu du fichier demo-ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
namespace: demo-apps
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /app1
pathType: Prefix
backend:
service:
name: app1-service
port:
number: 80
- path: /app2
pathType: Prefix
backend:
service:
name: app2-service
port:
number: 80
# Appliquer la configuration de l'Ingress
kubectl apply -f demo-ingress.yaml
apiVersion: networking.k8s.io/v1
: La version de l'API Kubernetes utilisée pour les Ingress.kind: Ingress
: Le type de ressource créée.metadata
: Contient les informations d'identification de la ressource.
name: demo-ingress
: Le nom de la ressource Ingress.namespace: demo-apps
: Le namespace où la ressource est créée.nginx.ingress.kubernetes.io/rewrite-target: /
: Cette annotation spécifique à NGINX Ingress Controller indique que l'URL doit être réécrite. Quand une requête arrive à /app1
ou /app2
, NGINX réécrira le chemin en /
avant de l'envoyer au service backend. Sans cette réécriture, la requête serait envoyée avec le préfixe, ce qui pourrait ne pas correspondre aux routes configurées dans l'application.rules
: Définit les règles de routage du trafic.http
: Cette règle s'applique au trafic HTTP.paths
: Liste des chemins d'URL à gérer.path: /app1
: Les requêtes visant le chemin /app1
seront routées selon cette règle.pathType: Prefix
: Le type de correspondance est "Prefix", ce qui signifie que /app1
et tous ses sous-chemins (comme /app1/page1
) seront gérés par cette règle.backend
: Définit où envoyer les requêtes qui correspondent à cette règle.
service
: Le service Kubernetes cible.
name: app1-service
: Le nom du service qui recevra les requêtes.port: number: 80
: Le port du service à utiliser.path: /app2
: Similaire à la première règle, mais pour le chemin /app2
./app2
seront envoyées au service app2-service
.Lorsqu'une requête HTTP arrive à l'Ingress Controller, celui-ci examine l'URL :
/app1
, la requête est redirigée vers le service app1-service
sur le port 80
, mais le chemin est réécrit en /
grâce à l'annotation rewrite-target
./app2
, la requête est redirigée vers le service app2-service
sur le port 80
, avec le chemin également réécrit en /
.Cela permet d'accéder à deux applications différentes via une seule adresse IP (celle de l'Ingress Controller), en utilisant différents chemins d'URL pour distinguer les applications. C'est particulièrement utile dans les environnements de production où vous souhaitez exposer plusieurs services sans multiplier les points d'entrée.
# Obtenir l'adresse IP de l'Ingress
minikube ip
# Ajouter une entrée dans le fichier /etc/hosts
echo "$(minikube ip) demo.kube.local" | sudo tee -a /etc/hosts
# Accéder aux applications via l'Ingress
curl http://demo.kube.local/app1
# Accéder à la deuxième application
curl http://demo.kube.local/app2
Il est possible également d'accéder via le navigateur web :
# Créer un fichier de configuration pour l'Ingress basé sur le nom d'hôte
nano host-based-ingress.yaml
Contenu du fichier host-based-ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: host-based-ingress
namespace: demo-apps
spec:
rules:
- host: app1.kube.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app1-service
port:
number: 80
- host: app2.kube.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app2-service
port:
number: 80
Kubernetes Ingress offre deux principales stratégies pour router le trafic vers différents services :
Caractéristique | Path-Based Ingress | Host-Based Ingress |
---|---|---|
Stratégie de routage | Basée sur le chemin d'URL | Basée sur le nom d'hôte (header HTTP Host) |
URL d'accès | http://demo.kube.local/app1 et http://demo.kube.local/app2 |
http://app1.kube.local et http://app2.kube.local |
Nombre de domaines | Un seul domaine avec différents chemins | Plusieurs domaines (ou sous-domaines) |
Réécriture d'URL | Nécessite rewrite-target pour transformer /app1 en / |
Ne nécessite pas de réécriture car le chemin est déjà / |
Configuration DNS | Une seule entrée DNS pour le domaine principal | Une entrée DNS par domaine/sous-domaine |
Le manifeste host-based-ingress.yaml
diffère principalement par ces aspects :
Clé host
dans les règles : Chaque règle spécifie explicitement un nom d'hôte différent (app1.kube.local
et app2.kube.local
)
Chemin simplifié : Comme chaque service a son propre domaine, le chemin peut être simplement /
pour les deux services
Absence d'annotation rewrite-target
: Aucune réécriture d'URL n'est nécessaire car les requêtes arrivent déjà avec le chemin /
Structure des règles : Au lieu d'avoir une seule règle avec plusieurs chemins, nous avons plusieurs règles, chacune avec son propre hôte
Avantages du Host-Based Ingress
app1.kube.local
plutôt qu'à demo.kube.local/app1
Avantages du Path-Based Ingress
Cas d'utilisation typiques
Mais revenons à notre exemple host-based ingress :
# Appliquer la configuration de l'Ingress basé sur le nom d'hôte
kubectl apply -f host-based-ingress.yaml
# Ajouter les entrées dans le fichier /etc/hosts
echo "$(minikube ip) app1.kube.local app2.kube.local" | sudo tee -a /etc/hosts
# Accéder à l'application 1 via le nom d'hôte
curl http://app1.kube.local
# Accéder à l'application 2 via le nom d'hôte
curl http://app2.kube.local
Et toujours par le web également (ex. app2) :
# Générer un certificat auto-signé
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt -subj "/CN=app1.kube.local"
# Créer un Secret pour stocker le certificat
kubectl create secret tls app1-tls \
--key tls.key \
--cert tls.crt \
--namespace demo-apps
Il faut au préalable supprimer le précédent ingress pour éviter un conflit :
kubectl delete ingress host-based-ingress -n demo-apps
Puis créer un nouveau avec TLS :
# Créer le fichier de configuration pour l'Ingress avec TLS
nano tls-ingress.yaml
Contenu du fichier tls-ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
namespace: demo-apps
spec:
tls:
- hosts:
- app1.kube.local
secretName: app1-tls
rules:
- host: app1.kube.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app1-service
port:
number: 80
avant d'appliquer c
# Appliquer la configuration de l'Ingress avec TLS
kubectl apply -f tls-ingress.yaml
# Accéder à l'application via HTTPS
curl -k https://app1.kube.local
Avec un navigateur, on obtient l'avertissement habituel lié à l'utilisation d'un certificat autosigné :
Voici quelques propositions d'exercices autour de ce sujet
Vous trouverez les correcions ici.
Le Horizontal Pod Autoscaler permet d'ajuster automatiquement le nombre de réplicas d'un Deployment, ReplicaSet ou StatefulSet en fonction de la charge observée (CPU, mémoire ou métriques personnalisées).
On nettoie l'environnement minikube :
minikube delete
Et on le redémarre avec suffisamment de ressources :
minikube start --cpus=2 --memory=4096
Activer le Metrics Server :
minikube addons enable metrics-server
Vérifier que le Metrics Server est bien déployé :
kubectl get pods -n kube-system | grep metrics-server
# Créer le fichier de déploiement pour l'application PHP Apache
nano php-apache-deployment.yaml
Contenu du fichier php-apache-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: k8s.gcr.io/hpa-example
ports:
- containerPort: 80
resources:
limits:
cpu: 500m
requests:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
labels:
run: php-apache
spec:
ports:
- port: 80
selector:
run: php-apache
Prenons le temps d'analyser ce manifeste (vous êtes d'ailleurs désormais en mesure de le faire par vous-même).
Ce manifeste YAML contient deux ressources Kubernetes distinctes, séparées par le délimiteur ---
:
Les définitions de ressources sont fondamentales pour l'HPA :
Par exemple, avec un seuil de 50% configuré dans l'HPA :
C'est un service de type "clusterIP" (par défaut).
Rappel : Ce type de service n'est accessible qu'à l'intérieur du cluster Kubernetes (il n'est pas exposé sur le node).
Le Service joue plusieurs rôles essentiels dans ce contexte HPA :
php-apache
)L'image k8s.gcr.io/hpa-example
est spécialement conçue pour les démonstrations d'HPA. Lorsqu'elle reçoit une requête HTTP, elle effectue un calcul intensif qui consomme délibérément du CPU. Ce comportement est idéal pour tester l'autoscaling, car :
Enfin, notons que :
# Appliquer le déploiement
kubectl apply -f php-apache-deployment.yaml
Il est possible de créer un HPA directement en ligne de commande :
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
Ou bien en utilisant un fichier YAML. Il faut d'abord supprimer le HPA qui vient d'être créé :
kubectl delete hpa php-apache
Notons que ce n'est pas obligatoire de le supprimer. L'application du manifeste suivant opérera une mise à jour implicite, et l'indiquera par un message d'alerte.
# Créer le fichier de configuration pour le HPA
nano php-apache-hpa.yaml
Contenu du fichier php-apache-hpa.yaml
:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
# Appliquer la configuration du HPA
kubectl apply -f php-apache-hpa.yaml
kubectl get hpa
Ce qui donne :
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache cpu: <unknown>/50% 1 10 1 4m4s
# Déployer un Pod pour générer de la charge
kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
Dans un autre terminal, observez le comportement du HPA :
kubectl get hpa php-apache --watch
Vous devriez voir le nombre de réplicas augmenter en fonction de la charge (cela peut prendre un peu de temps) :
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache cpu: <unknown>/50% 1 10 1 8m19s
php-apache Deployment/php-apache cpu: <unknown>/50% 1 10 1 8m20s
php-apache Deployment/php-apache cpu: 229%/50% 1 10 1 9m20s
php-apache Deployment/php-apache cpu: 229%/50% 1 10 4 9m35s
php-apache Deployment/php-apache cpu: 229%/50% 1 10 5 9m50s
kubectl top nodes
kubectl top pods
kubectl patch hpa php-apache --type='json' -p='[{"op": "replace", "path": "/spec/metrics/0/resource/target/averageUtilization", "value": 30}]'
kubectl patch hpa php-apache -p '{"spec":{"targetCPUUtilizationPercentage":30}}'
kubectl get hpa php-apache --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache cpu: 52%/30% 1 10 5 19m
php-apache Deployment/php-apache cpu: 52%/30% 1 10 5 19m
php-apache Deployment/php-apache cpu: 51%/30% 1 10 9 19m
php-apache Deployment/php-apache cpu: 30%/30% 1 10 9 20m
Vous pouvez arrêter le pod générateur de charge (CTRL+C).
Après l'arrêt du générateur de charge, le nombre de réplicas devrait diminuer, mais ce processus est délibérément plus lent que l'augmentation pour plusieurs raisons techniques et pratiques.
Lorsque vous arrêtez le générateur de charge, l'utilisation CPU des pods chute .L'HPA observe cette baisse mais ne réagit pas immédiatement. Par défaut, l'HPA attend environ 5 minutes après avoir détecté une baisse de charge avant de commencer à réduire le nombre de pods. La réduction se fait généralement de manière progressive, pas tous les pods en même temps
Cette lenteur est intentionnelle dans la conception de Kubernetes pour plusieurs raisons :
Après que vous avez lu attentivement ces explications , vous devriez observer une réduction des replicas :
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache cpu: 30%/30% 1 10 9 22m
php-apache Deployment/php-apache cpu: 20%/30% 1 10 9 23m
php-apache Deployment/php-apache cpu: 0%/30% 1 10 9 24m
php-apache Deployment/php-apache cpu: 0%/30% 1 10 9 28m
php-apache Deployment/php-apache cpu: 0%/30% 1 10 6 28m
php-apache Deployment/php-apache cpu: 0%/30% 1 10 6 29m
php-apache Deployment/php-apache cpu: 0%/30% 1 10 1 29m
# Créer le fichier de configuration pour le HPA multi-métriques
nano php-apache-multi-hpa.yaml
Contenu du fichier php-apache-multi-hpa.yaml
:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache-multi
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 50
Avant de l'appliquer, on va supprimer l'HPA précédent ...
kubectl delete hpa php-apache
... vérifier qu'il a bien été supprimé :
kubectl get hpa
et enfin on applique le nouveau HPA multi-métrique :
kubectl apply -f php-apache-multi-hpa.yaml
Pour vérifier qu'il est correctement configuré :
kubectl describe hpa php-apache-multi
Dans le contexte de notre exemple, il est difficile de mettre en place l'observation de la charge mémoire, sans mettre en oeuvre une application plus complexe. Nous nous en tiendrons à la promesse de Kubernetes de bien gérer cela ! Notons malgré tout ceci :
L'HPA avec des métriques multiples prend ses décisions de scaling selon ces règles :
Pour aller plus loin, voici quelques pistes d'exercices :
Les StatefulSets sont conçus pour les applications avec état qui nécessitent une identité réseau stable, un stockage persistant et un ordre de déploiement/scaling/terminaison déterministe.
On nettoie l'environnement minikube :
minikube delete
Et on le redémarre avec suffisamment de ressources :
minikube start --cpus=2 --memory=4096
On crée un namespace pour notre application :
kubectl create namespace stateful-demo
# Créer le fichier de configuration pour la classe de stockage
nano mysql-storage-class.yaml
Contenu du fichier mysql-storage-class.yaml
:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysql-retain
namespace: stateful-demo
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Retain
volumeBindingMode: Immediate
La StorageClass "mysql-retain" définie dans ce manifeste joue un rôle essentiel pour les StatefulSets : elle sert de modèle pour le provisionnement automatique du stockage persistant nécessaire aux applications avec état comme MySQL.
Dans un StatefulSet, chaque pod possède une identité stable et des besoins de stockage persistant uniques. La StorageClass permet :
En production, vous verriez :
Les StorageClass sont généralement des ressources au niveau du cluster (pas liées à un namespace). Nous avons spécifié ici un namespace dans les métadonnées, ce qui est inhabituel mais fonctionne dans Minikube.
# Appliquer la configuration de la classe de stockage
kubectl apply -f mysql-storage-class.yaml
# Créer le fichier de configuration pour MySQL
nano mysql-statefulset.yaml
Contenu du fichier mysql-statefulset.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
namespace: stateful-demo
data:
my.cnf: |
[mysqld]
default_authentication_plugin=mysql_native_password
skip-name-resolve
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
secure-file-priv=/var/lib/mysql-files
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
bind-address=0.0.0.0
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: stateful-demo
type: Opaque
data:
root-password: cm9vdHBhc3N3b3Jk # rootpassword (nb : encodé en base64)
database: bXlhcHA= # myapp
username: bXl1c2Vy # myuser
password: bXlwYXNzd29yZA== # mypassword
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: stateful-demo
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:8.0
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id based on pod ordinal index
POD_NAME="$HOSTNAME"
[[ "$POD_NAME" =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
echo "bind-address=0.0.0.0" >> /mnt/conf.d/server-id.cnf
# Only the first pod needs to be master
if [[ $ordinal -eq 0 ]]; then
echo "This is the master"
else
echo "This is a replica"
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-secret
key: database
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: conf
mountPath: /etc/mysql/conf.d
- name: config-map
mountPath: /etc/mysql/my.cnf
subPath: my.cnf
resources:
requests:
cpu: 100m
memory: 256Mi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "127.0.0.1", "--password=${MYSQL_ROOT_PASSWORD}"]
initialDelaySeconds: 30
periodSeconds: 2
timeoutSeconds: 1
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: mysql-retain
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: stateful-demo
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql
---
apiVersion: v1
kind: Service
metadata:
name: mysql-ha
namespace: stateful-demo
spec:
ports:
- port: 3306
name: mysql
selector:
app: mysql
Ce manifeste déploie un cluster MySQL sur Kubernetes en utilisant un StatefulSet.
Il s'agit d'une configuration pédagogique qui démontre les principes clés des StatefulSets, mais qui ne met pas en œuvre une véritable réplication de données entre les instances MySQL.
ConfigMap
Fournit la configuration globale de MySQL qui sera appliquée à tous les pods :
Secret
Stocke de manière sécurisée les informations d'authentification :
StatefulSet
Cœur du déploiement qui assure les propriétés essentielles pour les applications avec état :
Le container d'initialisation extrait l'indice ordinal depuis le nom du pod (par exemple, "2" dans "mysql-2") et configure chaque instance MySQL avec un identifiant unique. Bien qu'il identifie différents rôles (maître/réplica) dans les logs, tous les pods sont en fait configurés identiquement dans cette configuration simplifiée.
Le container principal exécute MySQL avec les variables d'environnement provenant du Secret, des volumes pour les données et la configuration, et des sondes de santé pour surveiller l'état de MySQL.
Le template de volume persistant provisionne automatiquement un volume distinct pour chaque pod MySQL, garantissant que les données survivent aux redémarrages des pods.
Services
Deux services distincts exposent le cluster MySQL de différentes manières :
mysql (headless) : Service sans IP virtuelle qui permet d'accéder directement à des pods spécifiques via leurs noms DNS individuels comme mysql-0.mysql, mysql-1.mysql, etc.
mysql-ha : Service standard avec une IP virtuelle qui offre un point d'accès hautement disponible distribuant les connexions entre tous les pods.
Cette configuration présente une limitation majeure à comprendre : il n'y a pas de réplication de données entre les pods. Chaque pod MySQL possède son propre volume et opère de manière indépendante.
Conséquences pratiques :
Pour une véritable solution hautement disponible et cohérente, il faudrait implémenter Galera Cluster, qui offre :
L'implémentation de Galera nécessiterait d'ajouter des paramètres de configuration spécifiques dans le script d'initialisation pour définir le fournisseur Galera, le nom du cluster, l'adresse du cluster et un mécanisme de bootstrap initial sur le premier nœud.
Ce manifeste est donc conçu pour saisir les concepts clés des StatefulSets :
# Appliquer la configuration du StatefulSet MySQL
kubectl apply -f mysql-statefulset.yaml
# Vérifier le StatefulSet
kubectl get statefulset -n stateful-demo
Vérifier le déploiement des pods (avec watch). Vous verrez le master qui se déploie dans un premier temps, puis les suivants :
watch kubectl get pods -n stateful-demo
# Vérifier les volumes persistants
kubectl get pvc -n stateful-demo
# Se connecter au pod master (mysql-0)
kubectl exec -it mysql-0 -n stateful-demo -- mysql -uroot -p
Entrez le mot de passe root (rootpassword), puis exécutez les commandes suivantes dans le shell MySQL :
CREATE DATABASE IF NOT EXISTS test;
USE test;
CREATE TABLE IF NOT EXISTS messages (message VARCHAR(255));
INSERT INTO messages VALUES ('Hello from StatefulSet!');
SELECT * FROM messages;
exit;
# Vérifiez que les données persistent après un redémarrage
kubectl delete pod mysql-0 -n stateful-demo
# Attendez que le pod soit prêt
kubectl wait --for=condition=Ready pod/mysql-0 -n stateful-demo --timeout=120s
# Vérifiez que les données sont toujours présentes
kubectl exec -it mysql-0 -n stateful-demo -- mysql -uroot -p -e "SELECT * FROM test.messages;"
# Augmenter le nombre de réplicas à 5
kubectl scale statefulset mysql -n stateful-demo --replicas=5
Observer l'ordre de création des pods, avec :
# Utilisez Ctrl+C pour quitter
kubectl get pods -n stateful-demo -w
ou encore (ma préférence) :
# Utilisez Ctrl+C pour quitter
watch kubectl get pods -n stateful-demo
# Créer le fichier de configuration pour le pod client
nano mysql-client.yaml
Contenu du fichier mysql-client.yaml
:
apiVersion: v1
kind: Pod
metadata:
name: mysql-client
namespace: stateful-demo
spec:
containers:
- name: mysql-client
image: debian:stable-slim
command:
- bash
- "-c"
- |
# Installer les outils nécessaires
apt update
apt install -y default-mysql-client inetutils-ping dnsutils curl
# Rester actif
echo "MySQL client pod est prêt."
echo "Utilisez 'kubectl exec -it mysql-client-debian -n stateful-demo -- bash' pour vous connecter."
tail -f /dev/null
# Déployer le pod client
kubectl apply -f mysql-client.yaml
# Se connecter au pod client
kubectl exec -it mysql-client -n stateful-demo -- bash
Une fois dans le conteneur, vérifiez :
# Tester la connectivité aux différents pods du StatefulSet
ping mysql-0.mysql.stateful-demo.svc.cluster.local
ping mysql-1.mysql.stateful-demo.svc.cluster.local
ping mysql-2.mysql.stateful-demo.svc.cluster.local
# Connexion au master
mysql -h mysql-0.mysql -uroot -p
# Connexion au service HA (load-balancing)
mysql -h mysql-ha -uroot -p
# Quitter le shell du conteneur
exit
(en cours de finalisation)
Les DaemonSets permettent de s'assurer qu'une copie d'un pod s'exécute sur tous les nœuds (ou un sous-ensemble) du cluster. Ils sont idéaux pour les services d'infrastructure comme la collecte de logs, la supervision ou le stockage distribué.
# Démarrer Minikube avec plusieurs nœuds (nécessite Minikube v1.14.0+)
minikube start --nodes 3
# Vérifier les nœuds du cluster
kubectl get nodes
# Créer le fichier de configuration pour le DaemonSet Fluentd
nano fluentd-daemonset.yaml
Contenu du fichier fluentd-daemonset.yaml
:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
# Appliquer la configuration du DaemonSet
kubectl apply -f fluentd-daemonset.yaml
# Vérifier le DaemonSet
kubectl get daemonset -n kube-system
# Vérifier les pods et leur distribution sur les nœuds
kubectl get pods -n kube-system -l name=fluentd-elasticsearch -o wide
# Ajouter un label à un nœud spécifique
kubectl label nodes minikube-m02 disk=ssd
# Créer le fichier de configuration pour le DaemonSet avec nodeSelector
nano node-exporter-daemonset.yaml
Contenu du fichier node-exporter-daemonset.yaml
:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: prometheus-node-exporter
namespace: default
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
nodeSelector:
disk: ssd
containers:
- name: node-exporter
image: prom/node-exporter:v1.1.2
ports:
- containerPort: 9100
# Appliquer la configuration du DaemonSet avec nodeSelector
kubectl apply -f node-exporter-daemonset.yaml
# Vérifier le déploiement et confirmer qu'il s'exécute uniquement sur le nœud avec le label
kubectl get pods -l app=node-exporter -o wide
# Mettre à jour l'image du DaemonSet
kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=quay.io/fluentd_elasticsearch/fluentd:v3.0.0 -n kube-system
# Observer le déploiement progressif
kubectl rollout status ds/fluentd-elasticsearch -n kube-system
# Créer le fichier de configuration pour le DaemonSet de supervision réseau
nano kube-proxy-metrics.yaml
Contenu du fichier kube-proxy-metrics.yaml
:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-proxy-metrics
namespace: kube-system
spec:
selector:
matchLabels:
app: kube-proxy-metrics
template:
metadata:
labels:
app: kube-proxy-metrics
spec:
hostNetwork: true
containers:
- name: tcpdump
image: nicolaka/netshoot
command:
- "/bin/sh"
- "-c"
- "tcpdump -i any -w /capture/$(date +%Y-%m-%d_%H-%M-%S).pcap port 80 -G 300 -W 12"
securityContext:
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
volumeMounts:
- name: capture-volume
mountPath: /capture
volumes:
- name: capture-volume
hostPath:
path: /tmp/network-captures
type: DirectoryOrCreate
# Appliquer la configuration du DaemonSet de supervision réseau
kubectl apply -f kube-proxy-metrics.yaml
# Examiner la stratégie de mise à jour du DaemonSet
kubectl get daemonset fluentd-elasticsearch -n kube-system -o yaml | grep -A 5 updateStrategy
# Créer le fichier de configuration pour modifier la stratégie de mise à jour
nano update-strategy.yaml
Contenu du fichier update-strategy.yaml
:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
# Appliquer la modification de la stratégie de mise à jour
kubectl apply -f update-strategy.yaml
(en cours de finalisation)
Les Jobs et CronJobs permettent d'exécuter des tâches à complétion, ponctuelles ou récurrentes, dans un cluster Kubernetes. Ils sont parfaits pour les traitements par lots, les migrations de données ou les tâches de maintenance.
# Démarrer Minikube
minikube start
# Créer un namespace pour les Jobs
kubectl create namespace jobs-demo
# Créer le fichier de configuration pour un Job simple
nano hello-job.yaml
Contenu du fichier hello-job.yaml
:
apiVersion: batch/v1
kind: Job
metadata:
name: hello-job
namespace: jobs-demo
spec:
template:
spec:
containers:
- name: hello
image: busybox
command: ["sh", "-c", "echo Hello from Kubernetes Job! && sleep 10"]
restartPolicy: Never
backoffLimit: 4
# Appliquer la configuration du Job
kubectl apply -f hello-job.yaml
# Vérifier l'état du Job
kubectl get jobs -n jobs-demo
# Vérifier les pods créés par le Job
kubectl get pods -n jobs-demo
# Consulter les logs du pod du Job
kubectl logs -n jobs-demo -l job-name=hello-job
# Créer le fichier de configuration pour un Job parallèle
nano parallel-job.yaml
Contenu du fichier parallel-job.yaml
:
apiVersion: batch/v1
kind: Job
metadata:
name: parallel-job
namespace: jobs-demo
spec:
completions: 5
parallelism: 2
template:
spec:
containers:
- name: worker
image: busybox
command: ["sh", "-c", "echo Processing item $HOSTNAME && sleep 5"]
restartPolicy: Never
backoffLimit: 4
# Appliquer la configuration du Job parallèle
kubectl apply -f parallel-job.yaml
# Créer le fichier de configuration pour un Job avec timeout
nano timeout-job.yaml
Contenu du fichier timeout-job.yaml
:
apiVersion: batch/v1
kind: Job
metadata:
name: timeout-job
namespace: jobs-demo
spec:
template:
spec:
containers:
- name: long-task
image: busybox
command: ["sh", "-c", "echo Starting long task... && sleep 100 && echo Task completed"]
restartPolicy: Never
backoffLimit: 2
activeDeadlineSeconds: 30
# Appliquer la configuration du Job avec timeout
kubectl apply -f timeout-job.yaml
# Créer le fichier de configuration pour un CronJob
nano hello-cron.yaml
Contenu du fichier hello-cron.yaml
:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cron
namespace: jobs-demo
spec:
schedule: "*/1 * * * *" # Toutes les minutes
concurrencyPolicy: Allow
startingDeadlineSeconds: 30
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
command: ["sh", "-c", "echo Hello from CronJob! && date && sleep 5"]
restartPolicy: OnFailure
# Appliquer la configuration du CronJob
kubectl apply -f hello-cron.yaml
# Vérifier les CronJobs
kubectl get cronjobs -n jobs-demo
# Vérifier les Jobs créés par le CronJob
kubectl get jobs -n jobs-demo
# Vérifier les pods créés par les Jobs
kubectl get pods -n jobs-demo
# Attendre quelques minutes pour voir plusieurs exécutions
kubectl get jobs -n jobs-demo
# Créer le fichier de configuration pour la base de données MySQL
nano mysql-deployment.yaml
Contenu du fichier mysql-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: jobs-demo
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: password
- name: MYSQL_DATABASE
value: testdb
ports:
- containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: jobs-demo
spec:
ports:
- port: 3306
selector:
app: mysql
# Appliquer la configuration de la base de données MySQL
kubectl apply -f mysql-deployment.yaml
# Créer le fichier de configuration pour le CronJob de sauvegarde
nano mysql-backup-cronjob.yaml
Contenu du fichier mysql-backup-cronjob.yaml
:
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
namespace: jobs-demo
spec:
schedule: "0 */6 * * *" # Toutes les 6 heures
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:5.7
command:
- /bin/sh
- -c
- |
BACKUP_DIR="/backup"
BACKUP_FILE="${BACKUP_DIR}/backup-$(date +%Y%m%d_%H%M%S).sql"
mkdir -p ${BACKUP_DIR}
mysqldump -h mysql -u root -ppassword --all-databases > ${BACKUP_FILE}
echo "Backup completed: ${BACKUP_FILE}"
ls -la ${BACKUP_DIR}
volumeMounts:
- name: backup-volume
mountPath: /backup
restartPolicy: OnFailure
volumes:
- name: backup-volume
emptyDir: {}
# Appliquer la configuration du CronJob de sauvegarde
kubectl apply -f mysql-backup-cronjob.yaml