Skip to content

Gateway API: Expose your services running on Kubernetes

Gateway API

The Gateway API is the next evolution of Kubernetes networking. It effectively replaces the older Ingress API, offering a more expressive, extensible, and role-oriented way to model service networking.

We are going to explore how to set up and use the Gateway API with practical examples. We'll look at exposing services to the internet, handling local traffic, and securing it all using Cilium as our underlying implementation (Note: it can be something else, it doesn't change the logic).

How it works

At its core, Gateway API separates the concerns of infrastructure provisioning from application routing.

  • GatewayClass: Defines the type of controller that will manage the Gateways (in our case, cilium).
  • Gateway: Describes a load balancer or ingress point that listens for traffic.
  • HTTPRoute: Defines the rules for routing HTTP/HTTPS traffic from a Gateway to your Services.
  • TLSRoute: Routes TLS streams based on SNI (Server Name Indication), often used for TLS passthrough.
  • TCPRoute & UDPRoute: Handle raw Layer 4 traffic for non-HTTP protocols like databases.
  • GRPCRoute: Specialized routing for gRPC traffic, allowing matches on services and methods.

The Internet-Facing Gateway

Let's start with the most common requirement: exposing applications to the world. We want automatic DNS management and TLS termination.

We define a Gateway specifically for internet traffic. Notice how we use annotations to delegate heavy lifting to cert-manager and external-dns.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: internet-facing
  annotations:
    # Tell cert-manager to use our Cloudflare Let's Encrypt issuer
    cert-manager.io/cluster-issuer: cloudflare-letsencrypt
    # Point external-dns
    external-dns.alpha.kubernetes.io/target: "xxx.mydomain.com"
spec:
  gatewayClassName: cilium
  # We bind this gateway to a specific LoadBalancer IP
  addresses:
  - type: IPAddress
    value: 192.168.0.2
  listeners:
  - name: https
    hostname: "*.mydomain.com"
    port: 443
    protocol: HTTPS
    allowedRoutes:
      namespaces:
        # we allow all namespaces to use this gateway
        from: All
    tls:
      mode: Terminate
      certificateRefs:
        # set the certificate name to be used (can be generated by cert-manager)
        - name: mydomain.com-tls

Routing Traffic

Now that the door is open, we need to guide the traffic. We use an HTTPRoute to send requests matching our hostnames to the correct service.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: internet-facing-wildcards
spec:
  parentRefs:
  # name of the gateway
  - name: internet-facing
  hostnames:
  - "*.mydomain.com"
  rules:
  - backendRefs:
    # where the service create by the gateway is located (namespace, and named)
    - name: cilium-gateway-internet-facing
      namespace: kube-system
      port: 8443

The Local Gateway

Sometimes we want services available only within our home or private network. We can create a separate "Local" Gateway for this purpose. This keeps internal tools internal.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: local
spec:
  gatewayClassName: cilium
  # bind this gateway to a specific LoadBalancer IP
  addresses:
  - type: IPAddress
    value: 192.168.0.1
  listeners:
    # we listen on port 80 for HTTP traffic
    - name: http
      hostname: "*.mydomain.local"
      port: 80
      protocol: HTTP
      # allow all namespaces to use this gateway
      allowedRoutes:
        namespaces:
          from: All
    # we listen on port 443 for HTTPS traffic
    - name: https
      hostname: "*.mydomain.local"
      port: 443
      protocol: HTTPS
      # allow all namespaces to use this gateway
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        # we use a certificate (can be generated by cert-manager)
        certificateRefs:
          - name: mydomain-local-tls-secret
            kind: Secret
            group: ""
            namespace: cert-manager

Cross-Namespace Access

One tricky part of Gateway API is security boundaries. By default, a Gateway in kube-system cannot read a Secret (like your TLS certificate) from cert-manager namespace.

We solve this with a ReferenceGrant. It explicitly allows the Gateway to reference secrets in another namespace.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-kube-system-gateways-to-ref-secrets
  namespace: cert-manager
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: kube-system
  to:
  - group: ""
    kind: Secret

Redirects

A very common pattern is redirecting insecure HTTP traffic to HTTPS. Gateway API handles this elegantly with Filters.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: redirect-http-to-https
spec:
  parentRefs:
  - name: local
    namespace: kube-system
    sectionName: http
  hostnames:
    - "*.mydomain.local"
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301

Here, we attach to the http listener of our local gateway and apply a RequestRedirect filter to upgrade the scheme to HTTPS.

Security with Cilium Network Policies

Finally, even with a Gateway, we should restrict network traffic. We can use a CiliumNetworkPolicy to ensure that only specific CIDR ranges (like our home network subnets) can talk to the Gateway.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-internal-to-gateway
  namespace: kube-system
spec:
  # select the gateway
  endpointSelector:
    matchLabels:
      gateway.networking.k8s.io/gateway-name: local
  ingress:
  # allow traffic from specific subnets
  - fromCIDR:
    - 192.168.1.0/24
    - 192.168.2.0/24
  # allow all outgoing traffic
  egress:
  - toEntities:
    - all

This policy locks down the local gateway so strictly clients from 192.168.94.0/24 and 192.168.27.0/24 can connect to it.