Cilium Cilium

Services mit Ciliums Gateway-API-Unterstützung bereitstellen

GatewayClass, Gateway und HTTPRoute – hostbasiertes Routing, Pfad-Matching, URL-Rewrites und TLS, ohne separate Gateway-Pods.

14 min Fortgeschritten

Die Kubernetes Gateway API ist der standardisierte Nachfolger von Ingress, und Cilium implementiert sie direkt mit seinem eigenen eBPF-Datenpfad plus einem eingebetteten Envoy für die Layer-7-Verarbeitung. Diese Anleitung behandelt die Kernressourcen GatewayClass, Gateway und HTTPRoute sowie die daraus gebauten Routing-Muster. Sie setzt voraus, dass Cilium bereits mit aktivierter Gateway API installiert ist (siehe die vorherige Anleitung).

GatewayClass: Cilium als verfügbaren Controller deklarieren

Eine GatewayClass sagt dem Cluster „dieser Controller steht für Gateways zur Verfügung“:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium
spec:
  controllerName: io.cilium/gateway-controller

Wenn du bei der Installation gatewayAPI.gatewayClass.create: false gesetzt hast (wie es die vorherige Anleitung empfiehlt), ist das die Ressource, die du selbst erstellst, um diese Lücke zu füllen.

Die Klasse mit CiliumGatewayClassConfig anpassen

Cilium bringt mit CiliumGatewayClassConfig eine eigene CRD für Einstellungen mit, die für jedes Gateway einer bestimmten Klasse gelten sollen, meist den Kubernetes-Service-Typ, über den das Gateway bereitgestellt wird:

apiVersion: cilium.io/v2alpha1
kind: CiliumGatewayClassConfig
metadata:
  name: gateway-api-config
  namespace: default
spec:
  service:
    type: NodePort
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium
spec:
  controllerName: io.cilium/gateway-controller
  parametersRef:
    group: cilium.io
    kind: CiliumGatewayClassConfig
    name: gateway-api-config
    namespace: default

NodePort ist hier speziell ein Zugeständnis an kind-Cluster (siehe unten). Auf einem Cluster mit einem echten Cloud-Load-Balancer ist LoadBalancer die typischere Wahl, und spätere Anleitungen dieser Reihe zur BGP-Route-Ankündigung behandeln, wie du eine routbare Adresse ganz ohne den Load Balancer eines Cloud-Anbieters bekommst.

Ein Gateway erstellen

Ein Gateway ist die eigentliche Listener-Konfiguration und referenziert die obige Klasse:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: demo-gateway
  namespace: default
spec:
  gatewayClassName: cilium
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
          - name: demo-tls-secret

Keine Gateway-Pods: Hier unterscheidet sich Cilium

Bei den meisten Gateway-API-Implementierungen erzeugt ein Gateway ein dediziertes Deployment samt Pods für diese Gateway-Instanz. Bei Cilium nicht: Der Traffic wird per eBPF auf Knotenebene abgefangen und gemäß der Konfiguration des Gateways geroutet. Es erscheint nur ein Kubernetes-Service, keine Pods, die dem Gateway selbst gewidmet sind.

kubectl get gateway demo-gateway
kubectl get service -l gateway.networking.k8s.io/gateway-name=demo-gateway

Speziell auf einem kind-Cluster bekommt ein NodePort-Service standardmäßig einen zufälligen Port, nicht die Ports, die du tatsächlich willst (30080/30443 aus der früheren kind-Cluster-Konfiguration). Patche ihn explizit:

kubectl patch service <gateway-service-name> --type='json' -p='[
  {"op": "replace", "path": "/spec/ports/0/nodePort", "value": 30080},
  {"op": "replace", "path": "/spec/ports/1/nodePort", "value": 30443}
]'

Dieser Patch-Schritt ist rein ein kind-Workaround für das Fehlen eines echten Load Balancers und keine allgemeine Cilium-Einschränkung. Auf einem Cluster mit einer echten Cloud-LoadBalancer-Implementierung oder mit Ciliums eigenen L2-Announcements oder der BGP-Route-Ankündigung (eine spätere Anleitung dieser Reihe) bekommt der Service des Gateways eine routbare Adresse ohne manuelles Patchen.

Nach Hostname routen

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route
  namespace: default
spec:
  parentRefs:
    - name: demo-gateway
  hostnames:
    - "python.example.com"
  rules:
    - backendRefs:
        - name: python-service
          port: 5000

parentRefs hängt diese Route an das Gateway an (sie müssen im selben Namespace liegen, oder die Route braucht eine explizite Cross-Namespace-Referenz-Policy). Jede Anfrage mit dem Host-Header python.example.com wird an python-service geschickt.

Nach Pfad routen: Exact vs. Prefix Matching

  rules:
    - matches:
        - path:
            type: Exact
            value: /
      backendRefs:
        - name: python-service
          port: 5000

Exact passt nur auf genau diesen Pfad. Eine Anfrage an /status würde nicht auf eine Regel für / passen. Für alles jenseits eines einzelnen festen Pfads passt PathPrefix auf das angegebene Präfix und leitet den Rest des Pfads unverändert weiter:

  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /

Der Unterschied ist in der Praxis wichtig: Eine API mit mehreren Endpunkten hinter einer Route braucht PathPrefix, nicht Exact, sonst liefert jeder Endpunkt außerhalb des exakt passenden Pfads „Route nicht gefunden“, statt das Backend überhaupt zu erreichen.

URL-Rewrites: ein sauberer externer Pfad, ein anderer interner

Ein gängiges Muster ist, mehrere Backend-Services unter einem gemeinsamen Hostnamen mit einem aufgeräumten Pfadschema bereitzustellen, während jedes Backend seine eigenen internen Routen behält und von diesem externen Präfix nichts weiß:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route
  namespace: default
spec:
  parentRefs:
    - name: demo-gateway
  hostnames:
    - "example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api/python
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: python-service
          port: 5000

Eine Anfrage an example.com/api/python/status wird vom Präfix /api/python erfasst und vor dem Erreichen von python-service zu /status umgeschrieben, sodass das Backend selbst nie vom Präfix /api/python wissen muss, das die Außenwelt sieht. Definiere für jeden Backend-Service eine parallele Route (/api/go zu einem Go-Service und so weiter), um aus mehreren unabhängig entwickelten Backends ein einziges konsolidiertes API-Gateway zu bauen, bei dem jedes die vom Gateway extern präsentierte Pfadstruktur nicht kennt.

TLS: Zwei Dinge müssen übereinstimmen

Für HTTPS-Traffic müssen zwei Teile konsistent konfiguriert sein:

  1. Der HTTPS-Listener des Gateways (oben gezeigt) referenziert ein TLS-Secret und setzt mode: Terminate.
  2. Der parentRefs-Eintrag jeder HTTPRoute, die HTTPS-Traffic annehmen soll, muss tatsächlich den HTTPS-Listener per Namen ansprechen, nicht nur das Gateway als Ganzes:
  parentRefs:
    - name: demo-gateway
      sectionName: https

Ohne sectionName, das auf den HTTPS-Listener zeigt, hängt sich eine Route standardmäßig an den oder die Listener, auf die ihre Match-Kriterien passen, was nicht der beabsichtigte sein muss. Erstelle das TLS-Secret, bevor du ein Gateway anwendest, das darauf verweist:

kubectl create secret tls demo-tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem \
  -n default

kubectl describe gateway demo-gateway ist die erste Anlaufstelle, wenn ein Listener keinen Traffic wie erwartet annimmt. Ein fehlendes oder fehlerhaftes TLS-Secret ist ein häufiger Grund, warum ein Listener als nicht akzeptiert angezeigt wird.

Beliebte Anleitungen