Amazon EKS — AWS Load Balancer Controller

2 minute read

The AWS Load Balancer Controller creates and manages ALBs and NLBs for Kubernetes Ingress and Service resources. It replaces the legacy in-tree AWS cloud provider load balancer.


What It Replaces

Feature Legacy (in-tree) AWS LB Controller
ALB support No Yes (via Ingress)
NLB support Basic Full (IP mode, cross-zone)
Target type Instance only Instance or IP
WAF integration No Yes
IngressGroup No Yes (shared ALB)
Active development Deprecated Active

Installation with Helm

Step 1 — Create IAM Role

Download the IAM policy:

curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.9.0/docs/install/iam_policy.json

Create the IAM policy:

aws iam create-policy \
  --policy-name AWSLoadBalancerControllerIAMPolicy \
  --policy-document file://iam-policy.json

Create the IAM role with IRSA:

eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace kube-system \
  --name aws-load-balancer-controller \
  --attach-policy-arn arn:aws:iam::123456789:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve

Step 2 — Install with Helm

helm repo add eks https://aws.github.io/eks-charts
helm repo update

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  --namespace kube-system \
  --set clusterName=my-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  --set region=us-east-1 \
  --set vpcId=vpc-0123456789abcdef0

Verify

kubectl get deployment aws-load-balancer-controller -n kube-system
kubectl logs -n kube-system deployment/aws-load-balancer-controller

Subnet Discovery Tags

The controller discovers subnets using tags:

Tag Value Purpose
kubernetes.io/role/elb 1 Public subnets — internet-facing ALB/NLB
kubernetes.io/role/internal-elb 1 Private subnets — internal ALB/NLB

EKS also requires:

kubernetes.io/cluster/my-cluster = owned  (or shared)

Controller Architecture

The controller watches Kubernetes Ingress and Service resources, then:

  1. Ingress → Creates/updates an ALB with target groups and rules
  2. Service (type: LoadBalancer) → Creates/updates an NLB with target groups
  3. TargetGroupBinding → Binds existing target groups to K8s endpoints

The controller reconciles the AWS resources continuously — if someone manually changes the ALB in the console, the controller reverts it.


Terraform Installation

module "lb_controller_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.0"

  role_name                              = "aws-load-balancer-controller"
  attach_load_balancer_controller_policy = true

  oidc_providers = {
    main = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

resource "helm_release" "aws_lb_controller" {
  name       = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  namespace  = "kube-system"
  version    = "1.9.0"

  set {
    name  = "clusterName"
    value = module.eks.cluster_name
  }

  set {
    name  = "serviceAccount.create"
    value = "true"
  }

  set {
    name  = "serviceAccount.name"
    value = "aws-load-balancer-controller"
  }

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = module.lb_controller_irsa.iam_role_arn
  }
}

Best Practices

  1. Tag subnets correctly — the controller cannot discover subnets without proper tags
  2. Use IP target mode — better latency (skips kube-proxy), required for Fargate
  3. Run two replicas of the controller for high availability
  4. Pin the controller version — test upgrades before applying
  5. Monitor controller logs — most issues are visible in controller pod logs

Updated: