OVH Managed Kubernetes: 12-Month Production Experience Report
Cloud

OVH Managed Kubernetes: 12-Month Production Experience Report

March 5, 202510 min readOVHKubernetesMKS

After 12 months running critical workloads on OVH Managed Kubernetes, here is our honest assessment: what worked, what surprised us, and what we would do differently.

Why OVH MKS? The Decision Context

In March 2024, Move2Cloud migrated a healthcare client's production workloads to OVH Managed Kubernetes (MKS). The requirements were non-negotiable: French-hosted infrastructure, HDS certification (healthcare data hosting), and a budget that ruled out AWS EKS. Twelve months later, here is our unfiltered production report.

The four key reasons OVH MKS beat the alternatives:

  • European data sovereignty: all datacenters in France (GRA7, SBG5), GDPR-native, ISO 27001 and HDS certified out of the box — no additional configuration required
  • Zero intra-OVH egress fees: traffic between MKS, OVH Object Storage, and OVH Managed DB is free — a meaningful saving at scale
  • Integrated Harbor private registry: OVH provides a managed Harbor registry included in the Public Cloud offer, with OIDC authentication
  • Competitive pricing: 40–57% cheaper than equivalent EKS configurations, as detailed in our cost breakdown below

Production Architecture

We kept the architecture deliberately simple to match our lean operational team. The topology below served us well through twelve months of continuous operation:

MKS Cluster — Region GRA7
├── Node Pool "workers" (b3-8 flavor: 8 vCPU / 32 GB RAM)
│   ├── Min nodes: 3  |  Max nodes: 10  (autoscaling enabled)
│   └── Private vRack network — workers have no public IP
│
├── 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 → Slack webhook
│
└── Namespace: ingress-nginx
    └── nginx-ingress-controller
        └── LoadBalancer Service → OVH Load Balancer (public IP)

OVH Object Storage (S3-compatible, GRA region)
  ├── Bucket: static-assets       (CDN enabled)
  ├── Bucket: postgresql-backups
  └── Bucket: loki-logs

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

Terraform Provisioning of the MKS Cluster

The entire infrastructure is managed with Terraform using the official OVH provider. Below are the essential resources for a production-ready MKS cluster with private networking:

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
}

# Private vRack network
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
}

# Managed Kubernetes cluster
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
  }
}

# Autoscaling node pool
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
}

Integrating OVH Managed Private Registry (Harbor) with MKS

OVH's managed Harbor registry integrates cleanly with MKS. The workflow: build locally or in CI, push to Harbor, Kubernetes pulls via an imagePullSecret. No egress costs because the registry is on the same OVH network.

# 1. Create a Robot Account in Harbor (UI: Projects → my-project → Robot Accounts)
#    Permissions: repository:pull, repository:push

# 2. Create the imagePullSecret in Kubernetes
kubectl create secret docker-registry ovh-registry-secret   --docker-server=gra.registry.ovh.net   --docker-username=robot$my-robot   --docker-password=<robot-token>   --namespace=production

# 3. Reference the secret in Deployments
# deployment.yaml
spec:
  template:
    spec:
      imagePullSecrets:
        - name: ovh-registry-secret
      containers:
        - name: api
          image: gra.registry.ovh.net/my-project/api:v1.2.3

# 4. Push from CI pipeline
docker tag my-app:latest gra.registry.ovh.net/my-project/api:v1.2.3
docker push gra.registry.ovh.net/my-project/api:v1.2.3

Monitoring: kube-prometheus-stack on OVH

The main adaptation for OVH is the StorageClass. OVH uses Cinder (OpenStack block storage) rather than AWS EBS. The csi-cinder-high-speed class provides the best IOPS for Prometheus write patterns:

# 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

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

GitHub Actions Deployment Pipeline: Build → Harbor → MKS

Our CI/CD pipeline builds Docker images, pushes them to OVH Harbor, and deploys to MKS. The kubeconfig is stored as a base64-encoded GitHub Secret — no OIDC provider is available for OVH yet (unlike AWS), so this is the current approach.

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

on:
  push:
    branches: [main]

env:
  REGISTRY: gra.registry.ovh.net
  IMAGE_NAME: my-project/api

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

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

      - name: Build and push with layer caching
        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: Rolling deploy
        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

Performance Results: 6-Month Data

Metrics collected via kube-prometheus-stack over the first six months of production:

  • P99 latency: 85 ms on the main API (target: <200 ms) — significantly exceeded our goal
  • Uptime: 99.95% over 6 months — two incidents of 8 and 15 minutes caused by announced OVH network maintenance
  • Autoscaling: during a marketing campaign traffic spike, the cluster scaled from 3 to 8 nodes in 4 minutes 20 seconds
  • Average CPU utilisation: 34% at peak hours, 12% at night — autoscaler correctly returns to 3 nodes overnight

Pain Points and Limitations

We want to be honest about the limitations. OVH MKS is excellent for "pure Kubernetes" workloads, but several points deserve attention before migrating from AWS:

  • Smaller ecosystem than EKS: no equivalent to AWS Controllers for Kubernetes (ACK), no native SQS/DynamoDB/SNS integration. If your application heavily consumes AWS managed services, migration is costly.
  • Slower support response: critical OVH tickets are resolved in 4–8 hours versus 15–30 minutes with AWS Enterprise Support. For clients with strict SLAs, this is a potential blocker.
  • No managed NFS: OVH has no EFS equivalent. For ReadWriteMany volumes, we deployed a self-hosted NFS server on a dedicated instance — additional operational complexity.
  • Cluster Autoscaler is slower: provisioning a new node takes 3–5 minutes at OVH versus 1–2 minutes on EKS. For very sudden spikes, slightly over-provision the minimum node count.
  • No managed service mesh: no managed Istio; manual installation of Istio or Linkerd is required. We chose Linkerd for its lighter footprint.

Cost Comparison: OVH MKS vs AWS EKS

For an equivalent configuration (6 active nodes on average, load balancer, block storage, managed PostgreSQL):

Component OVH MKS AWS EKS equivalent
Control planeFree~€73/month
6 worker nodes (b3-8 / m5.2xlarge)~€840/month~€1,400/month
Load Balancer~€20/month~€55/month
Block storage (500 GB)~€50/month~€110/month
Managed PostgreSQL~€200/month~€460/month
Egress traffic (500 GB/month)Free~€45/month
Monthly total~€1,110~€2,143

Saving achieved: 48% — over €12,000 per year for this single project alone.

When to Choose OVH MKS vs AWS EKS

  • Choose OVH MKS when: data must remain in France/EU (GDPR, HDS, public sector), your stack is "pure Kubernetes" without dependencies on proprietary AWS services, cost control is a top priority, and your SLA tolerates somewhat slower support response times.
  • Choose AWS EKS when: your application heavily consumes AWS services (Lambda, SQS, DynamoDB, Bedrock), you need premium support with sub-1h response times, you rely on AWS-native tooling (ACK, IRSA, AWS Load Balancer Controller), or autoscaling speed is business-critical.

Conclusion

OVH MKS is a mature, competitive, and sovereign managed Kubernetes service. After twelve months in production, our verdict is clear: for French and European companies with regulatory constraints and cost sensitivity, it is a genuine AWS EKS alternative. The main trade-off is the narrower ecosystem — but if your architecture is designed for pure Kubernetes without native AWS integrations, this constraint is manageable. The cost savings — consistently 40% or more — can fund additional engineering resources to absorb the slightly higher operational overhead.

← Back to blog