Skip to content

GRPCRoute and HTTPRoute sharing a Gateway listener section breaks external gRPC clients on Cilium #106

Description

@christianhuth

The problem

When a GRPCRoute and an HTTPRoute are attached to the same Gateway listener section (sectionName), Cilium's Gateway API translator generates a combined Envoy filter chain that:

  • Does not add the grpc_web and grpc_stats HTTP filters required for gRPC traffic
  • Sets the codec to HTTP1, blocking HTTP/2 negotiation

This causes all external gRPC connections to fail — peers connecting from outside the cluster receive an HTTP/1.1 response instead of a valid gRPC response. In-cluster traffic works because Cilium's eBPF kube-proxy replacement bypasses Envoy's filter chain processing (including the HTTP1 codec restriction) for in-cluster sources — though Cilium still consults Envoy's route tables, so a matching GRPCRoute must still exist on the listener.

The chart provides no documentation about this constraint. Users naturally configure the grpcRoute on the same listener as the httpRoute (same sectionName in parentRefs), which appears to work for in-cluster components but silently breaks external gRPC clients.

The suggested fix

No template changes are required — parentRefs is already freely configurable per-route. The fix is documentation in values.yaml under server.grpcRoute explaining:

  • Cilium requires a dedicated Gateway listener section exclusively for the GRPCRoute (no HTTPRoute on the same sectionName)
  • The dashboard.config.mgmtGrpcApiEndpoint should point to a hostname served by that dedicated listener
  • An example showing the correct parentRefs setup

The current workaround

Create a dedicated Gateway listener (e.g. netbird-grpc-https) for gRPC only. If in-cluster components such as the Netbird operator or NetworkRouter are used, they connect to the original hostname for both REST (/api/*) and gRPC — that hostname cannot be pointed at the dedicated gRPC listener because it has no HTTPRoute for REST. In that case, attach the GRPCRoute to both listener sections via dual parentRefs. Without in-cluster components, a single parentRef on the dedicated listener is sufficient.

dashboard:
  config:
    mgmtGrpcApiEndpoint: https://netbird-grpc.example.com

server:
  grpcRoute:
    hostnames:
      - netbird.example.com        # original, for in-cluster components
      - netbird-grpc.example.com   # dedicated gRPC listener, for external clients
    parentRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: shared-gateway
        namespace: gateway
        sectionName: netbird-https       # shared with HTTPRoutes (in-cluster)
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: shared-gateway
        namespace: gateway
        sectionName: netbird-grpc-https  # gRPC-only (external clients)

Requirements

For both the workaround and once the documentation fix lands:

  • When using Cilium: setting the Helm Value gatewayAPI.enableAlpn=true makes Cilium advertise h2 in TLS ALPN on the dedicated listener so gRPC clients negotiate HTTP/2
  • External clients must use netbird-grpc.example.com as their Management URL — the original hostname shares a listener with HTTPRoutes and will continue to fail gRPC for external connections even after applying the workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions