Aller au contenu

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

23 min de lecture Read in English
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 Ingress et 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 :

  1. Otoroshi récupère toutes les ressources Gateway API depuis l’API server Kubernetes

  2. Il valide les ressources GatewayClass qui référencent son nom de contrôleur

  3. Il résout les certificats TLS à partir des Secrets Kubernetes référencés

  4. Il valide les listeners des Gateway par rapport aux ports qu’il a configurés

  5. Il convertit les objets HTTPRoute et GRPCRoute en entités NgRoute natives

  6. 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 :

  1. Provisionner un cluster Kubernetes sur CKE

  2. Ajouter des nœuds workers

  3. Déployer Otoroshi avec le support de la Gateway API activé

  4. Installer les CRD de la Gateway API

  5. Déployer une application de démonstration

  6. Y router du trafic via la Gateway API

  7. 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 clever installée et authentifiée (npm install -g clever-tools ou téléchargement depuis la page des releases)

  • kubectl installé

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-plan KubernetesGatewayApiControllerJob — c’est le composant qui surveille les ressources de la Gateway API et les réconcilie

  • "gatewayApi": true active le contrôleur Gateway API

  • "gatewayApiWatch": true active 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 au controllerName de votre ressource GatewayClass

  • "gatewayApiHttpListenerPort": [8080, 80] — indique à Otoroshi quelles valeurs de port sont valides pour les listeners HTTP des ressources Gateway. 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 :

  1. otoroshi-service (ClusterIP sur 8080/8443) — accès interne pour les autres pods du cluster

  2. 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: otoroshi relie 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 de demo-cke.io peut être routé par cette gateway

  • allowedRoutes.namespaces.from: All signifie que des ressources HTTPRoute de n’importe quel namespace peuvent se rattacher à cette gateway. En production, vous voudrez plutôt Same ou Selector avec 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 Gateway demo-otoroshi-gateway (remarque : comme la Gateway est dans le namespace demo et que la HTTPRoute est aussi dans demo, aucune autorisation de référence entre namespaces n’est nécessaire). sectionName: http rattache cette route spécifiquement au listener http de cette gateway.

  • hostnames — cette route ne s’applique qu’aux requêtes portant l’en-tête Host: whoami.demo-cke.io

  • rules — une seule règle : capter les requêtes dont le chemin commence par /v1 et les transmettre au service whoami sur 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 → le controllerName ne correspond pas

  • Gateway Listener: Programmed: False → le port indiqué dans la spec de la Gateway ne correspond pas à gatewayApiHttpListenerPort dans la configuration d’Otoroshi

  • HTTPRoute: Accepted: False → le parentRef est incorrect ou le namespace n’est pas autorisé par les allowedRoutes de 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.

Ressources

Tapez pour rechercher dans tous les articles.

↑ ↓ pour naviguer↵ pour ouvriresc pour fermerPropulsé par Pagefind