Module 7: Eks Specifics For Production
6. The AWS Load Balancer Controller: the real production `Ingress`
Description
In Module 4, lesson 4, you actually installed ingress-nginx inside andes-cargo-cluster, resolved a real finding (the official manifest left the controller's Pod on the wrong node), and in lesson 5 applied an Ingress object that exposed andes-cargo-status-api over HTTP, with no port-forward, against 127.0.0.1. That Ingress worked exactly as the Kubernetes standard defines. This lesson shows you what happens when that same object, with the exact same syntax, runs on a real EKS cluster: the Ingress object doesn't change, but who interprets it and what it builds underneath changes completely.
Connection to the module
Kubernetes defines Ingress as an API, not an implementation — any controller can claim that API and interpret its rules its own way. ingress-nginx is one controller; the AWS Load Balancer Controller is another, with an underlying difference this lesson develops in depth: one lives completely inside the cluster, the other creates real infrastructure outside the cluster, in your AWS account.
The same object, two different worlds underneath
SAME KUBERNETES Ingress — TWO DIFFERENT CONTROLLERS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: andes-cargo-ingress
spec:
ingressClassName: ??? ◀── this decides which controller acts
rules:
- host: andes-cargo.local
http:
paths:
- path: /
backend:
service:
name: status-api-service
ingressClassName: nginx (M4, EXECUTED) ingressClassName: alb (M7, representative)
───────────────────────────────────── ─────────────────────────────────────────
ingress-nginx interprets the rule AWS Load Balancer Controller interprets
│ the rule
▼ ▼
Just another Pod, running INSIDE A call to the AWS API,
andes-cargo-cluster creates a REAL Application
(namespace ingress-nginx) Load Balancer in your account,
│ outside the cluster
▼ ▼
nginx.conf generated internally, An ALB with its own public
the process routes traffic DNS, its own target
between Pods inside the cluster groups, pointing
directly at the Pods
The AWS Load Balancer Controller, in the official documentation's exact words: "The AWS Load Balancer Controller manages AWS Elastic Load Balancers for a Kubernetes cluster... The controller watches for Kubernetes Ingress or Service resources. In response, it creates the appropriate AWS Elastic Load Balancing resources." The key word is "creates": it doesn't simulate, it doesn't proxy internally — it creates real AWS resources (an Application Load Balancer, with its own ARN, its own bill, visible in the EC2/ELB console) every time an Ingress object requests one.
What it creates exactly, depending on the object type
AWS's official documentation is precise about which resource corresponds to which Kubernetes object:
| Kubernetes object | What the AWS Load Balancer Controller creates |
|---|---|
Ingress | An Application Load Balancer (ALB) — layer-7 balancing, HTTP/HTTPS, routing rules |
Service of type LoadBalancer | A Network Load Balancer (NLB) — layer-4 balancing, TCP/UDP |
Gateway (Kubernetes Gateway API, controller v2.14.0+) | An Application Load Balancer (ALB), with standardized configuration instead of annotations |
ingress-nginx, for comparison, creates no AWS resource under any circumstance — it has no way to, and it doesn't need to: it is, itself, the load balancer, running as Pods inside the cluster you already administer. That's the complete architectural difference between the two: one is the load balancer; the other creates a separate one.
One documented detail worth knowing before deciding: since version 2.5 of the controller, if you install it on an EKS cluster, it becomes by default the controller for any Service of type LoadBalancer too — not just Ingress — replacing Kubernetes' legacy cloud provider (which creates Classic Load Balancers, a type of balancer AWS no longer recommends). It's an additional, documented reason to install it even if your only initial use is Ingress: it prevents Kubernetes from accidentally falling back to the older load balancer provider.
IngressClass: the same mechanism, pointing at a different controller
In Module 4, ingressClassName: nginx told Kubernetes "this rule belongs to ingress-nginx's controller" — the exact value that lesson's manifest created as an IngressClass. With the AWS Load Balancer Controller, the same field points to a different IngressClass, with its own controller:
With IngressClass (representative) — verified against the aws-load-balancer-controller project's official documentation:
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: alb
spec:
controller: ingress.k8s.aws/alb
And andes-cargo-status-api's Ingress, with the same path/host you already know from Module 4, pointing at this class instead of nginx:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: andes-cargo-ingress
namespace: andes-cargo
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- host: andes-cargo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: status-api-service
port:
number: 80
Compare this YAML, field by field, against Module 4.5's real ingress.yaml: host, path, pathType, backend.service, all identical. The only things that change are ingressClassName (nginx → alb) and the appearance of controller-specific annotations (alb.ingress.kubernetes.io/*), the standard mechanism every Ingress controller uses to expose its own configuration without breaking the base object's portability — a pattern docs.aws.amazon.com/eks documents with its own complete annotation catalog. alb.ingress.kubernetes.io/target-type: ip deserves a separate explanation: it tells the ALB to route directly to the Pod's IP (via the VPC's CNI), instead of the node's port — the recommended option when the cluster uses the Amazon VPC CNI, which gives every Pod a real, routable IP inside the VPC.
Direct contrast table
ingress-nginx (M4, EXECUTED) | AWS Load Balancer Controller (M7, representative) | |
|---|---|---|
| Where the balancer lives | Inside the cluster, as a Pod (namespace ingress-nginx) | Outside the cluster, as a real AWS resource |
| What AWS resource it creates | None | A real Application Load Balancer, with its own ARN |
| Cost | $0 extra — it's just cluster compute you already pay for | The real cost of an ALB (hours + AWS LCUs), on top of the cluster's cost |
ingressClassName | nginx | alb |
| High availability of the balancer itself | Depends on how many replicas of the controller Pod itself run, and whether the cluster keeps them healthy | Administered by AWS, with the same availability as any ALB on the account |
Needs extraPortMappings (Module 4's kind-specific detail) | Yes, on kind — doesn't apply to EKS, where the ALB has its own real public DNS | Doesn't apply — the ALB never forwards host ports, it has its own public address |
| Requires AWS identity (IRSA/Pod Identity, lesson 5) | No — it never calls any AWS API | Yes — the controller itself needs a ServiceAccount with IAM permissions to create/modify ALBs on your behalf |
That last row connects directly with the previous lesson: the AWS Load Balancer Controller is, itself, a consumer of the IRSA/EKS Pod Identity pattern — it needs permission to call the Elastic Load Balancing API on your behalf, so it gets installed with its own annotated ServiceAccount, using exactly the same mechanism you'd use for andes-cargo-status-api.
Installation with Helm (representative), the method AWS recommends for anyone starting out:
eksctl create iamserviceaccount \
--cluster andes-cargo-cluster \
--namespace kube-system \
--name aws-load-balancer-controller \
--attach-policy-arn arn:aws:iam::000000000000:policy/AWSLoadBalancerControllerIAMPolicy \
--approve
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--namespace kube-system \
--set clusterName=andes-cargo-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller
Common mistakes
Assuming you need to rewrite the entire Ingress object when migrating from ingress-nginx to the AWS Load Balancer Controller (overestimating the change). What happens: someone, learning that "the controller changes completely," assumes host, path, pathType, and the backend also need rewriting. How to spot it: if your migration plan includes changing the Ingress's spec.rules. How to fix it: as this lesson's field-by-field comparison confirmed, host/path/pathType/backend.service are identical — it's the same Kubernetes standard. Only ingressClassName and the controller-specific annotations change.
Forgetting the controller itself needs its own AWS identity before it can create anything (sequencing). What happens: someone installs the AWS Load Balancer Controller's Helm chart without first creating the ServiceAccount with IRSA/Pod Identity, and the controller boots with no permission to call the ELB API. How to spot it: if the controller stays in Running state but no ALB ever gets created, and the logs show AccessDenied errors against the elasticloadbalancing API. How to fix it: the correct sequence, as this lesson's installation example shows, is first eksctl create iamserviceaccount (with the official AWSLoadBalancerControllerIAMPolicy policy), and only then helm install — the same identity-dependency order you already saw with StatusApiTaskRole in the previous lesson.
Thinking alb.ingress.kubernetes.io/target-type: ip is optional or cosmetic (technical). What happens: someone omits that annotation, assuming the ALB is going to "just work" the same without it. How to spot it: if your Ingress for the AWS Load Balancer Controller declares no target-type at all. How to fix it: without that annotation, the default value (instance) routes traffic to the node's port, not directly to the Pod — a real behavioral difference with a real VPC CNI, where every Pod already has its own routable IP; for this guide's pattern (Amazon VPC CNI, real Pod IPs), target-type: ip is the option documented as recommended, not a cosmetic detail.
Exercises
Exercise 1 — Identify what changes and what doesn't, without looking at the lesson. Write from memory: which fields of an andes-cargo-status-api Ingress change when moving from ingress-nginx to the AWS Load Balancer Controller, and which stay exactly the same?
See solution
Changes: ingressClassName (from nginx to alb), and controller-specific annotations appear (alb.ingress.kubernetes.io/scheme, alb.ingress.kubernetes.io/target-type, among others). Stays exactly the same: host, path, pathType, and the entire backend.service reference (the Service's name and port). If your answer separated "what belongs to the Kubernetes standard" (unchanged) from "what belongs to the controller's implementation" (changes), you captured this lesson's central distinction.
Exercise 2 — Explain, without using the word "inside" or "outside," the architectural difference between both controllers. A colleague asks you what the real difference is between ingress-nginx and the AWS Load Balancer Controller. Answer them in one sentence, without using the words "inside" or "outside."
See solution
A complete answer sounds, roughly, like this: "ingress-nginx is, itself, the load balancer — a process running as a Pod, administered with the same tools as any other cluster workload; the AWS Load Balancer Controller, on the other hand, is an operator that watches Ingress objects and, in response, creates a real Application Load Balancer, an AWS resource with its own lifecycle, billing, and availability, separate from the cluster that requested it."
Exercise 3 — Predict the effect of installing the controller without giving it AWS identity first. Based on this lesson's "Common mistakes," predict: if you install the AWS Load Balancer Controller's Helm chart without first creating its ServiceAccount with IRSA/Pod Identity, does the controller's Pod boot? And does an Ingress applied afterward manage to create an ALB?
See solution
The controller's Pod does boot and stays in Running state — there's no technical dependency preventing it from booting without AWS credentials. What fails is its function: when it receives a new Ingress and tries to call the elasticloadbalancing API to create the ALB, that call gets rejected with an AccessDenied error, because the controller's ServiceAccount has no IAM role associated. The visible symptom, in practice, is an Ingress left with no address assigned (empty ADDRESS in kubectl get ingress) and error logs in the controller itself — not a boot failure, but a permissions failure on the first attempt to act.
Summary and next step
This lesson contrasted, field by field, the same Kubernetes Ingress object under two completely different controllers: ingress-nginx (Module 4, actually run on kind), which is the load balancer, running as a Pod inside the cluster; and the AWS Load Balancer Controller (representative), which watches the Ingress object and creates a real Application Load Balancer in your AWS account, outside the cluster, with its own cost, availability, and lifecycle. You confirmed host/path/pathType/backend.service don't change — only ingressClassName and the controller-specific annotations — and that the controller itself needs its own AWS identity (IRSA/Pod Identity, lesson 5) before it can create anything.
Before moving on you should be able to: name which AWS resource the controller creates for an Ingress versus a Service of type LoadBalancer; explain why target-type: ip matters with a real VPC CNI; and describe, unambiguously, the architectural difference between "being the load balancer" and "creating a load balancer."
Next lesson: hands-on — the EKS YAML, shown and explained. There you're going to see, gathered in one place, eksctl create cluster, an IAMServiceAccount with IRSA, and this same IngressClass — real syntax, verified, explicitly labeled as not executed.
Resources
- Amazon EKS — Route internet traffic with AWS Load Balancer Controller — the exact source for the citations and the table of which resource gets created for each Kubernetes object.
- AWS Load Balancer Controller — Ingress annotations — complete catalog of
alb.ingress.kubernetes.io/*annotations. - AWS Load Balancer Controller — IngressClass — the exact source for the
controller: ingress.k8s.aws/albused in this lesson. kubernetes-and-eks-in-production-guide(NIEVA), Module 4, lessons 4-5 —ingress-nginxinstalled andstatus-api-service's realIngress, this lesson's central comparison point.