OVH Managed Kubernetes : retour d'expérience après 12 mois en production
← Retour au blogOVH

OVH Managed Kubernetes : retour d'expérience après 12 mois en production

10 mars 202510 min de lectureOVHKubernetesMKS

Nous utilisons OVH Managed Kubernetes (MKS) en production depuis un an. Bilan honnête : ce qui fonctionne bien, les limitations rencontrées et notre architecture actuelle.

Contexte et motivations du déploiement

En mars 2024, Move2Cloud a migré l'infrastructure d'un client du secteur médical vers OVH Managed Kubernetes (MKS). Les contraintes étaient strictes : hébergement exclusivement en France, certification HDS (Hébergeur de Données de Santé), et un budget qui excluait de facto AWS EKS. Après douze mois de production, voici notre retour d'expérience sans filtre.

Pourquoi OVH MKS plutôt que EKS ou GKE ? Quatre raisons ont guidé le choix :

  • Souveraineté européenne : datacenters en France (GRA7, SBG5), conformité RGPD native, certification HDS et ISO 27001 sans configuration supplémentaire
  • Absence de frais d'egress intra-OVH : les transferts entre le cluster MKS, l'Object Storage et la Managed DB sont gratuits — une économie significative à grande échelle
  • Private Registry Harbor intégré : OVH propose un registre de conteneurs managé (basé sur Harbor) inclus dans l'offre Public Cloud, avec authentification OIDC
  • Tarification compétitive : environ 40 à 57 % moins cher qu'une configuration EKS équivalente, comme nous le détaillons plus bas

Architecture déployée

Le cluster de production repose sur une topologie simple mais robuste. Nous avons privilégié la lisibilité opérationnelle sur la sophistication architecturale, ce qui s'est avéré un bon choix pour une équipe de taille modeste.

MKS Cluster — région GRA7
├── Node Pool "workers" (flavor b3-8 : 8 vCPU / 32 GB RAM)
│   ├── Min nodes : 3  |  Max nodes : 10  (autoscaling activé)
│   └── Réseau privé vRack — pas d'IP publique sur les workers
│
├── Namespace: production
│   ├── Deployment: api            (3 replicas, requests 500m/512Mi)
│   ├── Deployment: worker         (2 replicas, requests 1000m/2Gi)
│   └── StatefulSet: redis-cache   (1 replica, PVC 10Gi csi-cinder-high-speed)
│
├── Namespace: monitoring
│   └── kube-prometheus-stack 58.x
│       ├── Prometheus (PVC 50Gi)
│       ├── Grafana
│       └── Alertmanager → webhook Slack
│
└── Namespace: ingress-nginx
    └── nginx-ingress-controller
        └── Service LoadBalancer → OVH Load Balancer (IP publique)

OVH Object Storage (S3-compatible, région GRA)
  ├── Bucket: assets-static       (CDN activé)
  ├── Bucket: backups-postgresql
  └── Bucket: logs-loki

OVH Managed DB: PostgreSQL 15 (plan Essential-4, 4 vCPU / 15 GB RAM)

Provisioning Terraform du cluster MKS

Nous gérons l'intégralité de l'infrastructure en Terraform avec le provider OVH officiel. Voici les ressources essentielles pour monter un cluster MKS production-ready :

terraform {
  required_providers {
    ovh = {
      source  = "ovh/ovh"
      version = "~> 0.46"
    }
  }
}

provider "ovh" {
  endpoint           = "ovh-eu"
  application_key    = var.ovh_application_key
  application_secret = var.ovh_application_secret
  consumer_key       = var.ovh_consumer_key
}

# Réseau privé vRack
resource "ovh_cloud_project_network_private" "k8s_network" {
  service_name = var.ovh_service_name
  name         = "k8s-private-net"
  regions      = ["GRA7"]
  vlan_id      = 10
}

resource "ovh_cloud_project_network_private_subnet" "k8s_subnet" {
  service_name = var.ovh_service_name
  network_id   = ovh_cloud_project_network_private.k8s_network.id
  region       = "GRA7"
  start        = "10.0.0.100"
  end          = "10.0.0.200"
  network      = "10.0.0.0/24"
  dhcp         = true
  no_gateway   = false
}

# Cluster Kubernetes managé
resource "ovh_cloud_project_kube" "prod" {
  service_name       = var.ovh_service_name
  name               = "prod-cluster"
  region             = "GRA7"
  version            = "1.30"

  private_network_id = tolist(ovh_cloud_project_network_private.k8s_network.regions_attributes)[0].openstackid

  private_network_configuration {
    default_vrack_gateway              = "10.0.0.1"
    private_network_routing_as_default = true
  }
}

# Node pool avec autoscaling
resource "ovh_cloud_project_kube_nodepool" "workers" {
  service_name  = var.ovh_service_name
  kube_id       = ovh_cloud_project_kube.prod.id
  name          = "workers-b3-8"
  flavor_name   = "b3-8"
  min_nodes     = 3
  max_nodes     = 10
  desired_nodes = 3
  autoscale     = true

  template {
    metadata {
      labels = {
        "node-role" = "worker"
      }
    }
    spec {
      unschedulable = false
    }
  }
}

Intégration OVH Managed Private Registry (Harbor)

OVH met à disposition un registre Harbor managé, accessible depuis le cluster MKS sans frais d'egress. Le flux de déploiement passe par ce registre pour toutes les images de production.

# 1. Créer un compte robot dans Harbor (UI ou API Harbor)
#    Harbor → Projects → mon-projet → Robot Accounts → New Robot Account
#    Permissions: repository:pull, repository:push

# 2. Créer le secret imagePullSecret dans Kubernetes
kubectl create secret docker-registry ovh-registry-secret   --docker-server=<region>.registry.ovh.net   --docker-username=robot$mon-robot   --docker-password=<token>   --namespace=production

# 3. Référencer le secret dans les Deployments
# deployment.yaml
spec:
  template:
    spec:
      imagePullSecrets:
        - name: ovh-registry-secret
      containers:
        - name: api
          image: gra.registry.ovh.net/mon-projet/api:v1.2.3

# 4. Push depuis la CI
docker tag mon-app:latest gra.registry.ovh.net/mon-projet/api:v1.2.3
docker push gra.registry.ovh.net/mon-projet/api:v1.2.3

Configuration du monitoring : kube-prometheus-stack sur OVH

L'adaptation principale pour OVH concerne la StorageClass. OVH utilise Cinder (OpenStack) pour le stockage bloc. La classe csi-cinder-high-speed offre les meilleures performances pour Prometheus.

# values-kube-prometheus.yaml
prometheus:
  prometheusSpec:
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: csi-cinder-high-speed
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 50Gi
    retention: 30d
    retentionSize: "45GB"

grafana:
  persistence:
    enabled: true
    storageClassName: csi-cinder-high-speed
    size: 10Gi

alertmanager:
  alertmanagerSpec:
    storage:
      volumeClaimTemplate:
        spec:
          storageClassName: csi-cinder-high-speed
          resources:
            requests:
              storage: 5Gi

# Installation
helm upgrade --install kube-prometheus-stack   prometheus-community/kube-prometheus-stack   --namespace monitoring   --create-namespace   --values values-kube-prometheus.yaml   --version 58.3.0

Pipeline GitHub Actions : build → Harbor → MKS

Le pipeline CI/CD que nous avons mis en place s'appuie entièrement sur GitHub Actions. L'authentification au cluster MKS utilise un kubeconfig stocké dans les secrets GitHub.

# .github/workflows/deploy-production.yml
name: Deploy to OVH MKS

on:
  push:
    branches: [main]

env:
  REGISTRY: gra.registry.ovh.net
  IMAGE_NAME: mon-projet/api

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to OVH Harbor Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.HARBOR_USER }}
          password: ${{ secrets.HARBOR_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Configure kubectl for OVH MKS
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBECONFIG_OVH }}" | base64 -d > ~/.kube/config

      - name: Deploy to MKS
        run: |
          kubectl set image deployment/api             api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}             -n production
          kubectl rollout status deployment/api -n production --timeout=5m

Résultats de performance après 6 mois

Les métriques collectées via kube-prometheus-stack sur les six premiers mois de production livrent un bilan positif :

  • Latence P99 : 85 ms sur l'API principale (objectif : < 200 ms) — dépassement des attentes
  • Disponibilité : 99,95 % sur 6 mois (deux incidents de 8 et 15 minutes liés à des maintenances réseau OVH annoncées)
  • Autoscaling : lors d'un pic de charge (campagne marketing), le cluster est passé de 3 à 8 nœuds en 4 minutes 20 secondes
  • Utilisation CPU moyenne : 34 % en heure de pointe, 12 % en creux — l'autoscaling descend bien à 3 nœuds la nuit

Points de friction rencontrés

Soyons honnêtes sur les limitations. OVH MKS est excellent pour les workloads Kubernetes "purs", mais plusieurs points méritent attention avant de migrer depuis AWS :

  • Écosystème plus restreint qu'EKS : pas d'équivalent à AWS Controllers for Kubernetes (ACK), pas d'intégration native SQS/DynamoDB/SNS. Si votre application consomme intensément des services AWS managés, la migration est coûteuse.
  • Réactivité du support : les tickets critiques sur OVH sont résolus en 4 à 8 heures contre 15 à 30 minutes sur AWS Enterprise Support. Pour un client avec des SLA stricts, c'est un facteur bloquant.
  • Pas de NFS managé natif : OVH ne propose pas d'équivalent à EFS. Pour les volumes partagés (ReadWriteMany), nous avons déployé un serveur NFS auto-hébergé sur une instance dédiée — une complexité opérationnelle supplémentaire.
  • Cluster Autoscaler plus lent : le provisioning d'un nouveau nœud prend 3 à 5 minutes chez OVH contre 1 à 2 minutes sur EKS. Pour les pics très soudains, il faut surdimensionner légèrement le minimum de nœuds.
  • Pas d'Istio managé : l'installation du service mesh est manuelle. Nous utilisons Linkerd (plus léger qu'Istio) mais c'est une charge opérationnelle à prévoir.

Comparaison de coûts : OVH MKS vs AWS EKS

Pour une configuration équivalente (6 nœuds actifs en moyenne, Load Balancer, stockage bloc, base de données PostgreSQL managée) :

Composant OVH MKS AWS EKS équivalent
Control planeGratuit~73 €/mois
6 nœuds workers (b3-8 / m5.2xlarge)~840 €/mois~1 400 €/mois
Load Balancer~20 €/mois~55 €/mois
Stockage bloc (500 GB)~50 €/mois~110 €/mois
PostgreSQL managé~200 €/mois~460 €/mois
Egress réseau (500 GB/mois)Gratuit~45 €/mois
Total mensuel~1 110 €~2 143 €

Économie réalisée : 48 %, soit plus de 12 000 € par an pour ce seul projet.

Quand choisir OVH MKS vs EKS ?

Voici notre grille de décision après 12 mois de production :

  • Choisissez OVH MKS si : vos données doivent rester en France/UE (RGPD, HDS, secteur public), votre stack est un Kubernetes "pur" sans dépendances aux services AWS propriétaires, la maîtrise des coûts est une priorité, et votre SLA accepte un support moins réactif.
  • Choisissez AWS EKS si : vous consommez intensément des services AWS (Lambda, SQS, DynamoDB, Bedrock), vous avez besoin d'un support premium < 1h de réponse, vous utilisez des outils AWS natifs (ACK, IRSA, AWS Load Balancer Controller), ou la vitesse d'autoscaling est critique pour votre activité.

Conclusion

OVH MKS est un service de Kubernetes managé mature, compétitif et souverain. Après 12 mois en production, notre verdict est clair : pour les entreprises françaises avec des contraintes réglementaires et un budget contraint, c'est une excellente alternative à EKS. Le principal sacrifié est l'accès à l'écosystème AWS — mais si votre architecture est conçue pour Kubernetes pur (et non pour l'intégration native AWS), cette contrainte est gérable. L'économie réalisée — souvent supérieure à 40 % — peut financer des ressources humaines supplémentaires pour absorber la charge opérationnelle légèrement plus élevée.

← Retour au blog