The AWS Load Balancer Controller
By Prabath Thalangama· September 7, 2026· 4 min read
#kubernetes#aws#eks
Introduction¶
On EKS, the AWS Load Balancer Controller (the successor to the "ALB Ingress Controller") watches Kubernetes objects and provisions AWS load balancers:
- Ingress → an Application Load Balancer (L7, path/host routing, WAF, OIDC auth).
- Service
type: LoadBalancerwith the right annotations → a Network Load Balancer (L4, static IPs, high throughput, preserves source IP).
Install prerequisites¶
- IAM: the controller needs an IAM policy (the published one) attached to a role, via IRSA (IAM Roles for Service Accounts) or Pod Identity.
- Subnet tags: the controller discovers subnets by tag —
kubernetes.io/role/elb = 1on public subnets (internet-facing LBs),kubernetes.io/role/internal-elb = 1on private subnets. Missing tags = "no subnets found" and nothing provisions. - Helm install:
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system --set clusterName=my-cluster \ --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
Ingress → ALB¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
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:...:certificate/xxxx
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/group.name: shared-alb # share one ALB across Ingresses
alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:...
spec:
ingressClassName: alb
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: web, port: { number: 80 } } }
target-type: ip vs instance¶
ip— the ALB targets pod IPs directly (requires the VPC CNI so pods have VPC IPs, which EKS does). Traffic skips the node's kube-proxy hop — lower latency, works with Fargate, and the target group tracks pods as they come and go. Preferred.instance— the ALB targets node IPs on the Service's NodePort; kube-proxy then forwards to a pod (possibly on another node). Extra hop, source IP obscured unlessexternalTrafficPolicy: Local.
IngressGroup — sharing an ALB¶
alb.ingress.kubernetes.io/group.name lets multiple Ingress objects (even across
namespaces) share one ALB. Each contributes rules; group.order sets rule
priority. Saves money (one ALB, not N) and lets teams manage their own routes on
a shared entry point.
Service → NLB¶
apiVersion: v1
kind: Service
metadata:
name: grpc-svc
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /grpc.health.v1.Health/Check
spec:
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
ports: [{ port: 443, targetPort: 8443 }]
selector: { app: grpc-svc }
Verification and troubleshooting¶
kubectl -n kube-system logs deploy/aws-load-balancer-controller
kubectl describe ingress app # events + the ALB DNS name once ready
kubectl get targetgroupbindings -A # the CRD the controller uses
aws elbv2 describe-target-health --target-group-arn ...
- Ingress stuck, no ALB, address empty — controller not running / crashing
(IAM policy missing →
AccessDeniedin logs), subnets not tagged, oringressClassName: albmissing. Logs name the exact problem. could not find any subnets for cluster— thekubernetes.io/role/elb(orinternal-elb) subnet tags, pluskubernetes.io/cluster/<name> = shared|owned.- ALB created, targets unhealthy — health check path returns non-2xx, wrong
port, the pod's security group doesn't allow the ALB's SG (the controller
manages a shared "backend" SG — ensure
enableBackendSecurityGroupor your node/pod SG rules allow it), ortarget-type: instance+ wrongexternalTrafficPolicy. - 502 from the ALB — backend closed the connection / idle timeout mismatch
(ALB default 60s), or HTTP/2 h2c to a backend that doesn't speak it. Set
alb.ingress.kubernetes.io/backend-protocol/-version. - Two Ingresses each made their own ALB — no shared
group.name, or mismatchedgroupsettings so they didn't merge. - Deleting the Ingress didn't delete the ALB — a finalizer/permission issue,
or the ALB is shared (still referenced by another Ingress in the group). Check
targetgroupbindingsand the controller logs. - Cross-zone traffic imbalance — NLB cross-zone load balancing is off by
default (and costs); enable with
service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true.
Related tools and reading¶
- On-site: HTTP Header Analyzer, SSL Certificate Checker.
- Related posts: ECS Fargate task networking, ALB vs NLB.
Related tools
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Docker & Kubernetes
Docker & Kubernetes· 3 min read
Pod Security Admission
PSP is gone. Pod Security Admission enforces the Pod Security Standards via namespace labels — three levels, three modes. Here's how to roll it out without breaking things.
August 30, 2026Docker & Kubernetes· 3 min read
Dockerfile Best Practices That Actually Matter
The handful of Dockerfile habits that cut build time and image size and remove the most common security findings.
August 27, 2026