Kubernetes networking

Gateway API vs Ingress for Kubernetes traffic

Same hostname, two APIs: classic Ingress versus role-split Gateway API—and how Universal Helm Chart maps both without inventing a third DSL.

Chart fidelity

Values keys below match Universal Helm Chart application 0.4.3 (values.yaml, configuration.md). Chart templates: templates/8_ingress.yaml, templates/route.yaml. Replace hostnames and images.

Chart 0.4.3 — args / multi-port / service.enabled

From application 0.4.3: main container args (default []); Deployment revisionHistoryLimit (default 10); multi-port via containerPorts and service.ports; optional chart-managed Service via service.enabled (default true). When service.ports is set, Ingress / simple HTTPRoute / NOTES use the first entry (templates/_helpers.tpl application.servicePort). Legacy single-port service.name / service.port / service.protocol / service.appProtocol remain supported. Set service.enabled: false when the workload does not need a chart-managed Service. Do not invent other port keys.

Chart 0.4.3 — route.spec

From application 0.4.3, set route.spec to a complete HTTPRoute.spec (parentRefs, hostnames, rules, filters, multi-backendRefs). When route.spec is non-empty, the chart renders it unchanged and ignores the simple route.hostname / route.path / route.gateway generator. Simple route.* remains the default path; route.gateway is required only when route.enabled: true and route.spec is empty. Do not invent shorthand keys like route.headers or route.cors.

Chart 0.4.3 — service.appProtocol

Optional service.appProtocol (default "") sets Service.spec.ports[].appProtocol on the chart-managed Service (templates/7_service.yaml). For Services created by ingressPlain paths with createService: true, set paths[].service.appProtocol (since 0.4.0). Leave empty when you do not need a protocol hint (for example grpc). Do not invent other service.* protocol keys.

Tested versions

Why Ingress hit a ceiling

Ingress is one object owned by whoever can edit annotations. Controllers disagree on annotation semantics. TLS, canaries, rewrites, and auth become vendor strings instead of typed fields. Status is thin: “it works” or “it doesn’t,” with little portable condition language.

Gateway API splits roles: platform owns GatewayClass and Gateway listeners; app teams own HTTPRoute (and related routes) that attach via parentRefs. Policies that are not portable stay in implementation CRDs (for kgateway: TrafficPolicy, BackendConfigPolicy, GatewayExtension)—labelled as such, not as “Ingress annotations 2.0.”

Role model

Official type reference: HTTPRoute, Gateway.

Equivalent exposure in Universal Helm Chart

Defaults: ingress.enabled: false, route.enabled: false. Prefer one exposure path per release unless you intentionally dual-publish during migration.

Ingress values → networking.k8s.io/v1 Ingress

service:
  port: 8080
ingress:
  enabled: true
  className: nginx
  annotations: {}
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-example-tls
      hosts:
        - app.example.com

HTTPRoute values → gateway.networking.k8s.io/v1 HTTPRoute

route.gateway is required when route.enabled: true. The template sets parentRefs to that Gateway and backendRefs to the chart Service on service.port. Optional: gatewayNamespace, sectionName, path, pathMatchType (default PathPrefix), backendWeight (default 1), timeouts.request / timeouts.backendRequest.

service:
  port: 8080
route:
  enabled: true
  gateway: external
  gatewayNamespace: kgateway-system
  sectionName: https
  hostname: app.example.com
  path: /
  pathMatchType: PathPrefix
  backendWeight: 1
  timeouts:
    request: 75s
    backendRequest: 75s

route.gatewayNamespace must match where the platform Gateway lives. This series uses lab/platform namespace kgateway-system (kgateway Helm install). If your platform uses another NS, set the value accordingly—do not invent a chart default.

The chart does not create Gateway, GatewayClass, or kgateway policies. Those stay platform-owned (or separate manifests). Chart HTTPRoute is a single hostname + single path match per release—see Gateway API examples.

helm repo add universal https://chaser100.github.io/u-helm-chart
helm repo update
helm template app-ingress universal/application --version 0.4.3 -f values-ingress.yaml | rg -n 'kind: Ingress|host:'
helm template app-route universal/application --version 0.4.3 -f values-route.yaml | rg -n 'kind: HTTPRoute|parentRefs|hostname'

Ownership and TLS

Status checks

# Gateway (platform NS)
kubectl get gateway -n kgateway-system
kubectl describe gateway -n kgateway-system external
# Expect: Accepted=True and Programmed=True

# Ingress
kubectl describe ingress -n apps app
# HTTPRoute (portable parent conditions)
kubectl get httproute -n apps
kubectl describe httproute -n apps app
# Expect parents: Accepted=True, ResolvedRefs=True
kubectl get httproute -n apps app -o jsonpath='{.status.parents}' ; echo

Battle-test evidence: twelve HTTPRoutes reported Accepted=True and ResolvedRefs=True through an external kgateway Gateway with wildcard TLS. Always confirm the Gateway itself is Programmed=True before blaming the chart HTTPRoute.

When to stay on Ingress

Migration decision checklist

  1. Inventory Ingresses, classes, TLS secrets, and annotations (portable vs controller-specific).
  2. Install Gateway API CRDs + choose implementation (here: kgateway); create shared Gateway listeners.
  3. Convert with review—do not blind-apply (Ingress NGINX → kgateway).
  4. Flip app values from ingress.enabled to route.* (or dual-publish briefly).
  5. Validate status + curl; keep rollback to Ingress until cutover is proven.

Security and failure modes

Known limitations

Next: Ingress NGINX to kgateway → · Route conversions → · Chart HTTPRoute values → · Gateway API docs