Considérations pour une Gateway partagée

Dans la continuité des articles précédents sur la Kubernetes Gateway API, nous explorons aujourd’hui un aspect plus opérationnel de ce produit, en considérant nos options pour une vision partagée de la fonctionnalité.
En effet, jusqu’ici, nous avons créé des Gateways selon nos besoins, et nous avons pu voir qu’il était facile d’ajouter des instances de celles-ci.
Toutefois, comme pour tout objet facile à déployer, il peut être souhaitable d’en limiter le nombre.
C’est à ceci que nous allons réfléchir dans cet article.
L’agenda :
- Scénario pour une Gateway partagée
- Configurer des HTTPRoutes et des Gateways pour plusieurs namespaces
- Configurer l’usage de Secrets pour les Gateways sur plusieurs namespaces
- Conclusion
1. Scénario pour une Gateway partagée
1.1. Réflexion sur le besoin d’une Gateway partagée
Jusqu’ici, comme nous l’avons évoqué en introduction, nous avons eu une approche distribuée dans l’usage de la Gateway API. C’est-à-dire que pour chaque périmètre, comme par exemple une application, nous avions une instance de Gateway dédiée, à laquelle étaient associées des HTTPRoutes, selon le besoin d’exposition de l’application.
Bien que cela fonctionne sans problème, cette approche distribuée ne capitalise pas sur le modèle Role-Based de la Gateway API, et donne probablement trop de responsabilités à une équipe qui gèrerait uniquement son application.

Si l’on part du postulat que le namespace représente une frontière de sécurité une fois les configurations RBAC souhaitées appliquées, déployer aussi bien la Gateway que la HTTPRoute n’est probablement pas souhaitable d’un point de vue sécurité. Après tout, la possibilité de créer une Gateway dans un Cloud-managed Kubernetes est équivalente à pouvoir créer un service exposé sur Internet.
Du point de vue de la GatewayClass, par sa nature, la ségrégation par namespace n’aura pas d’impact sur la capacité d’une équipe à utiliser une classe plutôt qu’une autre.

En revanche, la Gateway est une ressource dite namespaced, et dans une approche segragation of duties, il fait sens de déployer celle-ci dans un namespace distinct de l’application, dont le management serait confié à une équipe différente, typiquement une équipe d’opérateurs de cluster, vs des équipes responsables d’applications, comme indiqué sur le schéma.
On peut imaginer une organisation des ressources de la Gateway API comme ci-dessous :
- Un namespace, géré par des opérateurs de cluster, contenant la Gateway partagée, exposée sur le réseau externe.
- Des namespaces gérés par des responsables d’applications, contenant les ressources associées à l’application, et notamment la HTTPRoute requise pour l’exposition.
Rappelons que nous pouvons ajouter des annotations applicables sur le Service sous-jacent à la Gateway pour faire de celle-ci une Internal Gateway.

En prenant ce point en compte, on pourrait envisager la mise en place d’admission controller qui contrôleraient ou forceraient l’application de la section spec.infrastructure.annotations sur une instance de Gateway. Mais nous garderons ce sujet pour un autre article.
Enfin, à propos de l’usage de TLS, ce qui est absolument un prérequis pour le monde réel, nous pouvons voir que le certificat est référencé au niveau de la Gateway.

On remarque au passage l’existence d’un paramètre namespace dans la section certificateRegs, ce qui nous permet également d’envisager l’usage d’un namespace dédié pour les Secrets associés aux certificats permettant la configuration TLS. Auquel cas, il s’agirait encore d’un namespace géré par une équipe d’opérateurs de cluster, voire d’une équipe SecOps en charge de la gestion des certificats.
1.2. Un mot sur l’environnement de lab
Avant de nous plonger dans le cœur de notre sujet, un mot sur l’environnement de lab utilisé dans cet article.
En lieu et place d’un cluste AKS, nous utiliserons un kubernetes single node de type kubeadm, créé à l’aide de Vagrant.
Le VagrantFile est défini comme ci-dessous.

Un repo Github est disponible pour les « jusqu’auboutistes » qui voudraient manipuler eux-mêmes.
La GatewayClass dédiée suivante est créée.

Avec la CRD Cilium associée pour permettre l’usage d’un service NodePort, en l’absence d’un LoadBalancer disponible dans notre environnement local.

Pour simuler une application, nous utilisons un Deployment basé sur nginx, customisé avec une Configmap dans la configuration du Pod.

Exposer cette application, avec une approche distribuée nécessiterait la création d’une Gateway

et d’une HTTPRoute

Puisque nous sommes sur un cluster single-node, nous nous appuyons sur la gateway-class-config pour faire de notre Service sous-jacent à la Gateway un NodePort.

Si la Gateway est bien configurée pour un accès externe, nous obtenons un résultat similaire avec la commande curl.

Cependant, nous ne sommes pas à ce stade dans une approche partagée.
2. Configurer des HTTPRoutes et des Gateways sur plusieurs namespaces
2.1. Observation des propriétés de la Gateway
Pour commencer, étudions un peu les spécifications de la Gateway API.
L’on peut trouver dans la description de spec.listeners un paramètre appelé allowedRoutes, qui contient un champ namespaces. Sa valeur par défaut, comme affiché dans le tableau ci-après est Same, ce qui signifie donc que les HTTPRoutes acceptées par une Gateway par défaut sont celles dans le même namespace.
| Field | Description | Default |
| namespaces | Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. | { from:Same } |
En suivant les liens de la documentation, on trouve ensuite les valeurs acceptées pour ce champ namespaces.
| Field | Description |
| All | Routes/ListenerSets in all namespaces may be attached to this Gateway. |
| Selector | Only Routes/ListenerSets in namespaces selected by the selector may be attached to this Gateway. |
| Same | Only Routes/ListenerSets in the same namespace as the Gateway may be attached to this Gateway. |
| None | No Routes/ListenerSets may be attached to this Gateway. |
Ceci étant vu, créons à présent une nouvelle Gateway, dans son propre namespace.

On obtient les objets suivants après création.

En mettant à jour notre HTTPRoute créée précédemment, de la manière suivante :

On obtient le statut suivant.

Ce qui est prévisible, puisqu’à ce stade, nous n’avons pas modifié la valeur par défaut du paramètre allowedRoutes. Modifions notre Gateway pour qu’elle accepte les HTTPRoutes depuis tous les namespaces.

Une fois la modification faite, le statut de notre HTTPRoute passe à Accepted.

Et une commande curl nous permet d’accéder à notre Service derrière la Gateway.

Toutefois, autoriser tous les namespaces est peut-être un peu trop. Voyons comment utiliser le paramètre Selector afin d’être plus sélectif sur les namespaces cibles.
Le namespace gundam contient les labels ci-après.

Nous pouvons donc modifier notre Gateway comme ci-après pour cibler uniquement ce namespace.

Le statut de notre HTTProute ne change pas, puisqu’elle remplit toujours les conditions requises par la Gateway.
Ajoutons une nouvelle application, dans un autre namespace appelé demoapp.

Ainsi que sa HTTPRoute associée.

Le statut nous indique ce que l‘on attend, à savoir que la HTTPRoute n’est pas acceptée en l’état par la Gateway.

Pour configurer la Gateway pour accepter une liste de namespaces, on doit comprendre que par défaut, le champ matchLabels utilisé agit comme un AND. Ce qui signifie qu’ajouter d’autres namespaces comme ci-après indique à la Gateway de n’accepter que les HTTPRoutes dont les namespaces ont tous les labels spécifiés. Ce qui n’est pas possible dans notre cas, où nous utilisons un label reprenant le nom de chaque namespace.
Pour utiliser l’équivalent du OR dans un yaml kubernetes, nous utilisons la syntaxe suivante.

De cette manière, nous pouvons sélectionner plusieurs namespaces en nous appuyant sur le label kubernetes.io/metadata.name et en spécifiant une liste de valeurs acceptées.
Nous savons à présent comment gérer une Gateway partagée. Passons maintenant à la gestion du TLS.
3. Configurer l’usage de Secrets pour les Gateways sur plusieurs namespaces
Dans le cadre de la Gateway API, le certificat est géré au niveau de la Gateway. Le champ listerneters[].protocol doit être configuré avec la valeur HTTPS et le listeners[].port avec 443.
De plus, la section tls contient les informations relatives au certificat.
Une Gateway configurée avec un listener utilisant tls avec un Secret ressemble à ceci.

Référencer le Secret de cette manière implique que celui-ci est dans le même namespace que la Gateway, comme indiqué dans l’extrait de la documentation ci-après.
| Field | Description | Default |
| group | Group is the group of the referent. For example, « gateway.networking.k8s.io ». When unspecified or empty string, core API group is inferred. | |
| kind | Kind is kind of the referent. For example « Secret ». | Secret |
| name | Name is the name of the referent. | |
| namespace | Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred. Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace’s owner to accept the reference. See the `ReferenceGrant` documentation for details. |
Pour réaliser nos tests, nous allons créer un namespace supplémentaire, et recréer le Secret associé à notre certificat dans ce même namespace.

Puis nous ajoutons le champ namespace dans le listener de la Gateway.

En regardant le statut de notre HTTPRoute, nous voyons que la référence à notre certificat n’est pas autorisée.

Ce à quoi nous nous attendions. Il nous faut utiliser un autre objet de la Gateway API : le ReferenceGrant.
Cet objet permet de spécifier quels objets peuvent faire références à quels autres objets. Concrètement, quels Secrets une Gateway peut utiliser.
Dans notre cas, on ajoute donc un ReferenceGrant dans le namespace de notre Secret, et indiquons que celui-ci peut être référencé par la Gateway dans le namespace ciliumgateway.

Une commande curl nous permettra à présent d’atteindre avec succès nos applications exposées derrière notre nouvelle Gateway partagée, avec un certificat référencé dans un Secret.


Il est à présent temps de conclure.
Conclusion
Toujours dans l’usage de la Gateway API, nous avons cette fois ci exploré la mise en œuvre et l’usage d’une Gateway partagée.
Il ressort que l’on peut sans trop de difficultés définir quels namespaces peuvent être autorisés à utiliser une Gateway.
Il est également possible de séparer la gestion des Secrets utilisés pour les certificats et le trafic TLS en s’appuyant sur un ReferenceGrant.
L’ensemble de ces fonctionnalités étant en GA dans la Gateway API, il reste à présent à sélectionner un provider de Gateway API pour pouvoir mettre en œuvre tout ceci et sortir de l’Ingress Controller.