Kubernetes Gateway API vs Ingress: Migration Guide - 夜莺博客

Kubernetes Gateway API vs Ingress: Migration Guide

Ingress did one thing with one resource and one pile of controller-specific annotations. Gateway API is its designated successor, and it is not a drop-in replacement: it separates "where traffic enters" from "how it is routed", makes the features that used to hide in annotations portable, and adds extension points for everything else. If you are planning a migration — or just trying to read a Gateway YAML for the first time — this guide maps the old world onto the new one.

Three Differences That Explain Everything

Dimension Ingress Gateway API
Personas User / cluster operator / infrastructure provider Application developer, application admin, cluster operator
Features Basic TLS termination and host/path routing; everything else via annotations Traffic splitting, header/query/method matching, redirects, request/response manipulation as first-class fields; regex path matching; policies via a standard UX
Extensibility Vendor annotations, non-portable Defined extension points: extensionRef filters, custom backend/secret references, implementation-specific (custom) conformance

The practical consequence: the same routing intent expressed with five Ingress annotations across three controllers becomes one portable field in an HTTPRoute — but anything genuinely implementation-specific still needs that implementation's own resource.

Concept Mapping

Ingress Gateway API
Ingress object (load balancer + rules together) Gateway (listeners, ports, TLS) + HTTPRoute (routing rules)
spec.tls with a Secret TLS is a property of the Gateway listener, certificate still in a Secret
spec.rules.host HTTPRoute.spec.hostnames
spec.rules.http.paths HTTPRoute.spec.rules[].matches[].path
Default backend A rule with no matches, or an explicit default route
Annotations for timeouts/health checks/auth Extension points or implementation policies — check your Gateway implementation
# Before: Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-svc
            port:
              number: 80
---
# After: Gateway (entry points) + HTTPRoute (routing)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-gateway
spec:
  gatewayClassName: example-gateway-class
  listeners:
  - name: http
    protocol: HTTP
    port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-route
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "app.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web-svc
      port: 80

Two structural changes jump out: entry points must now be declared explicitly (a listener for port 80 or 443, instead of whatever the controller happened to configure), and routes attach to a Gateway through parentRefs rather than being installed globally.

Rules That Decide Behaviour

  • Hostname matching: an HTTPRoute's hostnames must match the Gateway listener's hostname, otherwise the listener ignores those routing rules. This is the most common "my route does nothing" cause.
  • Route merging: a Gateway implementation must merge rules from all HTTPRoutes attached to a listener. Where they conflict, the API design guide applies — more specific matches win over less specific ones.
  • Per-route semantics: in Ingress each hostname had its own routing rules; in HTTPRoute the rules apply across all hostnames on that route, so split routes per hostname when the intent differs.

Migrating Without Downtime

  1. Run both controllers side by side. A Gateway API controller and your existing Ingress controller can coexist and will receive different external IPs, so the new path can be tested in isolation.
  2. Start with ingress2gateway. It translates Ingress resources into Gateway and HTTPRoute objects; treat the output as a draft and review every annotation it could not map.
  3. Verify with real traffic against the new external IP — status codes, TLS chain, redirects, timeouts — before touching DNS.
  4. Cut over with DNS or the load balancer, keeping the Ingress objects in place until the rollback window closes.
  5. Test edge cases deliberately: the regex path type, traffic splitting during a canary, and any authentication that used to be an annotation.

What to Watch For

  • Annotation inventory: list every annotation in use across the cluster before migrating; unmapped ones are your real project scope.
  • Controller maturity: Gateway API features vary by implementation and conformance level — verify the features you depend on are supported, not just the API objects accepted.
  • RBAC and ownership: splitting Gateway from HTTPRoute lets platform teams own entry points while app teams own routes. That is the point — use it, rather than giving everyone Gateway write access.
  • Two APIs to monitor: while both exist, your dashboards need to cover both entry paths or incidents will hide in the one nobody watches.

What a Complete Gateway Deployment Looks Like

The most useful mental model for a migration is that Ingress was one object doing three jobs, and Gateway API splits those jobs across three owners. Walking a real deployment from the bottom up: a GatewayClass is installed by whoever installs the controller and names the implementation; a Gateway is created by the platform team and declares listeners (ports, protocols, hostnames, TLS); one or more HTTPRoutes are created by application teams and attach to that Gateway through parentRefs.

# what the controller declares about itself
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: example-gateway-class
spec:
  controllerName: example.com/gateway-controller
---
# a platform-owned gateway: one TLS listener, one redirect listener
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: prod-gateway
  namespace: infra
spec:
  gatewayClassName: example-gateway-class
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: "*.example.com"
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: wildcard-example-com
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            gateway-access: "true"
  - name: http
    protocol: HTTP
    port: 80
    hostname: "*.example.com"

allowedRoutes.namespaces is the cross-namespace security boundary that Ingress never had. A team in another namespace cannot attach a route to the platform's Gateway unless the platform team explicitly allowed that namespace — and the Route's own parentRefs must opt in as well, with a matching namespace and optionally a sectionName naming a single listener. Ingress had no equivalent: whoever could create an Ingress in any namespace could claim a hostname on the shared load balancer.

TLS, Certificates and the Redirect Problem

In Ingress, TLS was a single field — spec.tls with a host list and a Secret — and the certificate had to live in the same namespace as the Ingress, which pushed every team into copying a wildcard certificate around. Gateway API moves the certificate reference to the listener, where a platform-owned Secret can be referenced by routes and gateways in other namespaces with a ReferenceGrant.

# allow a Secret in cert-manager to be referenced from the infra namespace
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-infra-gateway-certs
  namespace: cert-manager
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: infra
  to:
  - group: ""
    kind: Secret

For certificates, cert-manager has first-class support: annotate the Gateway and it issues and rotates the Secret. The HTTP-to-HTTPS redirect pattern is expressed either as a second listener carrying a redirect filter, or through an implementation-specific filter — verify with your controller's documentation, because this is exactly the kind of feature that varies by conformance level and by version.

Traffic Splitting and Canary Releases

Weighted backends were the headline reason teams reached for vendor annotations. In Gateway API they are a first-class field, which turns a canary into a declarative object rather than a controller-specific hack.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-route-canary
spec:
  parentRefs:
  - name: example-gateway
  hostnames: ["app.example.com"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web-svc-stable
      port: 80
      weight: 90
    - name: web-svc-canary
      port: 80
      weight: 10

Two semantics trip people up. Weights are relative rather than fixed percentages of connections, and many implementations only express them as percentages when the numbers sum to 100. And header matching is often the safer first step: matches[].headers with a RegularExpression type routes one internal test account through the canary while everyone else stays on stable, which gives you real traffic with none of the blast radius.

Policies: The Extension Model That Replaces Annotations

Every controller has settings no portable API can standardise — connection timeouts, health checks, rate limits, authentication. Ingress expressed those as annotations, which is precisely why Ingress manifests were never portable. Gateway API's answer is policy attachment: a separate resource that names a Gateway, HTTPRoute or Service as its targetRef and is understood only by the implementation that defines it.

# implementation-specific shape — what matters is the targetRef pattern
apiVersion: example.com/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: web-timeouts
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: web-route
  timeout: 30s
  retry: 3

The migration consequence is simple to state and easy to underestimate: standard Gateway API objects are portable, your policies are not. Every annotation you deploy today becomes a policy tied to one implementation, so the annotation inventory really is the project plan. If you ever expect to change controllers, keep the implementation-specific surface small and documented.

Operating Two APIs at Once

During a migration you are running two controllers, two sets of CRDs and two external entry points, and that has concrete operational consequences. The Gateway API CRDs are versioned and must be upgraded deliberately: check the served and stored versions before installing an update, and never apply CRDs from an unpinned branch, because a CRD upgrade can invalidate stored objects.

# which CRD versions are installed and which one is used for storage
kubectl get crd gateways.gateway.networking.k8s.io -o \
  jsonpath='{.spec.versions[*].name}{"\n"}'

# what a controller reports about its own health and conformance
kubectl get gatewayclass example-gateway-class -o jsonpath='{.status.conditions}'

# is anything actually attached to my listener?
kubectl get httproute -A -o \
  jsonpath='{range .items[*]}{.metadata.name}{" -> "}{.status.parents[*].parentRef.name}{"\n"}{end}'

Conformance testing is the safety net here: the API project publishes a suite that runs against your controller and reports which features it genuinely implements, not merely which objects it accepts. And instrument both paths while they coexist — a Gateway listener with no attached routes looks healthy to every Kubernetes check while dropping all traffic, and status.conditions on the Route and the Gateway are the only place that failure is visible. Wire those into alerting before the cutover, not after it.

相关阅读:Kubernetes NetworkPolicy 默认拒绝模式、Calico、Cilium 与 Flannel CNI 对比、NGINX stream 模块 TCP/UDP 负载均衡 以及 NGINX 反向代理配置。

原文链接:Kubernetes Gateway API - Migrating from Ingress