Server--:--:--You--:--:--

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: LoadBalancer with 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 = 1 on public subnets (internet-facing LBs), kubernetes.io/role/internal-elb = 1 on 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 unless externalTrafficPolicy: 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 → AccessDenied in logs), subnets not tagged, or ingressClassName: alb missing. Logs name the exact problem.
  • could not find any subnets for cluster — the kubernetes.io/role/elb (or internal-elb) subnet tags, plus kubernetes.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 enableBackendSecurityGroup or your node/pod SG rules allow it), or target-type: instance + wrong externalTrafficPolicy.
  • 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 mismatched group settings 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 targetgroupbindings and 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.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.