Aller au contenu

Architecture

Ce document explique les choix et leur raison. Pour l'inventaire des ressources Scaleway, les ports et les couts : docs/infrastructure.md. Pour le cycle de vie et les pannes connues : docs/operations.md. Pour le filtre de challenge : docs/proxy.md.

Chaine de trafic

Internet
   |
   |  DNS : edge.exemple.fr -> IP publique du worker Elastic Metal
   v
+-----------------------------------------------------------+
| Worker Elastic Metal (EM-A116X-SSD, fr-par-1)             |
|                                                           |
|  Cilium en mode NodePort (plage restreinte 80,443)         |
|      |                                                    |
|      v                                                    |
|  Passerelle Istio (Gateway API, GatewayClass "istio")     |
|      |  TLS termine, cert-manager fournit le certificat    |
|      |                                                    |
|      |  [filtre de challenge, si actif]                    |
|      |  403 + page de preuve de travail, puis clearance    |
|      v                                                    |
|  HTTPRoute -> Service -> origines                          |
+-----------------------------------------------------------+
   ^                                    ^
   |  UDP 51871 (maillage WireGuard)    |  etcd 2379/2380
   |  TCP 10250, 6443                   |  API 6443
   |                                    |
+----------------------------------+  |
| 3 x instances Scaleway           |--+
| control plane Talos              |
| (etcd, kube-apiserver, istiod)   |
| pare-feu Scaleway restrictif     |
+----------------------------------+

Le trafic du control plane est en mTLS, genere par Talos : etcd, kube-apiserver, kubelet et apid. Ce qui n'est pas chiffre par defaut, c'est le trafic pod-a-pod : c'est le role de l'encryption WireGuard de Cilium.

Pourquoi le filtre de challenge est dans le proxy du bord

Le filtre est un module Proxy-WASM charge par le proxy d'Istio, pas un service a part. Trois consequences voulues :

  • L'origine ne voit jamais le trafic non qualifie. Le 403 et la page de challenge partent du bord, avant le routage vers l'origine. Un robot qui ne resout pas la preuve consomme du calcul du bord, mais aucune ressource de l'application.
  • Pas de saut reseau supplementaire. Le filtre tourne dans le processus du proxy : il n'ajoute ni appel reseau, ni latence de service tiers, ni piece logicielle de plus a surveiller.
  • Le filtre suit le sort de la passerelle. Il se pose par une ressource WasmPlugin, donc il est declare, versionne et detruit avec le reste de la couche de bord.

Deux choix a connaitre. La phase est AUTHN, donc le filtre s'execute avant l'authentification : un robot qui ne paie pas sa preuve de travail est arrete tot, et l'application peut poser ses propres controles derriere sans les voir contournes. Et failStrategy vaut FAIL_OPEN : si le module panique ou ne se charge pas, le trafic passe. Pour un filtre anti-robot, c'est le bon compromis, parce qu'une panne du filtre ne doit pas devenir une panne de service. Le prix est qu'un chargement rate se traduit par une protection absente et non par une alerte ; la sante du filtre se surveille donc a part.

Composants et versions

Tout est pinne, et chaque version est declaree a un seul endroit. C'est ce qui rend un apply reproductible et une montee de version intentionnelle.

Infrastructure et systeme (tofu/infra, tofu/platform)

  • Talos v1.13.11, blocage explique dans docs/infrastructure.md.
  • Kubernetes : la version que Talos embarque, kubernetes_version = null par defaut, donc pas de divergence possible entre l'image et le cluster.
  • OpenTofu >= 1.9 ; providers : scaleway 2.87.0, talos 0.12.0, alekc/kubectl 2.4.1, hashicorp/helm, hashicorp/random, hashicorp/local, hashicorp/null.

Composants du cluster (tofu/platform/variables.tf)

  • Cilium 1.20.3, en chart Helm, valeurs dans values/cilium.yaml.tftpl.
  • CRD de l'API Gateway v1.4.0, bundle versionne dans k8s/crds/gateway-api-v1.4.0.yaml et jamais telecharge a l'apply.
  • cert-manager v1.21.2, installe avec le support de l'API Gateway pour que le defi HTTP-01 passe par un HTTPRoute.
  • Istio 1.30.6, en deux temps : istio/base puis istiod.

Bord (tofu/edge)

  • GatewayClass istio, declaree a la main : ni istio/base ni istiod ne la creent, verifie sur 1.30.6. Sans elle, un Gateway est ignore en silence.
  • ClusterIssuer Let's Encrypt, serveur staging par defaut : c'est volontaire, pour ne pas consommer de quota de production pendant les essais. A basculer en production avant toute mise en service.
  • Filtre de challenge Proxy-WASM, facultatif, pow_enabled = false.

Trois couches, trois states

tofu/infra      machines Scaleway + cluster Talos         (API Scaleway, talosctl)
tofu/platform   CNI, CRD Gateway API, cert-manager, Istio (API Kubernetes)
tofu/edge       passerelle, routes, exposition            (API Kubernetes)

Le decoupage repond a un probleme concret : une ressource dont le CRD est cree dans le meme cycle ne peut pas etre declaree avec kubernetes_manifest, qui resout le schema OpenAPI au moment du plan. La couche plateforme installe donc les CRD avec alekc/kubectl, et la couche edge declare les objets qui s'en servent, sur un cluster ou ces types existent deja.

Deux consequences utiles : un tofu apply sur les add-ons ne touche jamais aux serveurs, et la couche edge se rejoue seule quand on itere sur les noms, les routes ou les certificats. Aucune couche ne lit le state d'une autre : l'infra ecrit talos/_out/platform-inputs.json, les deux autres le lisent.

Ce qui reste imperatif, et pourquoi

Trois operations n'ont pas d'equivalent declaratif :

  • le dd de l'image Talos sur l'Elastic Metal, parce que Scaleway n'accepte qu'un os-id de son catalogue et refuse toute image personnalisee ;
  • le telechargement de l'image qcow2, plusieurs centaines de Mo, qui n'a rien a faire dans un state ;
  • la premiere application de la configuration Talos, parce qu'un noeud en mode maintenance a un certificat auto-signe et que le provider n'expose pas --insecure.

Chacune est isolee dans un null_resource dont les declencheurs sont des hachages, donc rejouable sans effet de bord. Tout le reste est dans le state.

Pourquoi Talos plutot qu'une distribution classique

Aucun acces shell, image immuable, configuration declarative par API. Sur un point de presence exposes a Internet, la surface d'attaque du systeme d'exploitation est plus faible, et surtout il n'y a rien a configurer a la main qui puisse diverger du depot. Les patchs dans talos/patches/ sont partages entre OpenTofu et talosctl : une seule source de verite.

Pourquoi Cilium plutot qu'un CNI plus le kube-proxy

Trois fonctions sont demandees au meme endroit :

  • remplacement de kube-proxy : le service est resolu dans le datapath eBPF, sans iptables, ce qui tient la charge sur un point de presence ;
  • encryption WireGuard : chiffrement transparent du trafic inter-noeuds, avec un cout CPU mesurable mais faible ;
  • mode pare-feu : politiques L3/L4/L7 par endpoint, plus un host firewall qui protege les noeuds eux-memes.

Talos documente explicitement que KubeSpan et les configurations Cilium avancees ne cohabitent pas : Cilium attend que le trafic inter-noeuds passe par l'interface primaire, KubeSpan l'intercepte, et le routage devient asymetrique. Ce depot utilise donc Cilium WireGuard et pas KubeSpan.

Pourquoi Istio en API Gateway

L'API Gateway de Kubernetes remplace les Ingress. Sur un edge, elle apporte trois choses que l'Ingress n'a pas : des listeners typés (HTTP, HTTPS, TLS passthrough), des references de certificats par listener, et un modele de rattachement des routes explicite par parentRefs et sectionName. Istio fournit l'implementation, avec un controleur de deploiement qui cree lui-meme le Deployment et le Service derriere chaque Gateway.

Le choix de sectionName dans les routes est volontaire : plusieurs listeners partagent le port 443, et sans section explicite une route peut atterrir sur le mauvais.

Pourquoi cert-manager en HTTP-01 par la passerelle

Le defi HTTP-01 ne demande aucun credential DNS, contrairement au DNS-01 : rien a stocker, rien a fuiter. Il exige en revanche que le port 80 standard soit joignable, ce qui impose la plage NodePort restreinte cote Cilium.

Limite a connaitre : HTTP-01 ne delivre pas de certificat wildcard. C'est pourquoi deux listeners HTTPS distincts portent chacun leur nom et leur certificat, au lieu d'un wildcard.

Ce que ce POC ne couvre pas

  • Absorption volumetrique : l'anti-DDoS de Scaleway est en amont, hors de portee de la machine, et il n'y a ni anycast ni BGP ici. Un seul prefixe, une seule IP par noeud.
  • Performance de production : le Xeon E3-1220 de l'A116XSSD saturera avant le lien de 500 Mbps sur des petites requetes. Les chiffres mesures decrivent cette machine, pas l'architecture. Pour un chiffre exploitable, une EM-B130E-NVMe (EPYC 4245P, 3 Gbps) sur une journee suffit.
  • Haute disponibilite du bord : un seul worker signifie un point unique de defaillance pour le trafic de bord. Le control plane est tolerant aux pannes, pas la passerelle.
  • Acceleration XDP : non mesurable avec un seul worker, voir le README.