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 plane | Free | ~€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.
