Alright, let’s talk about getting traffic into your EKS cluster. You’ve got pods, they’re running your brilliant application, but they’re useless if users can’t reach them. You might be thinking, “It’s Kubernetes, I’ll just create a Service of type LoadBalancer and call it a day.” And you’d be right… sort of. On AWS, that classic move doesn’t get you a classic Elastic Load Balancer (ELB) by default. It gets you a Network Load Balancer (NLB). And while NLBs are fantastic for raw performance and preserving the client IP, they’re a bit like a sledgehammer—powerful but not always the right tool for the job, especially for HTTP-based services.

This is where the AWS Load Balancer Controller and Application Load Balancers (ALBs) come in. They’re the dynamic duo for sophisticated HTTP/S traffic management in EKS. Forget the old alb-ingress-controller; that’s deprecated. This is the new hotness, and it’s infinitely more powerful.

Why You Want This, Really

So why bother? Because an ALB is a Layer-7 load balancer. It speaks HTTP. This means it can do things an NLB can only dream of: path-based routing, host-based routing, redirects, rewrites, and stickiness (session affinity) without you having to muck about with custom protocols. You can route /api to one set of pods and /static to another, all through a single external endpoint. It’s elegant, it’s efficient, and it’s a native AWS service, which means tight integration and less stuff for you to manage. The controller’s job is to translate Kubernetes manifest magic into actual AWS API calls to create and configure these ALBs for you.

Installing the Controller: Don’t Mess This Up

First, you need to get the controller running in your cluster. This isn’t just a simple kubectl apply; it requires AWS IAM permissions to actually go out and create resources. The most common pitfall here is botching the IAM setup. You have two main choices: attaching an IAM policy to the node role (easier but less secure) or using IAM Roles for Service Accounts (IRSA) (the right way).

Let’s do it the right way. First, ensure your cluster has an OIDC provider associated with it. If it doesn’t, create one.

eksctl utils associate-iam-oidc-provider --cluster my-cool-cluster --region us-west-2 --approve

Now, download the IAM policy from AWS and create it. Don’t try to write your own; it’s massive and you’ll miss something.

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

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

Note the Policy ARN it returns. Now, create an IAM service account for the controller using eksctl. This command handles the OIDC trust relationship magic for you.

eksctl create iamserviceaccount \
  --cluster=my-cool-cluster \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::123456789012:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

Finally, install the controller using Helm. Make sure you have the EKS Helm repo.

helm repo add eks https://aws.github.io/eks-charts
helm upgrade -i aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=my-cool-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

Check the logs to ensure it’s running without panicking about permissions. If it is, you likely messed up the IAM steps. Go back and check your ARNs.

Your First Ingress: Making It Work

With the controller alive, you can now define an Ingress resource. This is the object that tells the controller, “Hey, go make an ALB for me.” Here’s a basic example that will create an internet-facing ALB and route all traffic to a simple web service.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-first-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-web-service
                port:
                  number: 80

Apply this, and after a minute or two, check your AWS EC2 Console -> Load Balancers. You’ll see a new ALB being provisioned. The kubectl describe ingress my-first-ingress command will eventually show you its DNS name. The target-type: ip annotation is crucial here; it means the ALB will route traffic directly to the pod IPs, not to the cluster’s node IPs. This is the modern, preferred approach as it bypasses an extra hop through kube-proxy and works seamlessly with pod scaling.

The Power of Annotations: Where the Real Magic Is

The default Ingress spec is pretty anemic. The real power, and frankly where the designers locked all the good stuff away, is in the annotations. You must use annotations to configure the ALB. Want SSL? Annotations. Want redirects? Annotations. Want to make your ALB internal instead? You guessed it.

Here’s a more advanced example that adds an HTTPS listener, terminates TLS, and redirects HTTP to HTTPS.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-advanced-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    # Specify the ARN of your SSL certificate stored in AWS Certificate Manager
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:123456789012:certificate/abc12345-...
    # Create a SSL listener on port 443 and a HTTP listener that redirects to HTTPS
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ssl-redirect
                port:
                  number: use-annotation
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-web-service
                port:
                  number: 80

See that trick? We’re using the actions.ssl-redirect annotation to create a fake action, and then routing all traffic on the HTTP listener to that action, which is just a redirect. The HTTPS listener then handles the real traffic. It’s a bit convoluted, but it works brilliantly.

Common Pitfalls and How to Avoid Them

  1. The 404 Mystery: You create the Ingress, the ALB is healthy, but you get a 404. 99% of the time, this is because your Ingress rule’s path or host doesn’t match the request you’re making. Double-check your rules. Use kubectl describe ingress to see how the controller has interpreted your rules.
  2. The Endless Pending State: If your Ingress stays in “pending” forever, the controller isn’t working. Check its logs (kubectl logs -n kube-system deployment/aws-load-balancer-controller). It’s almost always an IAM permission issue.
  3. Cost Surprises: ALBs aren’t free. Each ALB you create (per Ingress) costs money. Be mindful of creating many small Ingresses instead of consolidating routes into fewer, more efficient ALBs using rules. The alb.ingress.kubernetes.io/group.name annotation is your friend for grouping Ingress resources to share a single ALB.
  4. Security Group Hell: The controller automatically creates and manages a security group for the ALB. It’s your job to ensure your worker node security groups allow traffic from this ALB security group on the NodePorts your services are using (usually 30000-32768). Or, better yet, use target-type: ip and ensure your pod security groups (if using them) or your VPC’s network ACLs allow traffic from the ALB. This tripped me up for a solid hour once. Don’t be me.

The AWS Load Balancer Controller is one of those things that feels like black magic when it works and a personal affront when it doesn’t. Get the IAM right, understand the annotations, and you’ll have a incredibly powerful tool for routing traffic that seamlessly bridges the Kubernetes and AWS worlds.