Deploy AWS Load Balancer Controller with Multi-Configuration Terraform and Helm

Amazon EKS excels at running containerized workloads, but getting traffic to them requires thoughtful load-balancer architecture. The AWS Load Balancer Controller bridges this gap by automatically provisioning Application Load Balancers (ALBs) and Network Load Balancers (NLBs) based on Kubernetes resource definitions, eliminating manual infrastructure management.

In this article, I’ll deploy the AWS Load Balancer Controller using a multi-configuration Terraform approach that separates infrastructure from platform components. You’ll learn how to leverage Helm for platform component deployment, use SSM parameters for cross-configuration communication, and validate the setup by deploying a sample application behind an ALB using GitHub Actions.

Solution Overview

This solution introduces a multi-configuration Terraform architecture where infrastructure and platform components are managed in separate state files. The infrastructure configuration must be deployed first since the platform configuration depends on it. Once both are established, the platform configuration can be updated or rolled back independently without affecting the infrastructure. However, changes to the infrastructure configuration may impact the platform configuration. The infrastructure configuration (covered in Provision a Secure Amazon EKS Cluster using Terraform and GitHub Actions) stores its outputs (cluster name, endpoint, certificate authority data, and IAM role ARNs) in an encrypted SSM parameter, which the platform configuration reads at terraform plan command execution time.

With this architecture in place, the platform configuration deploys the AWS Load Balancer Controller, a Kubernetes pod that watches for Ingress and Service resources and automatically provisions the corresponding ALB or NLB. Since the controller runs as a pod, it uses Pod Identity to authenticate with AWS, which requires an IAM role to be created and associated with the pod beforehand. I defined this role in the infrastructure configuration alongside the other IAM roles and Pod Identity associations from the previous article, keeping all identity management in one place. The platform configuration then installs the controller via Helm, confident that the permissions are already in place.
AWS Load Balancer Controller multi-configuration Terraform architecture diagram
To implement this solution, I organized the use-case into three stages:

1. Infrastructure configuration: Create the IAM role for the Load Balancer Controller and associate it using Pod Identity
2. Platform configuration: Install the AWS Load Balancer Controller using the Helm provider
3. Application deployment: Create a sample application with an Ingress resource and a GitHub Actions workflow that uses kubectl to deploy it, verifying that the controller automatically provisions an ALB and configures traffic routing

You can find the complete implementation in my GitHub repository: kunduso-org/aws-eks-terraform (branch: platform-components). The code includes Terraform configurations, GitHub Actions CI/CD, and security scanning with Checkov.

Prerequisites

This use case builds on Provision a Secure Amazon EKS Cluster using Terraform and GitHub Actions, which covers all the foundational infrastructure resources. Please go through that before starting here.

Implementation

Step 1: Create the IAM role for the Load Balancer Controller
The Load Balancer Controller authenticates to AWS using Pod Identity, the same mechanism used by the EBS CSI driver in the previous article. This requires an IAM role with a trust policy that allows pods.eks.amazonaws.com to assume it, and the controller-specific permissions attached as an inline policy.

I created the IAM role in the infrastructure configuration alongside the other service roles, keeping all identity management in one place. The trust policy includes both sts:AssumeRole and sts:TagSession actions, which Pod Identity requires for session tagging.

For the permissions, I referenced the official IAM policy from the AWS documentation and stored it as lb-controller-iam-policy.json in the repository. This policy grants permissions to create and manage ALBs, NLBs, target groups, listeners, and security groups — everything the controller needs to provision load balancers on your behalf. Storing the policy as a separate JSON file keeps the Terraform code readable and makes it straightforward to update when new controller versions require additional permissions.

Finally, I created the Pod Identity association in the same file, binding this role to the aws-load-balancer-controller service account in the kube-system namespace. This is the same pattern used for the EBS CSI driver. The association tells EKS which IAM role to inject into pods running under that service account. The Helm chart in Step 2 creates the service account with this exact name, completing the link between the Kubernetes workload and AWS permissions.

Step 2: Install the AWS Load Balancer Controller using the Helm provider
The Terraform code for Load Balancer Controller is in the platform configuration, a separate Terraform state file with its own folder, backend, and deployment workflow. This separation means platform components can be updated or rolled back independently without risking the underlying cluster infrastructure.

2.1 Configure the platform resources
The platform configuration needs to authenticate with both AWS and the Kubernetes API. To avoid hard-coding cluster details, I read the encrypted SSM parameter created in the previous article and decode it into a local variable. This gives the platform configuration access to the cluster name, endpoint, certificate authority data, VPC ID, region, and IAM role ARNs — everything it needs without a direct dependency on the infrastructure state file.

The Helm provider uses these values to connect to the cluster: host from the cluster endpoint, cluster_ca_certificate decoded from base64, and token from the aws_eks_cluster_auth data source.

2.2 Deploy the Helm release
With the provider configured, I installed the AWS Load Balancer Controller using the helm_release resource. The chart comes from the AWS EKS charts repository at version 3.0.0, deployed into the kube-system namespace.

The chart values pass the cluster name, VPC ID, and region, so the controller knows which AWS resources to manage. The serviceAccount.name is set to aws-load-balancer-controller, matching the Pod Identity association from Step 1. This is what completes the authentication chain: EKS sees a pod running under that service account, finds the matching Pod Identity association, and injects the IAM role credentials.

Step 3: Create a sample application with an Ingress resource
To validate that the Load Balancer Controller is working, I created a sample nginx application in the application/ folder. This folder contains three Kubernetes manifests and sits alongside the infrastructure/ and platform/ folders in the repository.

3.1 Create the Deployment
The Deployment runs two replicas of nginx from the public.ecr.aws/nginx/nginx:1.27 image. Each pod has resource requests and limits defined to ensure predictable scheduling on the worker nodes.

3.2 Create the Service
The Service uses ClusterIP type, which means it is only reachable from within the cluster. It selects pods with the label app: nginx-app and exposes them on port 80. The ALB routes directly to pod IPs (because of the target-type: ip annotation on the Ingress), but the Ingress still references this Service by name to discover which pods to target.

3.3 Create the Ingress
The Ingress resource is what triggers the Load Balancer Controller. When the controller sees an Ingress with ingressClassName: alb, it provisions an Application Load Balancer in the public subnets. The annotations configure the ALB as internet-facing with IP-based target routing, and the rules direct all traffic on path / to the nginx-app Service on port 80.

Deployment

This use case uses three GitHub Actions workflows, one for each configuration. All three authenticate to AWS via OIDC and run on merge to the main branch.

The first workflow (terraform.yml) deploys the infrastructure configuration. While the bulk of infrastructure resources were created and discussed in the previous article, this use case adds the Load Balancer Controller IAM role, the IAM policy, and the Pod Identity association to that same configuration.

The second workflow (deploy-platform.yml) deploys the platform configuration. It runs terraform apply against the platform/ folder, which reads the infrastructure outputs from SSM and installs the Load Balancer Controller via Helm.

The third workflow (deploy-app.yml) deploys the sample application. It updates the kubeconfig to connect to the cluster and runs kubectl apply against the application/ folder. After deployment, it waits for the rollout to complete and prints the ALB hostname.

This repository also includes a code-scanning pipeline (code-scan.yml) using Checkov to scan Terraform configurations against security best practices before deployment. For detailed implementation of Checkov with GitHub Actions, see: automate-terraform-configuration-scan-with-checkov-and-github-actions.

Validation

After deploying all three configurations, I validated the setup by confirming each layer is functioning correctly.

The AWS Load Balancer Controller pods are running in the kube-system namespace, confirming that the Helm install succeeded, and the pods are healthy.

The Ingress resource shows the ALB hostname populated in the ADDRESS column, confirming that the controller detected the Ingress and provisioned the corresponding load balancer.

On the AWS console, the Application Load Balancer is active with the target group routing traffic to the pod IPs in the private subnets.

Finally, hitting the ALB URL in the browser returns the nginx welcome page, confirming end-to-end traffic flow from the internet through the ALB to the pods running in the cluster.

Conclusion

In this note, I walked through deploying the AWS Load Balancer Controller on Amazon EKS using a multi-configuration Terraform approach — from creating the IAM role and Pod Identity association in the infrastructure configuration, through installing the controller via Helm in the platform configuration, to validating the setup with a sample nginx application behind an ALB. The multi-configuration pattern keeps infrastructure and platform components independently deployable, with SSM Parameter Store bridging the two without shared state files.

In the next article, I’ll build on this by adding auto-scaling capabilities with Karpenter and Metrics Server, enabling the cluster to scale both pods and nodes based on demand.

If you have any questions or suggestions, feel free to comment or get in touch.

One thought on “Deploy AWS Load Balancer Controller with Multi-Configuration Terraform and Helm

Leave a Reply