This page is generated from skills/eks-ingress-migration/references/migration-plan.md. Edit the source, not this page.
Gateway API Migration Plan
Note: there is no standalone "Migration Planning" report section. This file drives Migration Options (the phased plan) and its scope/complexity/timeline observations feed Migration Options and Blockers — do not emit a separate Migration Planning section.
Rating model: Express every finding as Impact 0–5 using the Impact Indicator rubric, weighing three dimensions in priority order: (1) business logic / revenue — the live traffic at stake · (2) security / reputation · (3) effort to remediate. Effort is NOT a severity driver — a fix being easy or hard never moves the score (it depends on who implements it). Presence is decided by estate state — absent controller / empty estate / orphaned dead config = non-event (0); a broken controller is tech debt (1) with zero bound routes or a suspected active outage (flagged outside the score) with bound routes; a running controller with a control-plane CVE counts even at zero routes. See
ingress-discovery.mdfor the full presence/stacking rules. Band mapping is a starting point — 🟢 0 / 🟡 1–2 / 🟠 3–4 / 🔴 5 — but the Impact Indicator criteria set the final score (e.g. an easy-to-deploy prerequisite stays 🟡 low even if it blocks a path). All checks are read-only (kubectl get/describe,aws … describe/list).
Purpose
Generate a concrete, phased migration plan from Ingress to Gateway API based on assessment findings.
Plan Structure
Phase 1: Foundation (Week 1)
Install/verify Gateway API prerequisites:
-
Install the standard Gateway API CRDs (if not present) — pick the version that pairs with the controller line you land on in step 2, because the pairing is not interchangeable:
Controller line Standard CRDs Notes v3.4.0 (the tag the lbc-migrateCLI is built from)v1.5.0 TLSRoute is standard from v1.5.0; TCPRoute/UDPRoute still need the experimental channel on this pairing v3.5.0 (current runtime; the recommended target) v1.6.0 LBC v3.5.0 is built for Gateway API v1.6.0; TCPRoute/UDPRoute are in the standard channel from v1.6.0, so no experimental install is needed for L4 # v1.6.0 — pair with a v3.5.0 controller (see step 2)kubectl apply --server-side=true -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yamlAlso install the LBC Gateway API CRDs (
LoadBalancerConfiguration,TargetGroupConfiguration,ListenerRuleConfiguration) — the ALB Gateway controller does not enable without them, and Phase 2 configures the load balancer through them. -
Upgrade AWS LB Controller to ≥ v3.0.0 (the Gateway API production floor; the current v3.5.0 line is the recommended target) — still required on EKS Auto Mode, whose built-in controller does not provide Gateway API. There is no
aws-load-balancer-controllerEKS add-on — it is not in the AWS add-on catalog nor the community add-on list, so install and upgrade go through Helm (or the release manifests):helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set serviceAccount.create=false -
Create GatewayClass:
apiVersion: gateway.networking.k8s.io/v1kind: GatewayClassmetadata:name: aws-albspec:controllerName: gateway.k8s.aws/alb -
Update external-dns to add
--source=gateway-httproute -
Update cert-manager to enable Gateway API support (if using cert-manager)
Phase 2: Convert & Test (Week 2-3)
Automate this phase with
lbc-migratewhen the estate is already on LBC ALB Ingress. The official LBC Ingress → Gateway API toolkit (lbc-migrateCLI + Migration Console) translates the Ingress resources into Gateway API manifests and previews the result with a dry-run before any ALB is created. Prerequisites are the same runtime requirement as the hand-authored path below — controller ≥ v3.0.0, the Gateway API production floor — plus the standard Gateway API CRDs the controller line targets (v1.5.0 for v3.4.0, v1.6.0 for the v3.5.0) and the CLI built from the LBC v3.4.0 tag. Full flow, prerequisites, output resources and limitations:references/lbc-migrate-toolkit.md— don't restate them here. The hand-authored steps below remain the fallback for the toolkit's skip-or-warn cases, for clusters below that production floor (where upgrading the controller to the v3.5.0 release line is usually the better first move — it clears the floor and still carries the CLI), and for EKS Auto Mode.lbc-migrateconverts LBC Ingress, not raw NGINX — do the NGINX → LBC Ingress hop first (references/alb-migration.md).
For each Ingress resource, create an equivalent HTTPRoute:
-
Start with lowest-risk routes (internal, low-traffic)
-
Create a Gateway resource for each listener group. The ALB is configured through the
LoadBalancerConfigurationCRD, not throughalb.ingress.kubernetes.io/*annotations — those are Ingress/Service-only and are ignored on a Gateway. Two consequences worth stating explicitly, because both fail silently:schemedefaults tointernal, so omitting it gives you a private ALB, not an internet-facing one.- TLS certificates cannot be set via the listener's
certificateRefsfield. Upstream states this outright; the cert comes fromlistenerConfigurations[].defaultCertificate(an ACM ARN) or from hostname-based certificate discovery. A Gateway that only setscertificateRefsgets no certificate on the listener.
apiVersion: gateway.k8s.aws/v1kind: LoadBalancerConfigurationmetadata:name: main-gateway-lbnamespace: <namespace>spec:scheme: internet-facing # REQUIRED for public: default is internallistenerConfigurations:- protocolPort: HTTPS:443defaultCertificate: <acm-arn> # ACM ARN; NOT a Kubernetes SecretsslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06---apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata:name: main-gatewaynamespace: <namespace>spec:gatewayClassName: aws-albinfrastructure:parametersRef: # how the LB config attachesgroup: gateway.k8s.awskind: LoadBalancerConfigurationname: main-gateway-lblisteners:- name: httpsprotocol: HTTPSport: 443hostname: <fqdn> # drives certificate discoverytls:mode: Terminate# No certificateRefs: unsupported by LBC. Cert is set above.Per-target-group settings (health checks, target type, attributes) move to
TargetGroupConfiguration, and per-rule settings such as OIDC authentication move toListenerRuleConfiguration.authenticateOIDCConfig— again, not annotations. -
Create HTTPRoute for each Ingress:
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: <app-name>namespace: <namespace>spec:parentRefs:- name: main-gatewayhostnames:- "<host>"rules:- matches:- path:type: PathPrefixvalue: "/<path>"backendRefs:- name: <service>port: <port> -
Test each HTTPRoute independently (new ALB created by Gateway)
-
Validate DNS, TLS, routing, health checks
Report output format: In the report's "Target Config" column, show the equivalent Gateway API config as compact 1-liner:
HTTPRoute/<name>: parentRef=<gateway>, hostnames=[<host>], path=<path> → <backend>:<port>
Example: HTTPRoute/shopping-app-route: parentRef=main-gateway, path=/* → frontend:80
Example: HTTPRoute/nginx-app-route: parentRef=main-gateway, hostnames=[app.example.com], path=/* → nginx-service:80
Phase 3: Traffic Cutover (Week 4)
Validate the full cutover procedure in a non-production cluster before executing against production. "Lowest-risk routes" and keeping the old Ingress as fallback are mitigations, not a substitute for a non-prod gate — a low-risk production route is still production, and path-matching semantic changes can silently route traffic to the wrong backend (see
references/traffic-routing.md).
- Update DNS to point to new Gateway ALB (or use weighted DNS for gradual shift)
- Monitor error rates, latency, 5xx responses
- Keep old Ingress resources running as fallback
Phase 4: Cleanup (Week 5)
- Confirm all traffic flowing through Gateway API
- Delete old Ingress resources
- Remove the old ingress controller (nginx, etc.) if no longer needed —
helm uninstallthe release, neverkubectl delete deployalone. The ingress-nginx chart also owns aningress-nginx-admissionValidatingWebhookConfiguration (failurePolicy: Fail, no selectors); if the Deployment goes and the webhook stays, its Service has no endpoints and the API server rejects every Ingress create/update in the cluster — including re-applying the migrated resources and any GitOps reconcile. For a non-Helm install, delete the admissionValidatingWebhookConfigurationand its Service explicitly. - Remove the retired controller's IngressClass and its
LoadBalancerService — deleting the workload alone leaves the old CLB/NLB, its security groups and any DNS records still live and billable - Retire stale DNS records (including external-dns ownership TXT records, not just the A/CNAME entries)
- Update IaC/GitOps to manage HTTPRoute resources instead of Ingress
Enumerate the survivors before declaring cleanup done. Uninstalling the release (or deleting the Deployment on a non-Helm install) leaves these behind, and each is a real orphan with cost or blast radius: the controller's own
LoadBalancerService (and therefore its CLB/NLB, security groups and any DNS records still pointing at it), theingress-nginx-admissionValidatingWebhookConfiguration and its admission Service, the controller's IngressClass, and any external-dns TXT/A records it owned. On a non-Helm install every one of these must be deleted explicitly. Verify with: noingress-nginxService of typeLoadBalancerremains,kubectl get validatingwebhookconfigurationsshows no ingress-nginx entry, and the old load balancer is gone fromelbv2 describe-load-balancers.
Checks to Execute
These §7 checks are planning signals, NOT scored findings. §7.1 scope, §7.2 conversion complexity, and §7.3 timeline restate what sections 1–5 already scored (scope↔Scale/Volume, complexity↔Feature-Gap/Routing) and feed the phased plan and Blockers. Do not add any §7.x row to the Score Breakdown (see
report-generation.md§1.1 quarantine) — that would double-count. Timeline in particular is never an Impact score.
7.1 — Migration Scope
What to check:
- Total Ingress resources to convert
- Estimated HTTPRoute count (may differ — one Ingress can become multiple HTTPRoutes)
- ReferenceGrant resources needed (for cross-namespace routing)
Impact (per Impact Indicator):
- 🟡 1–2 (Low): <20 Ingress resources, straightforward 1:1 mapping
- 🟠 3–4 (Medium): 20-50 Ingress resources, some complex conversions
- 🔴 5 (High): >50 Ingress resources or heavy customization requiring redesign
- ⬜ Unknown: Cannot determine scope