Exposer des applications Kubernetes avec Otoroshi et la Gateway API sur Clever Cloud

Il y a une satisfaction particulière quand on vient de mettre en place une nouvelle brique d’infrastructure et qu’elle fonctionne vraiment. Ce genre de satisfaction qui vous fait attraper votre café, vous caler au fond de votre chaise et simplement… respirer. C’est exactement ce que j’ai ressenti quand, après quelques heures de bricolage par un après-midi pluvieux, j’ai vu une commande curl renvoyer un beau HTTP 200 depuis une application tournant au fin fond d’un cluster Kubernetes — routée par l’intégration Gateway API d’Otoroshi, elle-même hébergée sur le moteur Kubernetes managé de Clever Cloud.
Je vous propose de refaire ce chemin avec moi dans cet article. Nous partirons du problème fondamental de l’exposition des workloads Kubernetes au monde extérieur, nous passerons par l’histoire des contrôleurs Ingress, jusqu’au nouveau standard de Kubernetes, la Gateway API — et nous déploierons toute la stack, étape par étape, sur Clever Kubernetes Engine (CKE), l’offre Kubernetes managée de Clever Cloud.
Servez-vous un café : l’article est long.
Le problème : votre application est prisonnière du cluster
Quand vous déployez une application sur Kubernetes, vous la placez dans un environnement très capable, mais fondamentalement isolé. Les pods Kubernetes vivent dans leur propre espace réseau privé. Des services comme ClusterIP rendent ces pods joignables à l’intérieur du cluster mais, par conception, ils ne sont pas accessibles depuis l’extérieur.
C’est d’ailleurs une fonctionnalité, pas un bug. Kubernetes vous offre un réseau privé et maîtrisé. Mais dès que votre application doit servir de vrais utilisateurs — ou dialoguer avec des systèmes externes —, il vous faut percer une ouverture contrôlée dans cette isolation.
Au fil des années, la communauté Kubernetes a développé plusieurs réponses à ce problème, chacune avec ses compromis.
Les débuts : NodePort et LoadBalancer
L’option la plus simple est un service NodePort. Il ouvre un port sur chaque nœud du cluster et redirige le trafic vers vos pods. Ça marche, mais c’est peu pratique : vous manipulez des numéros de port élevés (30 000 et au-delà), vous exposez directement l’IP de chaque nœud, et vous n’avez aucune capacité de gestion du trafic. NodePort est parfait pour des tests rapides en local, mais ce n’est pas quelque chose que vous feriez tourner en production.
L’étape suivante est un service LoadBalancer. Les fournisseurs cloud — Clever Cloud compris — savent provisionner un load balancer externe et le brancher automatiquement à vos pods. C’est un vrai progrès : vous obtenez une IP externe stable et des numéros de port propres. Mais si vous avez des dizaines de services à exposer, vous vous retrouvez avec des dizaines de load balancers, qui coûtent chacun de l’argent et demandent chacun leur configuration DNS. Ça ne passe pas bien à l’échelle.
Ce dont vous avez vraiment besoin, c’est d’un point d’entrée unique — un reverse proxy — capable de router intelligemment le trafic vers le bon service en fonction du nom d’hôte, du chemin, des en-têtes ou d’autres attributs de la requête. Et c’est exactement pour cela que la ressource Ingress a été créée.
Une brève histoire d’Ingress
Kubernetes a introduit la ressource Ingress vers 2015, comme moyen standardisé de définir des règles de routage HTTP. L’idée était élégante : vous écrivez un manifeste YAML qui dit « les requêtes vers api.example.com/v1 doivent aller au service my-api sur le port 8080 », et un contrôleur s’en saisit pour configurer le véritable reverse proxy.
Une ressource Ingress typique ressemble à ceci :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: default
spec:
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: my-api
port:
number: 8080
L’écosystème des contrôleurs Ingress
La spécification Ingress, à elle seule, ne fait rien : il vous faut aussi un contrôleur Ingress, un logiciel qui surveille les ressources Ingress et les traduit en configuration concrète du proxy. Nginx a été le choix le plus populaire. D’autres ont suivi : Traefik, HAProxy Ingress, Contour, et bien d’autres.
Le problème, c’est que la spécification Ingress a été volontairement gardée minimale. Le standard ne vous offrait que du routage basique par hôte et par chemin. Tout le reste — modes de terminaison TLS, rate limiting, authentification, retries, déploiements canary — devait passer par des annotations. Et comme les annotations ne sont que des chaînes arbitraires attachées aux ressources, chaque contrôleur avait son propre vocabulaire. Une annotation de rate limiting pour Nginx ne ressemble en rien à celle de Traefik. Passer d’un contrôleur à un autre impliquait de réécrire toutes vos ressources Ingress.
# nginx-specific annotation
nginx.ingress.kubernetes.io/limit-rps: "10"
# Same thing in Traefik's world
traefik.ingress.kubernetes.io/rate-limit: |
extractorfunc: client.ip
rateset:
default:
period: 1s
average: 10
Cette prolifération d’annotations était un point douloureux bien identifié par la communauté. L’API Ingress commençait à accuser son âge.
L’histoire de la dépréciation
La situation s’est clarifiée en 2023, quand les mainteneurs de Kubernetes ont annoncé la dépréciation du contrôleur historique kubernetes/ingress-nginx — non pas le projet Nginx Ingress Controller lui-même, mais celui maintenu par la communauté Kubernetes. Le projet avait accumulé une dette technique importante et la communauté a décidé qu’il était temps de passer à autre chose.
Plus largement, l’API Ingress elle-même était passée en mode maintenance. Plus aucune nouvelle fonctionnalité n’y était ajoutée. La communauté travaillait déjà sur quelque chose de mieux.
L’avenir est déjà là : la Gateway API de Kubernetes
La Gateway API de Kubernetes est le successeur moderne et standardisé d’Ingress. Elle a atteint la disponibilité générale (GA) fin 2023 et son adoption progresse régulièrement depuis.
La Gateway API a été conçue avec plusieurs objectifs clés en tête : être assez expressive pour couvrir des scénarios de routage avancés sans annotations, séparer clairement les responsabilités entre les différents rôles, et être extensible de façon maîtrisée.
Un modèle différent
Là où Ingress n’a qu’un seul type de ressource, la Gateway API introduit une hiérarchie de ressources :
GatewayClass — Définit quelle implémentation de contrôleur prend en charge une classe donnée de gateways. Voyez-la comme une StorageClass, mais pour le trafic plutôt que pour le stockage. Elle est généralement créée par l’équipe infrastructure ou par l’opérateur de la plateforme.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: my-gateway-class
spec:
controllerName: vendor.io/my-controller
Gateway — Une instance concrète de gateway, qui utilise une classe donnée. Elle définit les protocoles et les ports à écouter, ainsi que les noms d’hôte à prendre en charge. C’est la ressource créée par l’équipe propriétaire du cluster ou de l’infrastructure réseau.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: infra
spec:
gatewayClassName: my-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.example.com"
allowedRoutes:
namespaces:
from: All
HTTPRoute — Les règles de routage proprement dites, définies par les équipes applicatives dans leurs propres namespaces. C’est la partie qui intéresse les développeurs : « mon application est servie sur api.example.com/v2 ».
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: my-app
spec:
parentRefs:
- name: my-gateway
namespace: infra
sectionName: http
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: my-api-service
port: 8080
Ce modèle fondé sur les rôles est une amélioration significative. Les opérateurs du cluster contrôlent la Gateway (quels noms d’hôte sont autorisés, quels namespaces peuvent y rattacher des routes), tandis que les développeurs contrôlent leurs propres ressources HTTPRoute sans avoir besoin de privilèges au niveau du cluster. Les références entre namespaces sont possibles, avec des autorisations explicites via des ressources ReferenceGrant.
La Gateway API apporte aussi un support natif du découpage de trafic (backends pondérés pour les déploiements canary), de la manipulation d’en-têtes, des redirections, de la réécriture d’URL, et un vrai mécanisme d’extension via des types de filtres personnalisés — le tout sans recourir à des annotations propres à un contrôleur.
Faites connaissance avec Otoroshi
Avant de passer à la pratique, laissez-moi vous présenter le personnage principal de cette histoire : Otoroshi.
Otoroshi est un reverse proxy et une API gateway open source que j’ai créé en 2017, alors que je travaillais comme prestataire chez la MAIF, un grand assureur français. La MAIF venait de migrer sur la plateforme Clever Cloud et avait besoin d’une solution unifiée pour sécuriser et gérer le trafic des API de son paysage applicatif très hétérogène — sans imposer de bibliothèques ou de frameworks particuliers à ses équipes de développement. Le projet a depuis largement dépassé son périmètre d’origine, et j’en suis toujours aujourd’hui le core maintainer et le responsable, sous la bannière Cloud APIM.
La philosophie d’Otoroshi se résume en cinq principes : être agnostique vis-à-vis des technologies, HTTP-first, API-first (l’interface web n’est qu’un client de l’API parmi d’autres), centré sur la sécurité et orienté événements. Il est conçu pour être le point d’entrée unique de tout le trafic HTTP vers vos applications, quel que soit l’endroit où elles sont hébergées ou le langage dans lequel elles sont écrites.
Que peut faire Otoroshi ?
La liste des fonctionnalités est conséquente. Côté gestion du trafic : load balancing avec plusieurs stratégies (round robin, sessions persistantes, least connections, meilleur temps de réponse), rate limiting distribué, mirroring de trafic, déploiements canary et relay routing entre zones réseau.
Côté sécurité : un Web Application Firewall intégré, propulsé par Coraza avec les OWASP Core Rule Sets, des listes de blocage d’IP avec support CIDR, du contrôle d’accès fondé sur la géolocalisation, la gestion des clés d’API avec quotas, la validation de JWT (JWE compris), OAuth 2.0/2.1 avec PKCE, OIDC, LDAP, SAML V2, et même le support de WebAuthn/FIDO2.
Pour l’observabilité : métriques Prometheus natives, traces OpenTelemetry (OTLP), Datadog, StatsD, streaming d’événements vers Kafka, et plus encore.
Et il supporte HTTP/1.1, HTTP/2, HTTP/3, WebSocket, TCP et gRPC — y compris via un serveur nouvelle génération basé sur Netty pour les charges les plus exigeantes.
Otoroshi dans Kubernetes
Otoroshi dispose d’une intégration native avec Kubernetes depuis la version 1.5.0. En mode Kubernetes, Otoroshi fonctionne comme un contrôleur Ingress et peut s’appuyer sur plusieurs mécanismes :
-
Contrôleur Ingress standard : surveille les ressources
Ingresset crée les routes correspondantes -
Custom Resource Definitions (CRD) : un riche ensemble de plus de 23 ressources personnalisées qui vous permettent de configurer chaque aspect d’Otoroshi nativement via des manifestes Kubernetes — routes, backends, certificats, clés d’API, vérificateurs JWT, modules d’authentification, et plus encore
-
Synchronisation des secrets : les secrets TLS Kubernetes sont automatiquement synchronisés en certificats Otoroshi
Le contrôleur d’Otoroshi tourne comme un job d’arrière-plan au sein même du processus Otoroshi : il interroge ou surveille l’API Kubernetes et réconcilie l’état souhaité avec la configuration de routage courante. Pas besoin de déployer un contrôleur séparé.
Une remarque importante tirée de la documentation : faire tourner Otoroshi derrière un contrôleur Ingress existant est explicitement déconseillé. Cela dégrade les capacités de proxy TCP, le TLS et le mTLS, et ajoute une latence inutile. Otoroshi doit être votre point d’entrée le plus en amont.
Otoroshi rencontre la Gateway API
C’est là que les choses deviennent passionnantes. Depuis la version 17.13.0, Otoroshi implémente la spécification Gateway API de Kubernetes. C’est un chantier que je voulais attaquer depuis un moment, et le passage en GA de la spécification m’a semblé être le bon moment pour enfin le faire proprement.
La fonctionnalité est marquée comme expérimentale dans la documentation actuelle, mais elle est pleinement opérationnelle pour les cas d’usage essentiels : support de Gateway, HTTPRoute et GRPCRoute. L’implémentation repose sur un choix d’architecture intéressant : plutôt que de provisionner dynamiquement de nouveaux listeners à la demande (ce qui exigerait des privilèges importants sur le cluster et beaucoup de complexité), Otoroshi vérifie que les listeners des Gateway correspondent aux ports HTTP/HTTPS sur lesquels il écoute déjà, puis utilise les routes pour construire sa table de routage.
Le cycle de réconciliation fonctionne ainsi :
-
Otoroshi récupère toutes les ressources Gateway API depuis l’API server Kubernetes
-
Il valide les ressources
GatewayClassqui référencent son nom de contrôleur -
Il résout les certificats TLS à partir des Secrets Kubernetes référencés
-
Il valide les listeners des
Gatewaypar rapport aux ports qu’il a configurés -
Il convertit les objets
HTTPRouteetGRPCRouteen entitésNgRoutenatives -
Il enregistre les routes et nettoie les entrées orphelines laissées par les cycles de réconciliation précédents
Toutes les routes générées portent le tag otoroshi-provider: kubernetes-gateway-api, ce qui permet de les identifier et de les retrouver facilement.
Le contrôleur Gateway API s’active en déclarant KubernetesGatewayApiControllerJob comme job d’arrière-plan dans la configuration d’Otoroshi. Voyons précisément comment mettre tout cela en place.
Passons à la pratique : installation de bout en bout sur Clever Cloud
Je vais vous montrer exactement ce que j’ai fait cet après-midi-là. Nous allons :
-
Provisionner un cluster Kubernetes sur CKE
-
Ajouter des nœuds workers
-
Déployer Otoroshi avec le support de la Gateway API activé
-
Installer les CRD de la Gateway API
-
Déployer une application de démonstration
-
Y router du trafic via la Gateway API
-
Vérifier que tout fonctionne
Avant d’entrer dans le détail des étapes, voici une vue d’ensemble de ce que nous construisons. Deux plans coexistent : le control plane (flèches en pointillés), où Otoroshi surveille les ressources de l’API Kubernetes et les réconcilie en routes actives, et le data plane (flèches pleines), où le trafic HTTP réel circule du monde extérieur jusqu’aux pods de l’application.

Prérequis
Il vous faut :
-
Un compte Clever Cloud
-
La CLI
cleverinstallée et authentifiée (npm install -g clever-toolsou téléchargement depuis la page des releases) -
kubectlinstallé
Important — CKE est actuellement en accès privé. J’ai testé cette installation alors que Clever Kubernetes Engine n’était ouvert qu’à certains utilisateurs. À partir de la semaine prochaine (27 avril 2026), CKE passe en bêta publique, ce qui en élargit nettement l’accès pour les utilisateurs de Clever Cloud. Une fois la fonctionnalité activée pour votre organisation, le reste de ce guide s’applique tel quel.
Étape 1 : activer Kubernetes sur votre organisation Clever Cloud
Une fois CKE débloqué pour votre organisation, vous devez activer la fonctionnalité Kubernetes via la CLI :
clever features enable k8s
Vous pouvez lister les clusters existants avec :
clever k8s list --org xxx
Étape 2 : créer votre cluster
La création d’un nouveau cluster tient en une seule commande. Le flag --watch maintient la CLI active et affiche la progression pendant que le control plane démarre — une attention appréciable quand on est là à se demander si quelque chose a mal tourné.
clever k8s create gateway-api-demo --org xxx --watch
Au bout de quelques minutes, vous aurez un cluster opérationnel.
Vous pouvez relancer la commande list. La sortie affiche le statut du cluster et finit par confirmer que le control plane est prêt.
clever k8s list --org xxx

Vous remarquerez aussi qu’un nouvel élément est apparu dans votre console Clever Cloud

Étape 3 : récupérer votre kubeconfig
Une fois le cluster prêt, téléchargez le fichier kubeconfig :
clever k8s get-kubeconfig gateway-api-demo --org xxx > kubeconfig.yaml
Puis pointez kubectl dessus :
export KUBECONFIG="$(pwd)/kubeconfig.yaml"
Ou, si vous préférez, placez cet export dans un fichier config.sh et chargez-le avec source :
source config.sh
Vérifions que nous pouvons communiquer avec le cluster :
kubectl get nodes
À ce stade, vous ne verrez probablement que le nœud du control plane, voire rien du tout : nous n’avons encore ajouté aucun nœud worker.
Étape 4 : ajouter des nœuds workers
Par défaut, un cluster CKE démarre avec un control plane mais sans aucun nœud worker. Nous devons créer un NodeGroup — l’abstraction de Clever Cloud pour un groupe de nœuds de calcul, défini par un flavor (profil CPU/mémoire) et un nombre de nœuds.
Voici le manifeste que j’ai utilisé (manifests/nodegroup.yaml) :
apiVersion: api.clever-cloud.com/v1
kind: NodeGroup
metadata:
name: gateway-api-demo-nodegroup
spec:
flavor: M
nodeCount: 2
Le flavor M offre une bonne quantité de CPU et de RAM pour faire tourner Otoroshi confortablement. J’ai choisi 2 nœuds pour avoir de la marge et expérimenter sans me heurter à des limites de ressources.
Appliquez-le :
kubectl create -f ./manifests/nodegroup.yaml
Regardez maintenant les nœuds arriver :
kubectl get nodes
kubectl get nodegroups
![]()
Vous verrez les nœuds rejoindre le cluster un par un. Laissez passer quelques minutes. Dès que les 2 nœuds sont à l’état Ready, vous pouvez continuer.

Astuce : si vous avez besoin de plus de capacité par la suite, vous pouvez scaler le node group sans le recréer :
kubectl scale nodegroup gateway-api-demo-nodegroup --replicas=4
Étape 5 : installer les règles RBAC et les CRD d’Otoroshi pour la Gateway API
Otoroshi a besoin de permissions RBAC spécifiques pour surveiller et mettre à jour les ressources de la Gateway API — notamment gatewayclasses, gateways, httproutes, grpcroutes, referencegrants, backendtlspolicies, ainsi que les sous-ressources /status correspondantes.
Le projet Otoroshi fournit un manifeste RBAC prêt à l’emploi, dédié à l’intégration Gateway API :
kubectl apply -f 'https://raw.githubusercontent.com/MAIF/otoroshi/refs/heads/master/kubernetes/gateway-api/rbac-gateway.yaml'
Ainsi que les Custom Resource Definitions d’Otoroshi :
kubectl apply -f 'https://raw.githubusercontent.com/MAIF/otoroshi/refs/heads/master/kubernetes/gateway-api/crds-gateway.yaml'
Ces CRD vous permettent de configurer les ressources natives d’Otoroshi (routes, backends, clés d’API, etc.) via des manifestes Kubernetes. Même si nous nous concentrons aujourd’hui sur la Gateway API, les installer vous donne accès à l’intégration Kubernetes complète d’Otoroshi.
Étape 6 : déployer Otoroshi
C’est le gros morceau. Je vais détailler le manifeste de déploiement, car il contient plusieurs réglages importants.
Manifeste complet (manifests/otoroshi.yaml) :
kind: Namespace
apiVersion: v1
metadata:
name: otoroshi
---
kind: ServiceAccount
apiVersion: v1
metadata:
namespace: otoroshi
name: otoroshi-admin-user
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otoroshi-admin-user
namespace: otoroshi
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: otoroshi-admin-user
subjects:
- kind: ServiceAccount
name: otoroshi-admin-user
namespace: otoroshi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: otoroshi
namespace: otoroshi
spec:
replicas: 1
selector:
matchLabels:
app: otoroshi
template:
metadata:
labels:
app: otoroshi
spec:
serviceAccountName: otoroshi-admin-user
terminationGracePeriodSeconds: 60
containers:
- name: otoroshi
image: maif/otoroshi:latest
imagePullPolicy: Always
ports:
- containerPort: 10049
name: http
- containerPort: 10048
name: https
env:
- name: APP_STORAGE_ROOT
value: otoroshi
- name: APP_STORAGE
value: inmemory
- name: ADMIN_API_EXPOSED_SUBDOMAIN
value: otoroshi-api-5jsfzwhhx6unbywz
- name: ADMIN_API_TARGET_SUBDOMAIN
value: otoroshi-target-5jsfzwhhx6unbywz
- name: APP_BACKOFFICE_SUBDOMAIN
value: otoroshi-backoffice-5jsfzwhhx6unbywz
- name: APP_PRIVATEAPPS_SUBDOMAIN
value: otoroshi-privateapps-5jsfzwhhx6unbywz
- name: APP_DOMAIN
value: cleverk8s.io
- name: OTOROSHI_INITIAL_ADMIN_PASSWORD
value: password
- name: ADMIN_API_CLIENT_ID
value: admin-api-apikey-id
- name: ADMIN_API_CLIENT_SECRET
value: admin-api-apikey-secret
- name: ADMIN_API_ADDITIONAL_EXPOSED_DOMAIN
value: otoroshi-api.otoroshi.svc.cluster.local
- name: OTOROSHI_SECRET
value: veryveryveryveryverysecret
- name: OTOROSHI_EXPOSED_PORTS_HTTP
value: "80"
- name: OTOROSHI_EXPOSED_PORTS_HTTPS
value: "443"
- name: HEALTH_LIMIT
value: "5000"
- name: SSL_OUTSIDE_CLIENT_AUTH
value: Want
- name: HTTPS_WANT_CLIENT_AUTH
value: "true"
- name: OTOROSHI_NEXT_EXPERIMENTAL_NETTY_SERVER_ENABLED
value: "true"
- name: OTOROSHI_NEXT_EXPERIMENTAL_NETTY_SERVER_ACCESSLOG
value: "true"
- name: OTOROSHI_NEXT_EXPERIMENTAL_NETTY_SERVER_EXPOSED_HTTP_PORT
value: "80"
- name: OTOROSHI_NEXT_EXPERIMENTAL_NETTY_SERVER_EXPOSED_HTTPS_PORT
value: "443"
- name: OTOROSHI_INITIAL_CUSTOMIZATION
value: >
{
"config": {
"scripts": {
"enabled": true,
"jobRefs": [
"cp:otoroshi.plugins.jobs.kubernetes.KubernetesGatewayApiControllerJob"
],
"jobConfig": {
"KubernetesConfig": {
"trust": false,
"namespaces": ["*"],
"labels": {},
"namespacesLabels": {},
"defaultGroup": "default",
"ingresses": false,
"crds": true,
"kubeLeader": false,
"restartDependantDeployments": false,
"watch": false,
"syncIntervalSeconds": 60,
"otoroshiServiceName": "otoroshi-service",
"otoroshiNamespace": "otoroshi",
"clusterDomain": "cluster.local",
"gatewayApi": true,
"gatewayApiWatch": true,
"gatewayApiControllerName": "otoroshi.io/gateway-controller",
"gatewayApiHttpListenerPort": [8080, 80],
"gatewayApiHttpsListenerPort": [8443, 443],
"gatewayApiSyncIntervalSeconds": 30,
"gatewayApiAddresses": []
}
}
}
}
}
- name: JAVA_OPTS
value: '-Xms1g -Xmx2g -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0'
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 5
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: otoroshi-service
namespace: otoroshi
spec:
selector:
app: otoroshi
ports:
- port: 8080
name: "http"
targetPort: "http"
- port: 8443
name: "https"
targetPort: "https"
---
apiVersion: v1
kind: Service
metadata:
name: otoroshi-lb-external
namespace: otoroshi
spec:
type: LoadBalancer
selector:
app: otoroshi
ports:
- port: 80
name: "http"
targetPort: "http"
- port: 443
name: "https"
targetPort: "https"
Voici les principaux choix de configuration.
Stockage : nous utilisons ici APP_STORAGE=inmemory pour garder les choses simples et éviter de mettre en place un stockage de données externe pour cette démo. C’est le moyen le plus rapide de faire tourner Otoroshi : aucune dépendance, aucune configuration supplémentaire. Mais soyons clairs sur le compromis : avec le stockage en mémoire, toute la configuration d’Otoroshi disparaît dès que le pod redémarre, et vous ne pouvez compter que sur les CRD d’Otoroshi pour vos configurations. En production, il vous faut absolument un backend persistant.
Le choix naturel est Redis (APP_STORAGE=redis), utilisé par la plupart des déploiements d’Otoroshi en production. Si vous êtes déjà sur Clever Cloud, bonne nouvelle : des add-ons Redis sont disponibles directement sur la plateforme — il suffit d’en provisionner un et de brancher la chaîne de connexion. Au-delà de la persistance, un déploiement de production voudra aussi faire tourner plusieurs instances d’Otoroshi pour la haute disponibilité. Otoroshi propose un mode cluster dans lequel les instances partagent leur état via Redis, ce qui s’accorde naturellement avec l’offre Redis managée de Clever Cloud. Pour aller plus loin, l’opérateur Kubernetes de Clever Cloud vous permet de provisionner et de gérer les add-ons Clever Cloud (Redis compris) directement depuis des manifestes Kubernetes — idéal si vous voulez tout garder en GitOps.
Le serveur Netty : ces variables OTOROSHI_NEXT_EXPERIMENTAL_NETTY_SERVER_* activent le serveur HTTP nouvelle génération d’Otoroshi, basé sur Netty. Ce serveur gère correctement HTTP/1.1, HTTP/2 et HTTP/3, et il est indispensable pour le support de gRPC. Nous l’activons et l’associons aux ports 80 et 443.
La configuration de la Gateway API — c’est la partie critique — se trouve dans OTOROSHI_INITIAL_CUSTOMIZATION. Décortiquons-la :
{
"config": {
"scripts": {
"enabled": true,
"jobRefs": [
"cp:otoroshi.plugins.jobs.kubernetes.KubernetesGatewayApiControllerJob"
],
"jobConfig": {
"KubernetesConfig": {
"gatewayApi": true,
"gatewayApiWatch": true,
"gatewayApiControllerName": "otoroshi.io/gateway-controller",
"gatewayApiHttpListenerPort": [8080, 80],
"gatewayApiHttpsListenerPort": [8443, 443],
"gatewayApiSyncIntervalSeconds": 10
}
}
}
}
}
-
"jobRefs"active le job d’arrière-planKubernetesGatewayApiControllerJob— c’est le composant qui surveille les ressources de la Gateway API et les réconcilie -
"gatewayApi": trueactive le contrôleur Gateway API -
"gatewayApiWatch": trueactive la surveillance quasi temps réel des ressources Kubernetes (au lieu d’un simple polling à intervalle fixe) -
"gatewayApiControllerName": "otoroshi.io/gateway-controller"est l’identifiant du contrôleur — il doit correspondre exactement aucontrollerNamede votre ressourceGatewayClass -
"gatewayApiHttpListenerPort": [8080, 80]— indique à Otoroshi quelles valeurs de port sont valides pour les listeners HTTP des ressourcesGateway. Les Gateway qui référencent le port 8080 ou le port 80 seront acceptées -
"gatewayApiSyncIntervalSeconds": 10— avec le mode watch activé, c’est l’intervalle entre deux cycles de réconciliation forcée
Les deux Services : nous créons deux services pour Otoroshi :
-
otoroshi-service(ClusterIP sur 8080/8443) — accès interne pour les autres pods du cluster -
otoroshi-lb-external(LoadBalancer sur 80/443) — c’est lui qui est exposé au monde extérieur. Clever Cloud provisionne un load balancer externe et lui attribue une IP publique
Appliquez le déploiement :
kubectl apply -f manifests/otoroshi.yaml
Étape 7 : surveiller les logs
Déployer quelque chose de nouveau a son rituel : on attend, et on regarde les logs. Suivons le démarrage d’Otoroshi :
kubectl -n otoroshi logs -f deploy/otoroshi --tail=200
Vous verrez Otoroshi démarrer, initialiser son stockage, lancer les jobs de synchronisation Kubernetes, et finir par afficher quelque chose comme :
[INFO] [KubernetesGatewayApiControllerJob] Gateway API controller started, watching for GatewayClass resources with controllerName: otoroshi.io/gateway-controller
C’est le signal que vous attendiez. Le contrôleur Gateway API tourne et il est prêt à prendre en charge les ressources.
Pendant qu’Otoroshi devient opérationnel, vous pouvez aussi consulter le service load balancer pour connaître l’IP externe qui lui a été attribuée :
kubectl -n otoroshi get service otoroshi-lb-external
La colonne EXTERNAL-IP affichera d’abord <pending>. Une fois le load balancer provisionné par Clever Cloud, elle se remplira avec une véritable adresse IP. Notez cette IP : vous en aurez besoin pour la configuration DNS et pour les tests.
kubectl -n otoroshi get service otoroshi-lb-external
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
otoroshi-lb-external LoadBalancer 10.x.x.x <LOAD_BALANCER_IP> 80:..., 443:...
Étape 8 : installer les CRD de la Gateway API
La Gateway API n’est pas intégrée par défaut à Kubernetes : elle est distribuée sous forme de CRD séparées. Nous devons les installer avant de pouvoir créer des ressources GatewayClass, Gateway ou HTTPRoute.
L’équipe SIG Network maintient les artefacts de release officiels. Nous utilisons la v1.4.0 :
kubectl apply -f 'https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml'
Cela installe les CRD du canal stable : GatewayClass, Gateway, HTTPRoute, GRPCRoute, ReferenceGrant, et quelques autres.
Vérifiez qu’elles sont bien en place :
kubectl get crds | grep gateway.networking.k8s.io
Vous devriez voir des entrées pour gatewayclasses.gateway.networking.k8s.io, gateways.gateway.networking.k8s.io, httproutes.gateway.networking.k8s.io, et ainsi de suite.
Étape 9 : déployer la configuration de la Gateway
C’est maintenant que ça devient amusant. Nous devons créer deux ressources : une GatewayClass qui dit à Kubernetes « c’est Otoroshi qui s’en charge », et une Gateway qui définit notre configuration d’écoute.
Voici le manifeste (manifests/gateway.yaml) :
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: otoroshi
spec:
controllerName: otoroshi.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-otoroshi-gateway
namespace: otoroshi
spec:
gatewayClassName: otoroshi
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.demo-cke.io"
allowedRoutes:
namespaces:
from: All
Voyons cela de plus près :
GatewayClass : le controllerName — ici otoroshi.io/gateway-controller — doit correspondre exactement au gatewayApiControllerName que nous avons configuré dans les variables d’environnement d’Otoroshi. C’est ainsi qu’Otoroshi revendique la gestion de cette GatewayClass.
Gateway :
-
gatewayClassName: otoroshirelie cette Gateway à notre GatewayClass -
Le listener écoute sur le port 80 (HTTP), qui correspond à l’une de nos valeurs
gatewayApiHttpListenerPort -
hostname: "*.demo-cke.io"est un wildcard : n’importe quel sous-domaine dedemo-cke.iopeut être routé par cette gateway -
allowedRoutes.namespaces.from: Allsignifie que des ressources HTTPRoute de n’importe quel namespace peuvent se rattacher à cette gateway. En production, vous voudrez plutôtSameouSelectoravec des labels spécifiques
Appliquez-le :
kubectl apply -f manifests/gateway.yaml
Quelques secondes après l’avoir appliqué (le mode watch de la Gateway API réagit très vite avec gatewayApiSyncIntervalSeconds: 2), vous pouvez vérifier que la GatewayClass a bien été acceptée :
kubectl get gatewayclass otoroshi -o yaml
Regardez la section status :
status:
conditions:
- lastTransitionTime: "..."
message: GatewayClass accepted
reason: Accepted
status: "True"
type: Accepted
Accepted: True signifie qu’Otoroshi a reconnu la GatewayClass et qu’il la gère. Si vous voyez False, vérifiez que le controllerName correspond exactement : il est sensible à la casse.
Étape 10 : déployer une application de démo et une HTTPRoute
Déployons maintenant quelque chose vers quoi router réellement du trafic. J’utilise traefik/whoami, un petit serveur web en Go qui renvoie des informations sur la requête HTTP qu’il a reçue. Parfait pour le débogage et les démos.
Voici le manifeste complet (manifests/routes.yaml) :
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
namespace: demo
labels:
app: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami:latest
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 1
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: demo
labels:
app: whoami
spec:
type: ClusterIP
selector:
app: whoami
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-route
namespace: demo
spec:
parentRefs:
- name: demo-otoroshi-gateway
namespace: otoroshi
sectionName: http
hostnames:
- "whoami.demo-cke.io"
rules:
- matches:
- path:
type: PathPrefix
value: /v1
backendRefs:
- name: whoami
port: 80
weight: 1
La HTTPRoute est particulièrement intéressante. Passons-la en revue :
-
parentRefs— référence notre Gatewaydemo-otoroshi-gateway(remarque : comme la Gateway est dans le namespacedemoet que la HTTPRoute est aussi dansdemo, aucune autorisation de référence entre namespaces n’est nécessaire).sectionName: httprattache cette route spécifiquement au listenerhttpde cette gateway. -
hostnames— cette route ne s’applique qu’aux requêtes portant l’en-têteHost: whoami.demo-cke.io -
rules— une seule règle : capter les requêtes dont le chemin commence par/v1et les transmettre au servicewhoamisur le port 80
Appliquez-le :
kubectl apply -f manifests/routes.yaml
Après quelques instants, vérifiez le statut de la HTTPRoute :
kubectl get httproute my-route -n demo -o yaml
Le statut devrait afficher :
status:
parents:
- conditions:
- lastTransitionTime: "..."
message: Route accepted
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "..."
message: All references resolved
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
parentRef:
name: demo-otoroshi-gateway
sectionName: http
Accepted: True et ResolvedRefs: True : toutes les références de backend sont valides et la route a été acceptée par la gateway.
Étape 11 : configuration DNS
Il faut que whoami.demo-cke.io résolve vers l’IP externe de notre load balancer. Dans une installation réelle, vous ajouteriez un enregistrement DNS A (ou un enregistrement wildcard *.demo-cke.io) pointant vers cette IP.
Récupérez l’IP de votre load balancer :
kubectl -n otoroshi get service otoroshi-lb-external
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
otoroshi-lb-external LoadBalancer 10.x.x.x <LOAD_BALANCER_IP> 80:..., 443:...
Pour cette démo, plutôt que de configurer un vrai DNS, nous pouvons utiliser l’option --resolve de curl pour associer manuellement le nom d’hôte à l’IP — ce qui simule le travail du DNS.
Étape 12 : l’heure de vérité
curl -v --resolve whoami.demo-cke.io:80:<LOAD_BALANCER_IP> http://whoami.demo-cke.io/v1
Si tout est correctement branché, vous verrez quelque chose comme :
* Trying <LOAD_BALANCER_IP>:80...
* Connected to whoami.demo-cke.io (<LOAD_BALANCER_IP>) port 80
> GET /v1 HTTP/1.1
> Host: whoami.demo-cke.io
> User-Agent: curl/8.x.x
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
<
Hostname: whoami-xxxxxxxxx-xxxxx
IP: 127.0.0.1
IP: ::1
IP: 10.x.x.x
RemoteAddr: 10.x.x.x:xxxx
GET /v1 HTTP/1.1
Host: whoami.demo-cke.io
User-Agent: curl/8.x.x
Accept: */*
X-Forwarded-For: x.x.x.x
X-Forwarded-Host: whoami.demo-cke.io
X-Forwarded-Proto: http
X-Real-Ip: x.x.x.x
Un beau HTTP 200. Le trafic est passé de curl → load balancer Clever Cloud → Otoroshi (qui route selon la HTTPRoute que nous avons définie) → le pod whoami du namespace demo.
Remarquez les en-têtes X-Forwarded-For, X-Forwarded-Host et X-Real-Ip injectés par Otoroshi. C’est transparent pour le service backend, mais entièrement géré au niveau de la gateway.
Conseils de dépannage
Si quelque chose ne fonctionne pas, voici ma checklist de débogage :
Consultez les logs d’Otoroshi :
kubectl -n otoroshi logs -f deploy/otoroshi --tail=200
Cherchez les lignes qui mentionnent KubernetesGatewayApiControllerJob, GatewayClass ou HTTPRoute. Toute erreur de réconciliation apparaîtra ici.
Vérifiez le statut des ressources :
kubectl get gatewayclass otoroshi -o yaml
kubectl get gateway demo-otoroshi-gateway -n demo -o yaml
kubectl get httproute my-route -n demo -o yaml
Les champs status.conditions vous indiquent précisément ce qui ne va pas. Problèmes fréquents :
-
GatewayClass: Accepted: False→ lecontrollerNamene correspond pas -
Gateway Listener: Programmed: False→ le port indiqué dans la spec de la Gateway ne correspond pas àgatewayApiHttpListenerPortdans la configuration d’Otoroshi -
HTTPRoute: Accepted: False→ leparentRefest incorrect ou le namespace n’est pas autorisé par lesallowedRoutesde la Gateway
Vérifiez les routes générées dans Otoroshi :
curl -s http://<otoroshi-service-ip>:8080/api/routes \
-u admin-api-apikey-id:admin-api-apikey-secret | \
jq '.[] | select(.metadata["otoroshi-provider"] == "kubernetes-gateway-api") | {id, name}'
Si la route a été générée, elle apparaîtra ici. Si la liste est vide, c’est que la réconciliation n’a pas encore eu lieu ou qu’elle a rencontré une erreur.
Vérifiez que le pod backend tourne :
kubectl get pods -n demo
kubectl get service whoami -n demo
Si le pod n’est pas prêt ou si le sélecteur du service ne correspond pas aux pods, vous obtiendrez des 503.
Aller plus loin : que peut-on faire d’autre ?
Ce que je vous ai montré ici, ce sont les bases : router du trafic HTTP d’un nom d’hôte et d’un chemin vers un service backend. Mais le support de la Gateway API dans Otoroshi va bien plus loin.
Découpage du trafic pour les déploiements canary
Le champ weight de backendRefs vous permet de répartir le trafic entre plusieurs versions d’un service :
rules:
- backendRefs:
- name: my-service-v1
port: 80
weight: 90
- name: my-service-v2
port: 80
weight: 10
Cela envoie 90 % du trafic vers la v1 et 10 % vers la v2 — un schéma classique de déploiement canary, entièrement configuré de façon déclarative.
Manipulation des en-têtes
Des filtres intégrés vous permettent d’ajouter, de modifier ou de supprimer des en-têtes de requête et de réponse :
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Request-Source
value: gateway
remove:
- X-Internal-Secret
backendRefs:
- name: my-service
port: 80
Redirections et réécritures d’URL
rules:
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
Étendre avec les plugins Otoroshi
C’est là que ça devient vraiment puissant. Vous pouvez brancher le système de plugins d’Otoroshi sur les routes via des annotations :
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-secure-route
namespace: demo
annotations:
proxy.otoroshi.io/route-plugins: |
[
{
"plugin": "cp:otoroshi.next.plugins.ApikeyCalls",
"enabled": true,
"config": {}
}
]
spec:
# ... rest of the HTTPRoute
Cette annotation impose l’authentification par clé d’API sur la route — en s’appuyant sur tout l’écosystème de plugins d’Otoroshi, accessible via une simple annotation Kubernetes standard. Vous pouvez ajouter de cette façon du rate limiting, de la validation JWT, des règles WAF, et n’importe lequel des plus de 200 plugins d’Otoroshi.
Routage entre namespaces
Si votre HTTPRoute se trouve dans un namespace différent de celui du Service backend, il vous faut un ReferenceGrant dans le namespace cible :
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-from-frontend
namespace: backend
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: frontend
to:
- group: ""
kind: Service
Terminaison HTTPS/TLS
Pour terminer le TLS au niveau d’Otoroshi, référencez un secret TLS Kubernetes dans le listener de votre Gateway :
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: my-tls-secret
allowedRoutes:
namespaces:
from: All
Otoroshi importera le certificat depuis le Secret et prendra en charge la terminaison TLS pour toutes les routes rattachées à ce listener.
En conclusion
C’est une installation qui fonctionne vraiment, et qui fonctionne bien.
CKE vous fournit un cluster Kubernetes prêt pour la production en quelques minutes. La commande clever k8s create, l’abstraction NodeGroup pour les pools de workers, le provisionnement automatique du load balancer : c’est rapide, propre, et il y a très peu de friction entre « je veux un cluster » et « j’ai un cluster ». Si vous utilisez déjà Clever Cloud pour vos autres workloads, CKE s’intègre directement dans votre workflow existant, sans la moindre surprise.
L’implémentation de la Gateway API dans Otoroshi tient exactement ses promesses : une réconciliation rapide (les changements se propagent en quelques secondes avec gatewayApiWatch: true), des mises à jour de statut précises sur les ressources GatewayClass, Gateway et HTTPRoute, qui simplifient le débogage, et un accès complet à l’écosystème de plugins d’Otoroshi via les annotations. Toute la surface de routage, de sécurité et de gestion du trafic d’Otoroshi — plus de 200 plugins — est disponible depuis des manifestes Kubernetes standard. L’implémentation fonctionne exactement comme prévu.
Toute la stack s’assemble proprement : quelques commandes kubectl, une poignée de manifestes YAML, et vous disposez d’une API gateway pleinement fonctionnelle, intégrée au standard Gateway API de Kubernetes. Si vous connaissez déjà Otoroshi — ce qui est fort possible si vous utilisez Clever Cloud, puisqu’il y est disponible en add-on —, la courbe d’apprentissage est quasi nulle. Vous exprimez simplement ce qu’Otoroshi sait déjà faire, à la manière standard de Kubernetes.
L’ère des ressources Ingress surchargées d’annotations et de l’enfermement propre à chaque contrôleur touche à sa fin. La Gateway API est la voie à suivre et, avec Otoroshi sur CKE, le chemin pour y arriver est tout tracé.
Si vous avez des questions, si vous rencontrez des problèmes ou si vous voulez partager ce que vous construisez avec cette stack, contactez-nous chez Cloud APIM. Nous sommes toujours ravis de parler réseau Kubernetes, API gateways et de tout ce qui touche à Otoroshi.


