Networking & Content Delivery
Introducing the LBC Ingress-to-Gateway API migration toolkit
Migrating your AWS Load Balancer Controller (LBC) Ingress resources to the Gateway API by hand is tedious and error prone. You need to rewrite annotations, path rules, and TLS configuration, and a mistake can disrupt the production traffic. The Ingress-to-Gateway API migration toolkit for LBC removes that risk by giving you a guided, validated path you can take at your own pace.
Earlier this year, we walked through migrating NGINX Ingress Controller annotations to AWS Load Balancer Controller (LBC) Ingress resources. That work translated your routing rules, TLS termination, and backend configuration smoothly to ALB-native constructs. The result was a stable, fully AWS-integrated Ingress layer. Now, we want to help you take the next step.
The toolkit starts with lbc-migrate, a CLI tool. It translates LBC Ingress resources to Gateway API equivalents. It is paired with Migration Console, a local web UI bundled into the lbc-migrate binary for reviewing dry-run plans before you migrate. In this post, we translate an Ingress manifest that uses TLS termination and a URI rewrite into equivalent Gateway API manifests, preview the result with a dry run, and verify the migrated configuration. For the Console UI, refer to the Migration Console reference linked in the tooling overview section.
Why migrate to Gateway API
The Kubernetes project now recommends the Gateway API as the successor to the Ingress API, which was frozen in March 2026. The Ingress API remains generally available and stable, with no plans for removal. It receives no further feature work, and new routing capabilities will land in the Gateway API instead. The Gateway API takes a different design approach. It provides a typed, structured extension model and a standard for traffic splitting. It also adds built-in multi-tenancy support and defines behavior through typed resources. Gateway API uses a role-oriented resource model. Infrastructure administrators manage GatewayClass and Gateway resources, while application developers own HTTPRoutes. Type-safe CRDs add schema validation at admission time. Weighted traffic splitting is first-class and supports canary deployments. Cross-namespace routing works through explicit ReferenceGrant permissions. Typed Policy resources let you extend behavior as your needs evolve. If your team runs workloads on Amazon Elastic Kubernetes Service (Amazon EKS), Gateway API project is a recommended long-term routing interface.
Migration tooling
The toolkit includes two complementary tools for a safe, validated migration from Ingress to Gateway API. One tool translates your resources. The other lets you review the result before it reaches production.
- lbc-migrate CLI: A standalone binary that translates your LBC Ingress resources into equivalent Gateway API YAML manifests. It handles annotations, path rules, and IngressGroup configurations for you. You can point it at static YAML files, or directly at a live cluster with –from-cluster. Your existing Deployments and Services are reused as-is, so the tool generates only routing-layer resources.
- Migration Console: A local web UI bundled within the lbc-migrate binary (lbc-migrate –console). It gives you a read-only, side-by-side view. You can compare Ingress and Gateway dry-run plans and review them with confidence before any production change. Launching the Console is optional, and the walkthrough in this post uses the CLI dry-run flow instead.
For installation instructions, CLI flags, output formats, and the annotation support table, refer to the lbc-migrate CLI Reference.
For the console UI guide and RBAC requirements, refer to Migration Console.
Migration guidance
The migration follows six steps, and you stay in control at each one. Every step is safe to pause at, so you can pause and resume whenever it suits you. First, you translate your Ingress resources into Gateway API manifests. Next, you preview the result with a dry run and confirm the plans match. You then apply the manifests. This creates new ALBs alongside your existing Ingress ALBs. After that, you verify the new Gateway ALBs are healthy. When you are confident, you shift traffic from the Ingress ALBs to the Gateway ALBs. Finally, you clean up the old Ingress resources. The CLI drives translation, application, and cleanup (Steps 1, 3, and 6). The optional Migration Console supports the Step 2 review. You can also do that review from the CLI dry-run output, as this walkthrough does.
The following table summarizes each step, what happens, and how to roll it back.
| Step | Action | What Happens | Rollback |
|---|---|---|---|
| 1 | Translate | lbc-migrate converts Ingress YAML to Gateway API YAML | Delete generated files |
| 2 | Dry-run Preview | Turn on the IngressPlanAnnotation=true feature gate at the LBC level. This adds dry-run plan annotations to existing Ingress resources. The Gateway manifests from Step 1 already include the gateway.k8s.aws/dry-run: “true” annotation by default. Apply them. Review the Ingress and Gateway dry-run plans, either from the CLI output or in the optional Migration Console. No AWS resources are created. | Delete dry-run Gateway |
| 3 | Apply | Regenerate with –dry-run=false, apply manifests. LBC creates new ALBs alongside existing Ingress-managed ALBs. | Delete Gateway resources |
| 4 | Verify | Confirm Gateway status Programmed: True. Validate target group health. Monitor Amazon CloudWatch metrics on the new ALB. | N/A |
| 5 | Shift Traffic | Gradually move traffic from Ingress ALBs to Gateway ALBs. You have two options. The first, Amazon Route 53 weighted routing, is DNS-based and adds no extra cost. The second, AWS Global Accelerator, splits per request and needs no domain. | Shift traffic back |
| 6 | Cleanup | Delete Ingress resources. LBC removes old ALBs, target groups, and listener rules automatically. | N/A |
For the detailed step-by-step walkthrough, including CLI commands, feature gate configuration, console usage, and traffic shifting instructions, refer to the official migration guide.
Example walkthrough
Let’s walk through a real example together. We’ll take a single ALB Ingress that does TLS termination and a URI rewrite. We translate it with lbc-migrate, preview the result with a dry run, and confirm you get the same behavior on a Gateway-managed ALB. Let’s start with the prerequisites.
Prerequisites
- Amazon EKS cluster with a managed node group
- AWS Load Balancer Controller v3.4.0 Installed. Gateway API support turns on automatically when the CRDs are present.
- Gateway API standard CRDs (v1.5.0) and the LBC Gateway CRDs installed:
# Standard Gateway API CRDs
kubectl apply --server-side=true \
-f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml
# LBC Gateway CRDs (LoadBalancerConfiguration, TargetGroupConfiguration, ...)
kubectl apply \
-f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v3.4.0/config/crd/gateway/gateway-crds.yaml
- Confirm the ALB Gateway controller is live:
kubectl -n kube-system logs deploy/aws-load-balancer-controller | grep "gateway.k8s.aws/alb"
# ...Starting EventSource ... "controller":"gateway.k8s.aws/alb" ...
- An AWS Certificate Manager (ACM) certificate for your hostname (referenced by the following ARN).
Step 0: The starting point: a working ALB Ingress
If you don’t already have a sample app behind the LBC, no problem. The echo server walkthrough deploys echoserver behind an ALB Ingress and verifies access in several kubectl apply commands. Complete the steps through its “Verify that you can access the service” step.
We’ll build on that base, adding the two annotations this post is about so a single Ingress carries both use cases:
- TLS termination: listen-ports (80/443) + certificate-arn + ssl-redirect.
- URI rewrite: transforms.echoserver strips the /api prefix before the request reaches the backend.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo
namespace: demo
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:<acct>:certificate/<id>
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/transforms.echoserver: >
[{"type":"url-rewrite","urlRewriteConfig":{"rewrites":[
{"regex":"^\\/api\\/(.+)$","replace":"/$1"}]}}]
spec:
ingressClassName: alb
rules:
- host: demo.lbc-migrate.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service: { name: echoserver, port: { number: 80 } }
Confirm that the configuration is correct before you migrate, since the tool assumes your Ingress is valid and currently serving:
ALB=$(kubectl get ingress demo -n demo -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
# TLS termination: HTTP is redirected to HTTPS
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
-H "Host: demo.lbc-migrate.example.com" "http://$ALB/api/hello"
# 301 -> https://demo.lbc-migrate.example.com:443/api/hello
# URI rewrite: client asks for /api/hello, backend receives /hello
curl -sk -H "Host: demo.lbc-migrate.example.com" "https://$ALB/api/hello" | grep "real path"
# real path=/hello
Step 1: Install lbc-migrate
You build lbc-migrate from the controller repo and install it once:
git clone https://github.com/kubernetes-sigs/aws-load-balancer-controller.git
cd aws-load-balancer-controller
make lbc-migrate # binary lands at bin/lbc-migrate
make install-lbc-migrate # optional: symlink onto your PATH
Step 2: Convert the Ingress
Now point the tool at your live cluster. The tool is read-only and never modifies your cluster. It reads the Ingress, Service, IngressClass, and IngressClassParams, and writes out Gateway API manifests:
lbc-migrate --from-cluster --namespaces demo --output-dir ./gateway-output
Found 1 Ingress(es), 1 Service(s), 1 IngressClass(es), 1 IngressClassParams
WARNING: url-rewrite transform with capture group reference "/$1" cannot be represented in Gateway API — skipping.
Generated 6 resource(s) in gateway-output/gateway-resources.yaml
Note: For details on which annotations are fully, partially, or not yet supported, refer to Limited Support for Certain Ingress Annotations.
The generated output maps directly to the Gateway API:
| Ingress annotation | Becomes |
|---|---|
| ingressClassName: alb | GatewayClass aws-alb (controllerName: gateway.k8s.aws/alb) |
| scheme, certificate-arn | LoadBalancerConfiguration (scheme, listenerConfigurations[].defaultCertificate) |
| listen-ports 80/443 | Gateway with http-80 and https-443 listeners |
| ssl-redirect: ‘443’ | HTTPRoute with a RequestRedirect filter (HTTP → 301 → HTTPS) |
| host + path → service | HTTPRoute with PathPrefix: /api → echoserver |
| healthcheck-path, target-type | TargetGroupConfiguration |
TLS termination converts fully and automatically, and the generated TLS half is shown next:
apiVersion: gateway.k8s.aws/v1beta1
kind: LoadBalancerConfiguration
metadata: { name: demo-lb-config, namespace: demo }
spec:
scheme: internet-facing
listenerConfigurations:
- protocolPort: HTTPS:443
defaultCertificate: arn:aws:acm:us-west-2:<acct>:certificate/<id>
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute # the ssl-redirect, as a portable Gateway API filter
metadata: { name: demo-redirect, namespace: demo }
spec:
parentRefs: [{ name: demo-gateway, sectionName: http-80 }]
rules:
- filters:
- type: RequestRedirect
requestRedirect: { scheme: https, port: 443, statusCode: 301 }
The tool also emits a second HTTPRoute, the one carrying your host + path → service rule. The generated HTTPRoute is shown next. The /api match comes across, but there is no rewrite filter, the capture-group gap the warning called out. We’ll finish this route in Step 4 and hand-edit with rewrite filter.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute # the path route - generated as-is, rewrite still missing
metadata: { name: demo-route, namespace: demo }
spec:
hostnames: [ demo.lbc-migrate.example.com ]
parentRefs: [{ name: demo-gateway, sectionName: https-443 }]
rules:
- matches:
- path: { type: PathPrefix, value: /api }
backendRefs:
- { name: echoserver, port: 80 }
Step 3: Preview with a dry run (optional, recommended)
By default, lbc-migrate stamps generated Gateways with gateway.k8s.aws/dry-run: “true”. Applying a dry-run Gateway lets the controller validate and model the AWS resources without provisioning an ALB, a safe look before you commit. Go ahead and apply the dry-run manifests. Review and then proceed to the next step.
kubectl apply -f ./gateway-output/gateway-resources.yaml
Note: if you apply the dry-run annotation to a Gateway that is already provisioned, the controller leaves your live ALB untouched. It logs Ignoring dry-run annotation on an already-provisioned Gateway. Use dry run for previewing new Gateways, not for pausing running ones.
Step 4: Finish the URI rewrite
Recall the warning we saw in Step 2. Our Ingress used a regex with a capture group (replace: /$1). The URLRewrite filter only does static path replacement. So lbc-migrate skips the dynamic part rather than translate it incorrectly and warns you when it does.
The intent (to strip the /api prefix) is what ReplacePrefixMatch expresses natively. Take the demo-route HTTPRoute from Step 2 and add the one filter it was missing:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: demo-route, namespace: demo }
spec:
hostnames: [ demo.lbc-migrate.example.com ]
parentRefs: [{ name: demo-gateway, sectionName: https-443 }]
rules:
- matches:
- path: { type: PathPrefix, value: /api }
filters:
- type: URLRewrite # <-- the rewrite, done the Gateway API way
urlRewrite:
path: { type: ReplacePrefixMatch, replacePrefixMatch: / }
backendRefs:
- { name: echoserver, port: 80 }
Step 5: Verification
Regenerate without dry run (or just delete the annotation) and apply:
lbc-migrate --from-cluster --namespaces demo --output-dir ./gateway-live --dry-run=false
# (add the URLRewrite filter from Step 4 to ./gateway-live/gateway-resources.yaml)
kubectl apply -f ./gateway-live/gateway-resources.yaml
Now wait for the Gateway to be programmed. It provisions a new ALB alongside your existing Ingress ALB:
kubectl get gateway demo-gateway -n demo
# NAME CLASS ADDRESS PROGRAMMED
# demo-gateway aws-alb k8s-demo-demogate-....us-west-2.elb.amazonaws.com True
Test both use cases against the Gateway-managed ALB:
GW=$(kubectl get gateway demo-gateway -n demo -o jsonpath='{.status.addresses[0].value}')
# TLS termination
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
-H "Host: demo.lbc-migrate.example.com" "http://$GW/api/hello"
# 301 -> https://demo.lbc-migrate.example.com:443/api/hello
# URI rewrite
curl -sk -H "Host: demo.lbc-migrate.example.com" "https://$GW/api/hello" | grep "real path"
# real path=/hello
Same behavior as the Ingress, now expressed in Gateway API.
Step 6: Clean up ingress
Because the new ALB runs alongside the old one, cutover is a DNS and traffic-shift decision that stays in your hands. When you are ready, point your record at the Gateway ALB, monitor it, then remove the Ingress:
kubectl delete ingress demo -n demo
Clean up resources
If instead you want to roll back the migration entirely, or you are done with the demo, remove the Gateway resources you created. Deleting them avoids ongoing AWS charges. Delete the Gateway, HTTPRoutes, LoadBalancerConfiguration, and TargetGroupConfiguration, and then delete the demo namespace:
kubectl delete -f ./gateway-output/gateway-resources.yaml
kubectl delete namespace demo
Recommendations
- Start with a dry run. The Migration Console helps you catch annotation mappings that need a manual look before you touch production.
- Understand your traffic path before shifting. Identify how clients reach the Ingress ALB. They may use a custom domain (e.g., api.example.com) backed by Amazon Route 53 or another DNS provider or reach it directly via the ALB DNS name. If clients use the ALB DNS directly, you cannot shift traffic without updating every client, so plan accordingly.
- Shift traffic gradually where your environment supports it. How you shift gradually depends on your DNS provider, load balancing service, and client configuration. Whichever mechanism you choose, make sure you can roll back quickly if anything looks off. We do not walk through a specific traffic-shifting procedure here, but for one example of CRD-based traffic management, refer to the LBC Global Accelerator documentation.
- Keep Ingress and Gateway running in parallel until you have validated. The dual-ALB architecture supports this kind of coexistence. Validate thoroughly and only delete Ingress resources once you have seen sustained, error-free operation on the Gateway path.
- Plan for Gateway API as your long-term routing interface. For new workloads, reach for HTTPRoute directly. You can reference the lbc-migrate annotation support table for current compatibility.
Conclusion
This guide helps you move to Gateway API with confidence. Gateway API is the long-term routing standard for Kubernetes. The lbc-migrate tool is here to make the transition safe and mostly automated. It translates the bulk of your LBC Ingress configuration into Gateway API manifests and gives you dry-run validation before you commit. Some annotations need a manual edit, such as the URI rewrite in Step 4. The tool flags each one so nothing is missed. Because you keep the existing Ingress ALB in place while you validate the new Gateway ALB, you can cut over on your own schedule, with confidence.
Combined with our earlier guide on migrating from NGINX Ingress to LBC Ingress, you now have a complete, validated migration path from earlier ingress controllers. It runs all the way to the Gateway API routing standard on AWS. We look forward to seeing what you build.
To get started, refer to the lbc-migrate CLI Reference, the official migration guide, and the Gateway API documentation.