Implementing Kubernetes Gateway API using AWS Load Balancer Controller - part II: L7 (ALBGatewayAPI)
The open-source AWS Load Balancer Controller supports Gateway API in GA (General Available) since v3.0.0.
This two-part article series provides a comprehensive example of installing AWS Load Balancer Controller (LBC) with Gateway API support, using Pod Identity and two sample implementations using L4 (NLBGatewayAPI) and L7 ((ALBGatewayAPI) routing and sample AWS CLI and kubectl outputs for reference.
Background
The open-source AWS Load Balancer Controller supports Gateway API in GA (General Available) since v3.0.0.
This fact and customer demand for implementing a Gateway API using AWS Load Balancer Controller (LBC), led to the creation of this article.
Prerequisites
Follow installation steps from rePost article Implementing Kubernetes Gateway API using AWS Load Balancer Controller - part I: L4 (NLBGatewayAPI) including sections Prerequisites, AWS Load Balancer Controller installation and Common configurations.
L7 (ALBGatewayAPI) routing.
LBC comes with two controllers. L7 routing uses the controller gateway.k8s.aws/alb and provisions ALB.
First create the GatewayClass YAML:
gwc-alb.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
name: aws-alb-gateway-class
spec:
controllerName: gateway.k8s.aws/alb
and apply it to the cluster:
$ kubectl apply -f gwc-alb.yaml
gatewayclass.gateway.networking.k8s.io/aws-alb-gateway-class created
$ kubectl get gc aws-alb-gateway-class
NAME CONTROLLER ACCEPTED AGE
aws-alb-gateway-class gateway.k8s.aws/alb True 96s
Now create the Gateway resource which references the LoadBalancerConfiguration internet-facing-config we created earlier:
gw-public-alb.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: aws-alb-gateway
spec:
gatewayClassName: aws-alb-gateway-class
infrastructure:
parametersRef:
kind: LoadBalancerConfiguration
name: internet-facing-config
group: gateway.k8s.aws
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
apply it to the cluster and check the ADDRESS. An ALB will be provisioned, which takes some time until PROGRAMMED shows True:
$ kubectl k apply -f gw-public-alb.yaml
gateway.gateway.networking.k8s.io/aws-alb-gateway created
$ kubectl get gateway aws-alb-gateway
NAME CLASS ADDRESS PROGRAMMED AGE
aws-alb-gateway aws-alb-gateway-class k8s-default-awsalbga-<redacted1>-<redacted2>.eu-west-1.elb.amazonaws.com Unknown 14s
One can check the ALB using the following AWS elbv2 command for reference:
# ELB is an internet-facing ALB (Type: application)
$ aws elbv2 describe-load-balancers --names k8s-default-awsalbga-<redacted1>
{
"LoadBalancers": [
{
"LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>",
"DNSName": "k8s-default-awsalbga-<redacted1>-<redacted2>.eu-west-1.elb.amazonaws.com",
"CanonicalHostedZoneId": "Z32O12XQLNTSW2",
"CreatedTime": "2026-03-16T08:16:08.190000+00:00",
"LoadBalancerName": "k8s-default-awsalbga-<redacted1>",
"Scheme": "internet-facing",
"VpcId": "vpc-<redacted>",
"State": {
"Code": "active"
},
"Type": "application",
"AvailabilityZones": [
{
"ZoneName": "eu-west-1b",
...
This ALB does not yet have a listener provisioned, even though it is referenced already as spec.listeners.name: http, because this still requires a HTTPRoutes resource:
$ aws elbv2 describe-listeners --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>
{
"Listeners": []
}
Note the API version of HTTPRoutes (and others), which is already in GA (generally available) status !!!
$ kubectl api-resources | grep -w httproutes
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
$ kubectl api-resources | grep -w gateway.networking.k8s.io/v1
backendtlspolicies btlspolicy gateway.networking.k8s.io/v1 true BackendTLSPolicy
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
grpcroutes gateway.networking.k8s.io/v1 true GRPCRoute
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
The HTTPRoutes references the backend service created earlier and the resource YAML looks like:
httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-backend-aws
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: aws-alb-gateway
sectionName: http
hostnames:
- "aws-k8s-gw-alb.<redacted domain name>"
rules:
- backendRefs:
- group: ""
kind: Service
name: backend
port: 3000
weight: 1
matches:
- path:
type: PathPrefix
value: /
and apply it to the cluster:
$ kubectl apply -f httproute.yaml
httproute.gateway.networking.k8s.io/http-backend-aws created
Now check the ALB for related AWS resources listener, rules, target groups and target health using the following AWS elbv2 commands for reference:
# describe ALB listener, which listens on port 80 and has a default HTTP response 404 Not Found
$ aws elbv2 describe-listeners --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>
{
"Listeners": [
{
"ListenerArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:listener/app/k8s-default-awsalbga-<redacted1>/<redacted2>/<redacted3>",
"LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>",
"Port": 80,
"Protocol": "HTTP",
"DefaultActions": [
{
"Type": "fixed-response",
"Order": 1,
"FixedResponseConfig": {
"StatusCode": "404",
"ContentType": "text/plain"
}
}
]
}
]
}
# describe ALB listener forward rule to target group k8s-default-httpback-<redacted>
$ aws elbv2 describe-rules --listener-arn arn:aws:elasticloadbalancing:eu-west-1:<redacted>:listener/app/k8s-default-awsalbga-<redacted1>/<redacted2>/<redacted3>
{
"Rules": [
{
"RuleArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:listener-rule/app/k8s-default-awsalbga-<redacted1>/<redacted2>/<redacted3>",
"Priority": "1",
"Conditions": [
{
"Field": "host-header",
"Values": [
"aws-k8s-gw-alb.<redacted domain name>"
],
"HostHeaderConfig": {
"Values": [
"aws-k8s-gw-alb.<redacted domain name>"
]
}
},
{
"Field": "path-pattern",
"Values": [
"/*"
],
"PathPatternConfig": {
"Values": [
"/*"
]
}
}
],
"Actions": [
{
"Type": "forward",
"TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:targetgroup/k8s-default-httpback-<redacted>/<redacted>",
"Order": 1,
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:targetgroup/k8s-default-httpback-<redacted>/<redacted>",
"Weight": 1
}
],
"TargetGroupStickinessConfig": {
"Enabled": false
}
}
}
],
"IsDefault": false,
"Transforms": []
},
{
"RuleArn": "arn:aws:elasticloadbalancing:eu-west-1:618668373247:listener-rule/app/k8s-default-awsalbga-<redacted1>/<redacted2>/<redacted3>/<redacted4>",
"Priority": "default",
"Conditions": [],
"Actions": [
{
"Type": "fixed-response",
"Order": 1,
"FixedResponseConfig": {
"StatusCode": "404",
"ContentType": "text/plain"
}
}
],
"IsDefault": true,
"Transforms": []
}
]
}
# describe ALB target group, which is of targetType: ip and is using targets from backend service on port 3000 using HTTP
$ aws elbv2 describe-target-groups --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>
{
"TargetGroups": [
{
"TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:targetgroup/k8s-default-httpback-<redacted>/<redacted>",
"TargetGroupName": "k8s-default-httpback-<redacted>",
"Protocol": "HTTP",
"Port": 3000,
"VpcId": "vpc-<redacted>",
"HealthCheckProtocol": "HTTP",
"HealthCheckPort": "traffic-port",
"HealthCheckEnabled": true,
"HealthCheckIntervalSeconds": 15,
"HealthCheckTimeoutSeconds": 5,
"HealthyThresholdCount": 3,
"UnhealthyThresholdCount": 3,
"HealthCheckPath": "/",
"Matcher": {
"HttpCode": "200-399"
},
"LoadBalancerArns": [
"arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/app/k8s-default-awsalbga-<redacted1>/<redacted2>"
],
"TargetType": "ip",
"ProtocolVersion": "HTTP1",
"IpAddressType": "ipv4"
}
]
}
# targets are the backend deployment pod(s)
$ aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:eu-west-1:<redacted>:targetgroup/k8s-default-httpback-<redacted>/<redacted>
{
"TargetHealthDescriptions": [
{
"Target": {
"Id": "192.168.165.118",
"Port": 3000,
"AvailabilityZone": "eu-west-1c"
},
"HealthCheckPort": "3000",
"TargetHealth": {
"State": "healthy"
},
"AdministrativeOverride": {
"State": "no_override",
"Reason": "AdministrativeOverride.NoOverride",
"Description": "No override is currently active on target"
}
}
]
}
# backend K8s pod
$ kubectl get pod backend-86c6c76f-vwbxt -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
backend-86c6c76f-vwbxt 1/1 Running 0 4d8h 192.168.165.118 ip-<redacted>.eu-west-1.compute.internal <none> <none>
Finally create a Route53 alias record pointing to the NLB, check it and use curl to generate some traffic:
$ aws route53 list-resource-record-sets --hosted-zone-id <public hosted zoneID> --query 'ResourceRecordSets[?Name==`aws-k8s-gw-alb.<redacted domain name>`]'
[
{
"Name": "aws-k8s-gw-alb.<redacted domain name>",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z32O12XQLNTSW2",
"DNSName": "dualstack.k8s-default-awsalbga-<redacted1>-<redacted2>.eu-west-1.elb.amazonaws.com.",
"EvaluateTargetHealth": true
}
}
]
$ dig +short aws-k8s-gw-alb.<redacted domain name>
<public ALB IP1>
<public ALB IP2>
<public ALB IP3>
$ curl -s -o /dev/null -w "%{http_code}\n" aws-k8s-gw-alb.<redacted domain name>
200
$ curl aws-k8s-gw-alb.waltju.people.<redacted domain name>
{
"path": "/",
"host": "aws-k8s-gw-alb.<redacted domain name>",
"method": "GET",
"proto": "HTTP/1.1",
"headers": {
"Accept": [
"*/*"
],
"User-Agent": [
"curl/8.7.1"
],
"X-Amzn-Trace-Id": [
"Root=1-69b7d989-5d038fe15420da4844429c41"
],
"X-Forwarded-For": [
"95.90.210.65"
],
"X-Forwarded-Port": [
"80"
],
"X-Forwarded-Proto": [
"http"
]
},
"namespace": "default",
"ingress": "",
"service": "",
"pod": "backend-86c6c76f-vwbxt"
}
$ curl -v aws-k8s-gw-alb.<redacted domain name>
* Host aws-k8s-gw-alb.<redacted domain name>.:80 was resolved.
* IPv6: (none)
* IPv4: <public ALB IP1>, <public ALB IP2>, <public ALB IP3>
* Trying <public ALB IP1>:80...
* Connected to aws-k8s-gw-alb.<redacted domain name> (<public ALB IP1>) port 80
> GET / HTTP/1.1
> Host: aws-k8s-gw-alb.<redacted domain name>
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Mon, 16 Mar 2026 10:36:18 GMT
< Content-Type: application/json
< Content-Length: 477
< Connection: keep-alive
< X-Content-Type-Options: nosniff
<
{
"path": "/",
"host": "aws-k8s-gw-alb.<redacted domain name>",
"method": "GET",
"proto": "HTTP/1.1",
"headers": {
"Accept": [
"*/*"
],
"User-Agent": [
"curl/8.7.1"
],
"X-Amzn-Trace-Id": [
"Root=1-69b7dd22-51c4f8fc6968fd470d1eef71"
],
"X-Forwarded-For": [
"95.90.210.65"
],
"X-Forwarded-Port": [
"80"
],
"X-Forwarded-Proto": [
"http"
]
},
"namespace": "default",
"ingress": "",
"service": "",
"pod": "backend-86c6c76f-vwbxt"
* Connection #0 to host aws-k8s-gw-alb.<redacted domain name> left intact
}
This concludes the two-part series.
- Topics
- Containers
- Language
- English
Relevant content
- Accepted Answerasked 4 years ago
