TL;DRDocker fabrique le conteneur, Kubernetes le fait tourner, le répare et le multiplie quand la charge monte. Voici comment les deux s'articulent pour héberger des sites web, ce que nous avons appris en migrant les hébergements Websource, et comment superviser le tout avec Prometheus, Grafana et VictoriaMetrics, captures d'écran et requêtes PromQL commentées à l'appui.

Docker et Kubernetes : deux outils, deux rôles#

On oppose souvent Docker et Kubernetes, comme s'il fallait choisir. En réalité, ils ne font pas le même travail et se complètent.

Docker seul sait lancer un conteneur sur une machine. Mais si la machine redémarre, si le conteneur plante à 3 heures du matin ou si un site reçoit dix fois plus de visites que d'habitude, personne ne réagit. Kubernetes surveille en permanence l'état souhaité (« je veux 2 copies de ce site, prêtes à répondre ») et corrige tout écart avec l'état réel.

BesoinDockerKubernetes
Emballer une application et ses dépendancesOui (image, Dockerfile)Non, il utilise les images existantes
Lancer un conteneur sur un serveurOuiOui, sur n'importe quel serveur du cluster
Relancer un conteneur qui plantePartiellement (politique de redémarrage locale)Oui, et le déplace si le serveur tombe
Répartir les visiteurs entre plusieurs copiesNon (outil à ajouter)Oui (Service, Ingress)
Mettre à jour sans coupureNonOui (mise à jour progressive)
Ajouter des copies quand la charge monteNonOui (HPA)

Ce retour d'expérience vient de notre propre chantier : le 6 novembre 2026, nous avons migré les hébergements Websource vers Docker et Kubernetes, site par site, sans coupure, sur nos deux serveurs dédiés. Nous utilisons k3s, une distribution de Kubernetes légère et certifiée, adaptée à un petit nombre de serveurs.

Le parcours d'une requête, d'Internet au conteneur#

Quand un visiteur ouvre une page d'un site hébergé dans Kubernetes, sa requête traverse plusieurs étapes :

  1. HTTPS : le navigateur se connecte en chiffré. Chez nous, le serveur web de l'hôte garde les certificats et déchiffre la connexion, puis relaie la requête dans le cluster.
  2. Ingress et Traefik : un Ingress est une règle Kubernetes qui dit « les requêtes pour tel nom de domaine vont vers tel Service ». Le programme qui applique ces règles s'appelle un Ingress controller ; k3s fournit Traefik par défaut.
  3. Service : un Service est une adresse stable devant un groupe de pods. Les pods naissent et meurent, leur adresse change ; le Service, lui, reste et ne transmet qu'aux pods prêts.
  4. Pods : un pod est la plus petite unité que Kubernetes fait tourner, un ou plusieurs conteneurs lancés ensemble. Les deux copies d'un site peuvent tourner sur deux serveurs différents : Traefik envoie vers des pods de n'importe quel nœud.
Schéma du parcours d'une requête : le visiteur appelle le site en HTTPS, le serveur frontal déchiffre et relaie en HTTP vers le cluster Kubernetes, où l'Ingress Traefik choisit le Service selon le nom de domaine, puis le Service transmet à l'un des deux pods du site, placés sur deux nœuds différents ; les pods partagent les sessions dans Redis et la base MariaDB
Le parcours d'une requête dans un hébergement Docker et Kubernetes. Schéma simplifié.

Ce découpage a un avantage concret : nous avons pu basculer chaque site d'un serveur web classique vers le cluster en changeant seulement la destination du relais, sans toucher au DNS. En cas de problème, le retour arrière se fait de la même façon.

Les types d'objets Kubernetes appliqués à un hébergement web#

Kubernetes ne lance pas les conteneurs « à la main » : on lui décrit des objets dans des fichiers YAML (on parle de manifests), et chaque type d'objet correspond à un usage. Voici la règle que nous appliquons, résumée ; l'article Kubernetes détaille chaque type avec ses exemples.

ObjetRôleChez Websource
DeploymentGarder N copies identiques et interchangeables d'une application, les mettre à jour progressivementChaque site web (2 réplicas par site client)
StatefulSetApplications avec des données : nom stable, disque attitré, démarrage dans l'ordreRegistre d'images privé, réplica MariaDB, Redis des sessions
DaemonSetExactement un pod sur chaque serveur du clusterAgents de supervision (node-exporter et exporteurs des services de l'hôte)
CronJobTâche planifiée, comme une ligne de crontabPar exemple une tâche SEO d'un site
Init containerConteneur qui s'exécute et se termine avant le conteneur principalPréparer le réseau des pods de renfort
SidecarConteneur secondaire qui tourne à côté du principal, dans le même podUn relais vers la base MariaDB à côté du site

Pour un hébergement, la logique est simple : ce qui sert les pages et peut être recréé à l'identique est un Deployment ; ce qui garde des données est un StatefulSet ; ce qui doit tourner partout est un DaemonSet ; ce qui s'exécute à heure fixe est un CronJob.

Monter en charge : requests, limits et HPA#

Réserver et plafonner : requests et limits#

Chaque conteneur déclare deux valeurs par ressource (processeur et mémoire) :

  • la request est ce que Kubernetes réserve pour le conteneur : il ne le place sur un serveur que s'il reste assez de place ;
  • la limit est le plafond : au-delà de sa limite mémoire, le conteneur est arrêté (on dit qu'il est « OOMKilled ») ; au-delà de sa limite CPU, il est ralenti.

Le CPU se compte en millicœurs : 250m vaut un quart de cœur. Nous avons calculé ces valeurs à partir des mesures réelles de chaque site. Pour l'un de nos sites, cela donne :

resources:
  requests:
    cpu: 25m        # réservé : 2,5 % d'un cœur, la consommation habituelle mesurée
    memory: 64Mi    # réservé : 64 mébioctets
  limits:
    memory: 3Gi     # plafond mémoire : protège les autres sites du serveur
    # pas de limite CPU : le site peut profiter du processeur libre lors d'un pic

Un piège rencontré : un autoscaler posé avant d'ajuster les requests monte tout de suite au maximum, parce qu'il raisonne par rapport à des valeurs qui ne correspondent pas à la réalité. Mesurez d'abord, réglez ensuite.

L'autoscaling horizontal (HPA)#

Le HorizontalPodAutoscaler (HPA) ajoute ou retire des pods selon une mesure, le plus souvent l'utilisation du processeur. « Horizontal » signifie qu'on ajoute des copies, au lieu de grossir une copie. Chez nous, 16 sites ont un HPA : minimum 2 pods, maximum 3 ou 4 selon le site, montée d'un pod par minute au plus, descente lente. Un HPA simplifié ressemble à ceci :

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mon-site
  namespace: sites
spec:
  scaleTargetRef:            # ce que l'on fait varier : le Deployment du site
    apiVersion: apps/v1
    kind: Deployment
    name: mon-site
  minReplicas: 2             # jamais moins de 2 pods (mises à jour sans coupure)
  maxReplicas: 3
  metrics:
  - type: ContainerResource  # mesure le conteneur du site seul, pas ses voisins du pod
    containerResource:
      name: cpu
      container: web
      target:
        type: AverageValue
        averageValue: 300m   # au-delà de 0,3 cœur en moyenne par pod : on ajoute un pod
  behavior:
    scaleUp:
      policies: [{type: Pods, value: 1, periodSeconds: 60}]    # +1 pod par minute au plus
    scaleDown:
      stabilizationWindowSeconds: 600                          # attendre 10 min de calme
      policies: [{type: Pods, value: 1, periodSeconds: 300}]   # -1 pod toutes les 5 min
# Appliquer, puis suivre l'autoscaler en direct
kubectl apply -f hpa.yaml
kubectl -n sites get hpa mon-site --watch

# Comprendre une décision (événements, mesure lue, raison)
kubectl -n sites describe hpa mon-site

# Voir la consommation réelle des pods (metrics-server, fourni par k3s)
kubectl -n sites top pods

Notre test de charge a été fait sur un site de test réglé à 1 pod minimum (les sites clients ont 2 pods minimum) : le HPA est passé de 1 à 2 pods en 50 secondes environ, puis de 2 à 3 en 85 secondes environ ; les 1 641 requêtes du test ont toutes reçu une réponse 200. Après le test, le site est redescendu en une dizaine de minutes, comme prévu par la descente lente.

Pour que les mises à jour ne coupent rien, trois réglages travaillent ensemble : au moins 2 réplicas, un PodDisruptionBudget (règle qui interdit de retirer tous les pods d'un site à la fois, par exemple lors d'une maintenance), et une courte pause avant l'arrêt de chaque pod (preStop de 10 secondes) combinée à une nouvelle tentative automatique dans Traefik. Résultat mesuré : 0 erreur sur 12 520 requêtes envoyées pendant les mises à jour. Attention toutefois : un PodDisruptionBudget qui exige 1 pod disponible sur un site qui n'a qu'un seul réplica bloque la commande kubectl drain utilisée pour vider un serveur avant maintenance.

Les sessions dans Redis : indispensable dès 2 pods#

Une session PHP garde ce qui identifie un visiteur entre deux pages : connexion à son compte, contenu du panier. Par défaut, PHP l'enregistre dans un fichier sur le disque du serveur. Avec deux pods, la première requête peut arriver sur le pod 1 et la suivante sur le pod 2, qui ne connaît pas ce fichier : le visiteur est déconnecté ou son panier se vide.

La solution : stocker les sessions dans un service commun que tous les pods consultent. Nous utilisons Redis, une base de données en mémoire très rapide, sous la forme de Valkey 8.1 (un projet compatible Redis). Chaque site a son propre utilisateur, limité par des règles d'accès (ACL) à ses propres sessions. Nous avons basculé les sites vers Redis sans faire perdre de session aux visiteurs, en vérifiant qu'un panier créé avant la bascule restait lisible par chacun des pods.

La base de données et la latence : la leçon des pods de renfort#

Ajouter des pods ne sert à rien si tous attendent la même ressource. Nous avons testé des pods de renfort placés sur l'autre serveur, avec les fichiers du site partagés par le réseau. Sur le papier, cela double la capacité. En pratique, chaque requête SQL depuis l'autre serveur prend environ 2,5 ms au lieu de 0,03 ms en local. Une page PrestaShop fait entre 350 et 1 200 requêtes SQL : elle devient 5 à 20 fois plus lente.

La leçon : les renforts éloignés de la base conviennent aux sites qui font peu de requêtes SQL ou qui servent leurs pages depuis un cache ; pour une boutique, il vaut mieux garder le calcul près de la base. Côté données, MariaDB a désormais un réplica (une copie synchronisée en temps réel) dans le cluster, vérifié par sommes de contrôle sur 519 tables ; sa promotion en cas de panne est préparée mais n'a pas encore été faite.

Superviser : Prometheus, Grafana et VictoriaMetrics#

Un cluster qui répare tout seul les pannes peut aussi les masquer : un pod qui redémarre en boucle, un serveur qui manque de mémoire, un site qui répond lentement. La supervision (ou monitoring) consiste à mesurer en continu, à afficher ces mesures et à alerter quand elles sortent des clous.

Prometheus : le principe#

Prometheus est l'outil de référence pour mesurer un cluster Kubernetes. Ses idées clés :

  • Le modèle pull : c'est Prometheus qui vient chercher les mesures (on dit qu'il « scrape »), à intervalle régulier, sur une adresse HTTP de chaque cible, en général /metrics. Les applications n'envoient rien : elles exposent.
  • Les exporters (exporteurs) : de petits programmes qui traduisent l'état d'un système au format Prometheus. node-exporter pour le serveur (CPU, mémoire, disques, réseau), mysqld-exporter pour MariaDB, redis_exporter pour Redis, kube-state-metrics pour l'état des objets Kubernetes. Traefik expose ses propres mesures.
  • Les séries temporelles : chaque mesure est une suite de valeurs horodatées. Une série est identifiée par un nom de métrique et des labels (étiquettes clé=valeur).
  • PromQL : le langage de requête qui calcule des taux, des moyennes, des percentiles sur ces séries.
  • Alertmanager : le composant qui reçoit les alertes, les regroupe, évite les doublons et les envoie (e-mail, messagerie…).

Voici à quoi ressemble une mesure exposée par node-exporter, au format texte de Prometheus :

# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 530347.52

Lecture : depuis son démarrage, le cœur n°0 a passé 530 347 secondes inoccupé (mode="idle"). C'est un compteur : il ne fait qu'augmenter. Seule sa vitesse d'augmentation est intéressante, et c'est PromQL qui la calcule avec rate().

Grafana : les tableaux de bord#

Grafana n'enregistre pas les mesures : il les affiche. On lui déclare des sources de données (Prometheus ou un équivalent, mais aussi des bases SQL, des journaux…), puis on compose des tableaux de bord faits de panneaux (courbes, jauges, tableaux), chacun alimenté par une requête PromQL. Des variables en haut du tableau permettent de filtrer (un serveur, un namespace, un site) sans réécrire les requêtes. Grafana sait aussi déclencher des alertes ; nous avons préféré confier les nôtres à la chaîne règles + Alertmanager, indépendante de l'affichage.

Pourquoi VictoriaMetrics plutôt que Prometheus#

Chez nous, la base qui collecte et stocke les mesures n'est pas Prometheus lui-même mais VictoriaMetrics, une base de séries temporelles compatible Prometheus : elle lit le même format d'exposition, accepte la même configuration de collecte, comprend PromQL (son langage MetricsQL en est une extension) et se branche dans Grafana comme une source Prometheus. Son composant vmalert évalue des règles d'alerte au format Prometheus et les transmet au même Alertmanager.

Pourquoi ce choix, qui est courant ? Pour sa légèreté : sa version « single » tient en un seul programme, et sa documentation met en avant une consommation de mémoire et de disque réduite. Sur deux serveurs qui hébergent déjà des sites, chaque mégaoctet compte. Prometheus aurait aussi fait l'affaire : tout ce qui suit (exporters, PromQL, règles, Grafana) s'applique à l'un comme à l'autre.

Notre installation : collecte toutes les 30 secondes, conservation 30 jours, node-exporter en DaemonSet, kube-state-metrics, exporteurs Apache, MariaDB et Redis, mesures de Traefik, et 26 règles d'alerte envoyées par e-mail (mémoire, disque, pod en redémarrage en boucle, erreurs 5xx d'un site, temps de réponse, MariaDB ou Redis arrêtés…).

Nos tableaux de bord, en images#

Les captures suivantes viennent de notre Grafana, le 6 novembre 2026 vers 15 h (UTC), sur la dernière heure. Les noms de nos serveurs ont été remplacés par « nœud-1 » et « nœud-2 », et nous n'affichons que des éléments internes ou les sites du fondateur.

Tableau de bord Grafana Nœuds : courbes du CPU utilisé par nœud, du CPU par mode, de la charge système et de la RAM pour deux serveurs nommés nœud-1 et nœud-2 sur une heure
Tableau de bord « Nœuds » : processeur, charge et mémoire de chaque serveur du cluster, mesurés par node-exporter. Les noms des serveurs sont masqués.
Tableau de bord Grafana Pods et conteneurs filtré sur le namespace monitoring : 10 pods en marche, aucun pod non prêt ni en attente, aucun redémarrage, courbes du CPU et de la mémoire par pod et de la mémoire par nœud
Tableau de bord « Pods et conteneurs », filtré sur le namespace de la supervision : nombre de pods, redémarrages, CPU et mémoire de chaque pod.
Tableau de bord Grafana Sites alimenté par Traefik, filtré sur deux sites : requêtes par seconde, taux d'erreurs 5xx, temps de réponse p95 et moyen par site
Tableau de bord « Sites (Traefik) » : trafic, erreurs 5xx et temps de réponse, site par site. Filtré ici sur deux sites du fondateur.
Tableau de bord Grafana MariaDB, Redis et Apache : état OK des services sur les deux nœuds, requêtes par seconde, connexions, requêtes lentes, lectures InnoDB et buffer pool de MariaDB
Tableau de bord « MariaDB, Redis, Apache » : état des services des serveurs et activité de la base de données.

PromQL par l'exemple : quatre requêtes commentées#

Ces requêtes sont celles de nos tableaux de bord et de nos alertes. Vous pouvez les tester dans l'onglet Explore de Grafana ou dans l'interface de requêtes de Prometheus (ou vmui pour VictoriaMetrics).

1. Le CPU utilisé par chaque serveur#

1 - avg by (node) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
  • node_cpu_seconds_total{mode="idle"} : le temps d'inactivité de chaque cœur ;
  • rate(...[5m]) : sa vitesse d'augmentation par seconde, en moyenne sur 5 minutes, soit la part de temps inactif (entre 0 et 1) ;
  • avg by (node) : la moyenne de tous les cœurs, par serveur (le label node est ajouté par notre configuration de collecte ; sans elle, utilisez instance) ;
  • 1 - ... : on passe du temps inactif au temps utilisé. 0,8 signifie 80 %.
Interface vmui de VictoriaMetrics : la requête PromQL du CPU utilisé par nœud et sa courbe sur une heure pour nœud-1 et nœud-2, avec minimum, médiane et maximum de chaque série
La même requête dans vmui, l'interface de requêtes de VictoriaMetrics (l'équivalent de celle de Prometheus). Noms des serveurs masqués.

2. La mémoire réellement utilisée par chaque pod#

sum by (pod) (container_memory_working_set_bytes{namespace="monitoring", container!=""})

Le working set est la mémoire que le conteneur utilise vraiment, celle que Kubernetes compare à la limite. Le filtre container!="" écarte la ligne qui totalise déjà le pod entier, pour ne pas compter deux fois. Comparez ce chiffre à la request et à la limit du conteneur pour savoir s'ils sont bien réglés.

3. Le taux d'erreurs 5xx par site#

sum by (service) (rate(traefik_service_requests_total{code=~"5.."}[5m]))
  /
sum by (service) (rate(traefik_service_requests_total[5m]))

En haut, les réponses dont le code commence par 5 (erreur côté serveur) ; en bas, toutes les réponses ; le tout par site (service, au sens Traefik). Le résultat est une proportion : 0,05 signifie 5 % d'erreurs. Notre alerte se déclenche au-delà de 5 % pendant 5 minutes, et seulement si le site reçoit un minimum de trafic, pour ignorer une erreur isolée sur un site presque sans visite.

4. Le temps de réponse au 95e percentile (p95)#

histogram_quantile(0.95,
  sum by (le, service) (rate(traefik_service_request_duration_seconds_bucket[5m])))

Le p95 est la durée sous laquelle se trouvent 95 % des requêtes : si le p95 vaut 1 seconde, 5 % des visiteurs attendent plus d'une seconde. C'est plus parlant qu'une moyenne, qui cache les visiteurs les plus mal servis. Traefik range chaque durée dans des « seaux » (_bucket) avec un label le (inférieur ou égal) ; histogram_quantile estime le percentile à partir de ces seaux. Conséquence : le résultat ne peut pas dépasser le plus grand seau défini.

Une alerte qui a tout de suite servi#

Quelques heures après la mise en place de la supervision, l'alerte « CPU d'un nœud au-dessus de 80 % » s'est déclenchée. Les tableaux de bord ont montré d'où venait la charge : un robot d'indexation envoyait de nombreuses requêtes vers deux sites. Plutôt que de modifier ces sites, nous avons limité ce robot dans Traefik, devant eux : un middleware (un traitement appliqué aux requêtes en transit) de type rateLimit, appliqué seulement aux requêtes dont le User-Agent contient le nom du robot. Au-delà de la limite, le robot reçoit une réponse 429 Too Many Requests (« trop de requêtes »), qui l'invite à ralentir ; les visiteurs, eux, ne passent pas par cette règle.

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: robot-ratelimit
  namespace: sites
spec:
  rateLimit:
    average: 1          # 1 requête par seconde en moyenne
    period: 1s
    burst: 5            # pointe tolérée
    sourceCriterion:
      requestHeaderName: User-Agent   # un seul compteur pour tout le robot
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: robot-mon-site
  namespace: sites
spec:
  entryPoints: [web]
  routes:
  - match: Host(`www.exemple.fr`) && HeaderRegexp(`User-Agent`, `ExempleBot`)
    priority: 10000                  # passe avant la route normale du site
    middlewares: [{name: robot-ratelimit}]
    services: [{name: mon-site, port: 80}]

C'est l'intérêt d'avoir Docker, Kubernetes et la supervision ensemble : le problème est vu en quelques minutes, compris grâce aux mesures par site, et corrigé à l'entrée du cluster sans toucher au conteneur du site.

Mise en œuvre : installer les briques de supervision#

1. node-exporter en DaemonSet#

node-exporter doit tourner sur chaque serveur : c'est exactement le rôle d'un DaemonSet. Version simplifiée de notre manifest :

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels: {app: node-exporter}
  template:
    metadata:
      labels: {app: node-exporter}
    spec:
      hostNetwork: true            # utilise le réseau du serveur : voit ses vraies interfaces
      hostPID: true                # voit les processus du serveur
      tolerations: [{operator: Exists}]   # tourne aussi sur les nœuds marqués « réservés »
      containers:
      - name: node-exporter
        image: quay.io/prometheus/node-exporter:v1.12.1
        args:
        - --path.procfs=/host/proc
        - --path.sysfs=/host/sys
        - --path.rootfs=/host/root
        ports: [{name: metrics, containerPort: 9100}]
        resources:
          requests: {cpu: 10m, memory: 24Mi}
          limits: {memory: 64Mi}
        volumeMounts:
        - {name: proc, mountPath: /host/proc, readOnly: true}
        - {name: sys, mountPath: /host/sys, readOnly: true}
        - {name: root, mountPath: /host/root, readOnly: true}
      volumes:                     # dossiers du serveur, montés en lecture seule
      - {name: proc, hostPath: {path: /proc}}
      - {name: sys, hostPath: {path: /sys}}
      - {name: root, hostPath: {path: /}}

Le port 9100 utilisé en hostNetwork est ouvert sur le serveur : filtrez-le dans le pare-feu pour qu'il ne soit pas joignable depuis Internet.

2. Dire au collecteur où trouver les mesures#

Prometheus comme VictoriaMetrics lisent une configuration de collecte au même format. Ici, la liste des serveurs vient directement de l'API Kubernetes (kubernetes_sd_configs), et l'on remplace le port par celui de node-exporter :

global:
  scrape_interval: 30s           # une mesure toutes les 30 secondes
scrape_configs:
- job_name: node
  kubernetes_sd_configs:
  - role: node                   # une cible par nœud du cluster
  relabel_configs:
  - source_labels: [__address__]
    regex: '(.*):\d+'
    replacement: '${1}:9100'     # adresse du nœud, port de node-exporter
    target_label: __address__

3. Une règle d'alerte#

Une règle d'alerte est une requête PromQL avec une condition, une durée et un message. Celle qui a révélé le robot :

groups:
- name: quand-scaler
  interval: 1m                   # évaluée chaque minute
  rules:
  - alert: NoeudCPUSature
    expr: 1 - avg by (node) (rate(node_cpu_seconds_total{mode="idle"}[5m])) > 0.80
    for: 10m                     # doit rester vraie 10 minutes : ignore les pics brefs
    labels:
      severity: scale            # sert à Alertmanager pour choisir le destinataire
    annotations:
      summary: "CPU {{ $labels.node }} > 80 % depuis 10 min"

Une alerte passe par trois états : inactive (condition fausse), pending (condition vraie, mais pas encore depuis la durée for) et firing (déclenchée, transmise à Alertmanager).

Page des règles d'alerte de vmalert pour le groupe quand-scaler : sept règles avec leur expression PromQL, leur durée, leur label de sévérité et leur état inactive ou pending
Le groupe de règles « quand-scaler » dans l'interface de vmalert : chaque règle, son expression, son délai et son état (inactive ou pending).

4. Appliquer et vérifier avec kubectl#

# Créer le namespace puis appliquer les manifests du dossier
kubectl create namespace monitoring
kubectl apply -f supervision/

# Le DaemonSet doit afficher autant de pods prêts que de nœuds
kubectl -n monitoring get daemonset node-exporter
kubectl -n monitoring get pods -o wide        # -o wide : affiche le nœud de chaque pod

# En cas de souci : événements et journaux
kubectl -n monitoring describe pod <nom-du-pod>
kubectl -n monitoring logs daemonset/node-exporter

# Après une modification du manifest : suivre la mise à jour progressive
kubectl -n monitoring rollout status daemonset/node-exporter

5. Ouvrir Grafana sans l'exposer sur Internet#

Un tableau de bord de supervision révèle beaucoup de choses sur votre infrastructure : ne le publiez pas. Gardez le Service de Grafana interne au cluster et accédez-y par un tunnel SSH, qui fait passer la connexion par votre accès SSH chiffré.

# Sur le serveur : relayer le Service Grafana vers le port local 3000 du serveur
kubectl -n monitoring port-forward svc/grafana 3000:80

# Sur votre poste : relier votre port 3000 à celui du serveur, à travers SSH
ssh -N -L 3000:127.0.0.1:3000 utilisateur@votre-serveur

# Puis ouvrez http://127.0.0.1:3000 dans votre navigateur

Si kubectl est installé sur votre poste avec l'accès au cluster, la première commande suffit, lancée en local. L'option -N de SSH ouvre le tunnel sans lancer de terminal distant ; -L définit la redirection de port.

Chez Websource : un conteneur Docker pour chaque hébergement, dès la commande. Chaque hébergement web reçoit systématiquement son propre conteneur Docker, orchestré par Kubernetes. Quand vous commandez, votre compte est créé puis, en arrière-plan, votre conteneur est construit et déployé, en 25 secondes environ. Il part d'une image PHP par version, avec le php.ini et les extensions de votre hébergement ; si vous changez de version de PHP ou de réglage dans l'espace client, le conteneur suit. Vos fichiers restent accessibles comme avant (FTP, espace client, sauvegardes), et l'installation en un clic de WordPress ou PrestaShop fonctionne dans ce conteneur. Si la mise en conteneur échouait, votre site resterait servi de façon classique, sans coupure.

Ce que cela vous apporte : un environnement d'exécution identique et reproductible, des mises à jour sans coupure grâce aux deux réplicas, et une supervision de chaque site. C'est aussi une bonne base pour une démarche CI/CD : Websource ne fournit pas de pipeline Git hébergé, mais le dépôt d'un site se fait par l'API et le serveur MCP Websource (préparer un dépôt, envoyer l'archive ou le dump par curl, suivre le dépôt), donc il se scripte depuis GitHub Actions ou GitLab CI. Découvrez nos offres d'hébergement web et leur section « Notre infrastructure ».

Les pièges que nous avons rencontrés#

  • Les images PHP officielles n'ont pas de php.ini : partez de php.ini-production, fourni dans l'image, sinon PHP tourne avec ses valeurs par défaut.
  • Gardez le même chemin que sur le serveur d'origine : certains CMS enregistrent des chemins absolus dans leur configuration ou leur base.
  • Les .htaccess avec php_admin_* étaient ignorés sous PHP-FPM mais provoquent une erreur 500 sous mod_php dans le conteneur : à remplacer avant de basculer.
  • PodDisruptionBudget et site à un seul réplica : bloque kubectl drain.
  • HPA avant les requests : l'autoscaler monte tout de suite au maximum.

Glossaire Docker et Kubernetes#

TermeDéfinition
ImagePaquet figé contenant une application et tout ce qu'il lui faut pour tourner. Fabriquée par Docker à partir d'un Dockerfile.
ConteneurUne image en cours d'exécution, isolée du reste du serveur.
OrchestrateurLogiciel qui place, relance, répartit et met à jour des conteneurs sur plusieurs serveurs. Kubernetes en est un.
Cluster, nœudLe cluster est l'ensemble des serveurs gérés par Kubernetes ; chaque serveur est un nœud.
PodPlus petite unité lancée par Kubernetes : un ou plusieurs conteneurs qui partagent le réseau.
Deployment, StatefulSet, DaemonSet, CronJobTypes d'objets Kubernetes : application interchangeable, application avec données, un pod par nœud, tâche planifiée.
Init container, sidecarConteneur qui prépare le pod avant le démarrage du principal ; conteneur auxiliaire qui tourne à côté du principal.
ServiceAdresse stable devant un groupe de pods, qui répartit les requêtes entre les pods prêts.
Ingress, Ingress controllerRègle d'entrée HTTP (nom de domaine vers Service) et programme qui l'applique (Traefik dans k3s).
MiddlewareTraitement appliqué par Traefik aux requêtes en transit : limite de débit, nouvelle tentative, en-têtes…
Request, limitRessources réservées pour un conteneur ; plafond qu'il ne peut pas dépasser.
HPAHorizontalPodAutoscaler : ajoute ou retire des pods selon une mesure comme le CPU.
PodDisruptionBudgetRègle qui garantit un nombre minimal de pods disponibles pendant une maintenance.
SessionDonnées d'un visiteur conservées entre deux pages (connexion, panier).
RedisBase de données en mémoire, utilisée ici pour partager les sessions entre pods.
Réplica (base de données)Copie d'une base synchronisée en continu avec la base principale.
Supervision (monitoring)Mesure continue de l'état des serveurs et des applications, avec affichage et alertes.
PrometheusOutil open source de collecte de mesures et d'alerte, fondé sur le modèle pull.
Modèle pull, scrapeLe collecteur vient chercher les mesures sur chaque cible ; chaque collecte s'appelle un scrape.
ExporterProgramme qui traduit l'état d'un système (serveur, MariaDB, Redis) au format Prometheus.
Série temporelleSuite de valeurs horodatées, identifiée par un nom de métrique et des labels.
LabelÉtiquette clé=valeur qui précise une série : serveur, site, code HTTP…
PromQLLangage de requête de Prometheus, compris aussi par VictoriaMetrics.
p95 (95e percentile)Valeur sous laquelle se trouvent 95 % des mesures, ici des temps de réponse.
AlertmanagerComposant qui regroupe, dédoublonne et envoie les alertes.
VictoriaMetrics, vmalert, vmuiBase de séries temporelles compatible Prometheus ; son moteur de règles d'alerte ; son interface de requêtes.
GrafanaOutil de visualisation : sources de données, tableaux de bord, panneaux, variables.
Tunnel SSHRedirection d'un port à travers une connexion SSH chiffrée, pour joindre un service non exposé.

Questions fréquentes#

Faut-il choisir entre Docker et Kubernetes ?#

Non. Docker fabrique les images et lance des conteneurs ; Kubernetes orchestre ces conteneurs sur un ou plusieurs serveurs : il les relance en cas de panne, répartit le trafic, les met à jour sans coupure et en ajoute quand la charge monte. On utilise en général Docker (ou un outil équivalent) pour construire les images, et Kubernetes pour les faire tourner.

Pourquoi faut-il stocker les sessions dans Redis quand on a plusieurs pods ?#

Par défaut, PHP enregistre les sessions dans des fichiers locaux. Avec plusieurs pods, deux requêtes d'un même visiteur peuvent arriver sur des pods différents : le second ne trouve pas la session, et le visiteur perd sa connexion ou son panier. Un Redis commun permet à tous les pods de lire la même session.

Quelle est la différence entre Prometheus et Grafana ?#

Prometheus collecte et stocke les mesures, les interroge avec le langage PromQL et évalue les règles d'alerte. Grafana ne stocke rien : il se connecte à des sources de données comme Prometheus et affiche les mesures dans des tableaux de bord. Les deux sont presque toujours utilisés ensemble.

VictoriaMetrics remplace-t-il Prometheus ?#

Il peut jouer le même rôle de collecte et de stockage : il lit le même format de mesures, accepte la même configuration de collecte, comprend PromQL et se branche dans Grafana comme une source Prometheus. Il est souvent choisi pour sa légèreté. Les exporters, les requêtes et Alertmanager restent les mêmes.

Comment accéder à Grafana sans l'exposer sur Internet ?#

Laissez son Service interne au cluster, puis créez un tunnel : kubectl port-forward relaie le Service vers un port local du serveur, et ssh -N -L relie ce port à votre poste à travers la connexion SSH chiffrée. Vous ouvrez ensuite Grafana à l'adresse http://127.0.0.1 suivie du port choisi.

Article publié le 06/11/2026 sous la signature de Jonathan Gutierrez, rédigé à partir des sources citées ci-dessus, puis relu avant publication. Une information vous semble inexacte ou datée ? Signalez-le nous, nous corrigeons.