{"id":420,"date":"2025-10-04T07:20:00","date_gmt":"2025-10-04T07:20:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=420"},"modified":"2025-10-04T07:20:00","modified_gmt":"2025-10-04T07:20:00","slug":"aws-load-balancer-controller-on-eks","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=420","title":{"rendered":"Setup AWS Load Balancer Controller on EKS (Complete Guide)"},"content":{"rendered":"<p>In this step-by-step guide, you will learn to configure AWS Load Balancer Controller on EKS with detailed workflows and configurations.<\/p>\n<h2 id=\"what-is-aws-load-balancer-controller\">What is AWS Load Balancer Controller?<\/h2>\n<p>The AWS Load Balancer Controller is a Kubernetes controller that manages AWS Elastic Load Balancers for a Kubernetes cluster.<\/p>\n<p>It automatically provisions Application Load Balancers (ALBs) when you create Kubernetes Ingress resources. <\/p>\n<p>Also, it provisions dedicated Network Load Balancers (NLBs) when you create Kubernetes Service resources of type <strong><code>LoadBalancer<\/code><\/strong>.<\/p>\n<p>In short, it is used for exposing Kubernetes services to external traffic using,<\/p>\n<ol>\n<li>Ingress resources via ALB&#8217;s and  <\/li>\n<li>LoadBalancer services using (NLB&#8217;s)<\/li>\n<\/ol>\n<p>The following diagram shows how the workflow of AWS Load Balancer controller.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-52.png\" class=\"kg-image\" alt=\"the workflow diagram of the aws load balancer controller\" loading=\"lazy\" width=\"2000\" height=\"1618\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-52.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-52.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-52.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-52.png 2118w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The Controller operates through a continuous monitoring and reconciliation process.<\/p>\n<p>Here is how it works.<\/p>\n<ol>\n<li>The controller runs as a deployment inside the EKS cluster with required IAM permissions to create\/manage AWS load balancers.<\/li>\n<li>The Controller Pod continuously monitors the <strong>Services<\/strong> and <strong>Ingress<\/strong> objects.<\/li>\n<li>When you create an <strong>ingress<\/strong> object, the controller detects it and will provision an <strong>Application Load Balancer<\/strong> <\/li>\n<li>Also, when you create a <strong>service type LoadBalancer<\/strong>, the controller detects it and will provision a <strong>Network Load Balancer.<\/strong><\/li>\n<\/ol>\n<p>The load balancer to pod traffic has supported modes.<\/p>\n<ol>\n<li><strong>Instance Mode (default): <\/strong>In this mode, the traffic from the load balancers to the pods are routed via Nodeport.<\/li>\n<li><strong>IP Mode: <\/strong>In this mode, the controller registers pod IPs directly as targets in the load balancer&#8217;s target groups. This removes the need for the NodePort.<\/li>\n<\/ol>\n<h2 id=\"setup-prerequisites\">Setup Prerequisites<\/h2>\n<p>The Following are the prerequisites for this setup.<\/p>\n<ol>\n<li><a href=\"https:\/\/devopscube.com\/install-configure-aws-cli-linux\/\" rel=\"noreferrer\">AWS CLI<\/a> should be installed on your local system with the required privileges.<\/li>\n<li>EKSCTL should be on your local system<\/li>\n<li>The Pod Identity Agent addon should be available on the EKS cluster.<\/li>\n<li>Amazon VPC CNI should be available in the EKS cluster (CNI will be available in the cluster by default)<\/li>\n<li><a href=\"https:\/\/devopscube.com\/kubectl-set-context\/\" rel=\"noreferrer\">Kubectl<\/a> should be on your local system<\/li>\n<li><a href=\"https:\/\/devopscube.com\/create-helm-chart\/\" rel=\"noreferrer noopener\">Helm<\/a> should be on your local system<\/li>\n<\/ol>\n<h2 id=\"subnet-configuration\">Subnet Configuration<\/h2>\n<p>As we discussed, the primary job of the controllers is to manage ALB and NLB for EKS clusters. <\/p>\n<p>For the controller to know which subnets in the VPC it can use to create load balancers, you need to tag those subnets.<\/p>\n<p>Here are the tags that you need to add <\/p>\n<ol>\n<li><code>kubernetes.io\/role\/elb = 1<\/code> : Tag added to <strong>public subnets<\/strong> where you are planning  to create <strong>internet-facing<\/strong> ALBs or NLBs.<\/li>\n<li><code>kubernetes.io\/role\/internal-elb = 1<\/code> : Tag Added to <strong>private subnets<\/strong> where you are planning  to create <strong>internal<\/strong> ALBs or NLBs.<\/li>\n<\/ol>\n<p>This way you decide which subnets are allowed for ALB\/NLB creation. It provides good security and network isolation. <\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\">At least two subnets should be tagged for the controller to create the LoadBalancers<\/div>\n<\/div>\n<p>For the examples used in this guide, we are using public subnets. <\/p>\n<p>The following AWS CLI comand is used to add these tags to the subnets that belong to the EKS cluster. Replace <strong><code>&lt;your-cluster-name&gt;<\/code><\/strong> with your EKS cluster name.<\/p>\n<pre><code class=\"language-bash\">export CLUSTER_NAME=&lt;your-cluster-name&gt;\n\nSUBNET_IDS=$(aws eks describe-cluster --name $CLUSTER_NAME --query \"cluster.resourcesVpcConfig.subnetIds\" --output text | tr '\\t' ' ')<\/code><\/pre>\n<pre><code class=\"language-bash\">SUBNET_IDS_ARRAY=($(echo $SUBNET_IDS))<\/code><\/pre>\n<pre><code class=\"language-bash\">for subnet in \"${SUBNET_IDS_ARRAY[@]}\"; do\n    aws ec2 create-tags --resources \"$subnet\" --tags Key=kubernetes.io\/role\/elb,Value=\"1\"\ndone<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\">In actual project setup, you should only tag the subnets that are dedicated for the load balancers. Do not tag all the subnets.<\/div>\n<\/div>\n<p>Ensure the subnets are tagged by validating it from the console as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-127-5.png\" class=\"kg-image\" alt=\"attaching the service discovery tags to subnets to that the aws load balancer controller to identify and provision the load balancer.\" loading=\"lazy\" width=\"973\" height=\"859\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-127-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-127-5.png 973w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note: <\/strong><\/b>Instead of Tagging the subnets, you can also directly provide the subnet names on the Ingress object manifest by passing an annotation <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">alb.ingress.kubernetes.io\/subnets<\/code><\/div>\n<\/div>\n<h2 id=\"step-by-step-controller-installation\">Step-by-Step Controller Installation<\/h2>\n<p>Follow the steps given below for the complete controller setup.<\/p>\n<h3 id=\"step-1-create-an-iam-policy-for-the-load-balancer-controller\">Step 1: Create an IAM Policy for the Load Balancer Controller<\/h3>\n<p>The AWS Load Balancer Controller will run as Pods inside the EKS cluster, and these controller Pods needs IAM permissions to access AWS Services.<\/p>\n<p>Download the <strong>IAM Policy<\/strong> JSON file from the official repo.<\/p>\n<pre><code>curl -o iam-policy.json https:\/\/raw.githubusercontent.com\/kubernetes-sigs\/aws-load-balancer-controller\/v2.14.0\/docs\/install\/iam_policy.json\n<\/code><\/pre>\n<p>Create an <strong>IAM Policy<\/strong> using the <code>iam-policy.json<\/code> file.<\/p>\n<pre><code class=\"language-bash\">export POLICY_NAME=AWSLoadBalancerControllerIAMPolicy<\/code><\/pre>\n<pre><code class=\"language-bash\">aws iam create-policy \\\n    --policy-name ${POLICY_NAME} \\\n    --policy-document file:\/\/iam-policy.json<\/code><\/pre>\n<p>To ensure the creation of the IAM Policy and verify the permissions, we can use the AWS Console and shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-51-5.png\" class=\"kg-image\" alt=\"The required permission for the AWS load balancer controller\" loading=\"lazy\" width=\"2000\" height=\"1625\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-51-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-51-5.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-51-5.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-51-5.png 2056w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, store the <a href=\"https:\/\/devopscube.com\/aws-arn-guide\/\" rel=\"noreferrer\">ARN<\/a> of the created IAM Policy in <code>POLICY_ARN<\/code> variable using the following command for the upcoming configuration.<\/p>\n<pre><code>export POLICY_ARN=$(aws iam list-policies --query \"Policies[?PolicyName=='${POLICY_NAME}'].Arn\" --output text)<\/code><\/pre>\n<h3 id=\"step-2-create-iam-role-trust-policy-for-pod-identity\">Step 2: Create IAM Role (Trust Policy for Pod Identity)<\/h3>\n<p>The IAM Policy with all the required permissions is ready. Next, we will create an <strong>IAM Role<\/strong> and attach that policy to it.<\/p>\n<p>Start by creating a <strong>Trust Policy JSON file<\/strong>, which defines who is allowed to use this role. A <strong>trust policy<\/strong> allows specific AWS services or entities to use the role securely.<\/p>\n<p>In this case, the trust policy will allow only the <strong>Pod Identity Agent<\/strong> to assume this IAM Role.<\/p>\n<pre><code class=\"language-json\">cat &lt;&lt;EOF &gt; trust-policy.json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"pods.eks.amazonaws.com\"\n      },\n      \"Action\": [\n        \"sts:AssumeRole\",\n        \"sts:TagSession\"\n      ]\n    }\n  ]\n}\nEOF<\/code><\/pre>\n<p>Create an<strong> IAM Role<\/strong> <code>AmazonEKSLoadBalancerControllerRole<\/code> with the Trust Policy<\/p>\n<pre><code class=\"language-bash\">aws iam create-role \\\n  --role-name AmazonEKSLoadBalancerControllerRole \\\n  --assume-role-policy-document file:\/\/\"trust-policy.json\"<\/code><\/pre>\n<p>Verify the <strong>IAM Role<\/strong> <code>AmazonEKSLoadBalancerControllerRole<\/code> is created, and the <strong>Trust Policy<\/strong> is properly attached.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-122-5.png\" class=\"kg-image\" alt=\"aws load balancer controller iam role trust relationship for the pod identity agent\" loading=\"lazy\" width=\"2000\" height=\"1163\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-122-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-122-5.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-122-5.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/03\/image-122-5.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Attach <strong>IAM Policy<\/strong> <code>AWSLoadBalancerControllerIAMPolicy<\/code> with the <strong>IAM Role<\/strong> <code>AmazonEKSLoadBalancerControllerRole<\/code> using the folling command .<\/p>\n<pre><code class=\"language-bash\">export ROLE_NAME=AmazonEKSLoadBalancerControllerRole<\/code><\/pre>\n<pre><code class=\"language-bash\">aws iam attach-role-policy \\\n  --policy-arn ${POLICY_ARN} \\\n  --role-name ${ROLE_NAME}<\/code><\/pre>\n<p>The IAM dashboard will help ensure the attachment of the Role and the Policy.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-123-6.png\" class=\"kg-image\" alt=\"the association of the aws load balancer controller iam role and iam policy\" loading=\"lazy\" width=\"2000\" height=\"1268\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-123-6.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-123-6.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-123-6.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/03\/image-123-6.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Use the following command to store the Role ARN as an environment variable for the upcoming configuration.<\/p>\n<pre><code class=\"language-bash\">export ROLE_ARN=$(aws iam get-role --role-name $ROLE_NAME --query \"Role.Arn\" --output text)<\/code><\/pre>\n<h3 id=\"step-4-create-service-account-pod-identity-association\">Step 4: Create Service Account &amp; Pod Identity Association<\/h3>\n<p>In <strong>Step 2<\/strong>, we created a trust policy for the <strong>Pod Identity Agent<\/strong>. This agent runs inside the EKS cluster and is responsible for providing IAM permissions to Pods through their <strong>Service Accounts<\/strong>.<\/p>\n<p>To enable this, we need to bind the <strong>IAM Role<\/strong> that we created to a Kubernetes <strong>Service Account<\/strong> using a <strong>Pod Identity Association.<\/strong><\/p>\n<p>Before creating this association, we will first <strong>create a Service Account<\/strong> for the AWS Load Balancer Controller in the EKS cluster.<\/p>\n<p>Run the following commands to create the Service Account:<\/p>\n<pre><code class=\"language-bash\">export SERVICE_ACCOUNT=aws-load-balancer-controller\nexport NAMESPACE=kube-system\nexport REGION=us-west-2<\/code><\/pre>\n<pre><code class=\"language-yaml\">cat &gt;lbc-sa.yaml &lt;&lt;EOF\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io\/component: controller\n    app.kubernetes.io\/name: aws-load-balancer-controller\n  name: ${SERVICE_ACCOUNT}\n  namespace: ${NAMESPACE}\nEOF<\/code><\/pre>\n<pre><code class=\"language-bash\">kubectl apply -f lbc-sa.yaml<\/code><\/pre>\n<p>To list the available <strong>Service Accounts<\/strong> in the <code>kube-system<\/code> Namespace.<\/p>\n<pre><code class=\"language-bash\">kubectl -n kube-system get sa<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-124-6.png\" class=\"kg-image\" alt=\"the aws load balancer controller pod running status from the kubernetes\" loading=\"lazy\" width=\"952\" height=\"518\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-124-6.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-124-6.png 952w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Before performing the <strong>Pod Identity Association<\/strong>, we need to create the Cluster name as an environment variable and ensure that the <strong>Pod Identity Agent<\/strong> is present in the cluster.<\/p>\n<p>To list the available EKS clusters in a specific region.<\/p>\n<pre><code class=\"language-bash\">aws eks list-clusters --region ${REGION}<\/code><\/pre>\n<p>To create a cluster name as an environment variable. Change &#8220;eks-spot-cluster&#8221; with your cluster name.<\/p>\n<pre><code class=\"language-bash\">export CLUSTER_NAME=eks-spot-cluster<\/code><\/pre>\n<p>To list the available addons in the cluster.<\/p>\n<pre><code class=\"language-bash\">aws eks list-addons --cluster-name $CLUSTER_NAME<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-39-6.png\" class=\"kg-image\" alt=\"the list of addons available in the eks cluster\" loading=\"lazy\" width=\"741\" height=\"364\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-39-6.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-39-6.png 741w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Use the following command if the Pod Identity Agent is unavailable in the cluster.<\/p>\n<pre><code class=\"language-bash\">aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name eks-pod-identity-agent<\/code><\/pre>\n<p>The Service Account is ready; we can perform the Pod Identity Association.<\/p>\n<pre><code class=\"language-bash\">eksctl create podidentityassociation \\\n    --cluster $CLUSTER_NAME \\\n    --namespace $NAMESPACE \\\n    --service-account-name $SERVICE_ACCOUNT \\\n    --role-arn $ROLE_ARN<\/code><\/pre>\n<p>After the successful association, we can list the <strong>Pod Identity Associations<\/strong>.<\/p>\n<pre><code class=\"language-bash\">aws eks list-pod-identity-associations --cluster-name $CLUSTER_NAME<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-125-4.png\" class=\"kg-image\" alt=\"the list of pod identity association from the local terminal\" loading=\"lazy\" width=\"1695\" height=\"885\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-125-4.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-125-4.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-125-4.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-125-4.png 1695w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>This ensures the Pod Identity Association is properly done to the AWS Load Balancer Controller Service Account.<\/p>\n<h3 id=\"step-5-install-controller-with-helm-chart\">Step 5: Install Controller with Helm Chart<\/h3>\n<p>First, we have to add the aws-load-balancer-controller <a href=\"https:\/\/devopscube.com\/create-helm-chart\/\" rel=\"noreferrer\">Helm chart<\/a> using the following command.<\/p>\n<pre><code class=\"language-bash\">helm repo add eks https:\/\/aws.github.io\/eks-charts<\/code><\/pre>\n<p>Update the Helm repository.<\/p>\n<pre><code class=\"language-bash\">helm repo update eks<\/code><\/pre>\n<p>Install the AWS Load Balancer Controller.<\/p>\n<pre><code class=\"language-bash\">helm install aws-load-balancer-controller eks\/aws-load-balancer-controller \\\n  -n kube-system \\\n  --set clusterName=${CLUSTER_NAME} \\\n  --set serviceAccount.create=false \\\n  --set serviceAccount.name=aws-load-balancer-controller <\/code><\/pre>\n<h3 id=\"step-6-verify-deployment-crds\">Step 6: Verify Deployment &amp; CRDs<\/h3>\n<p>To check whether the <strong>Load Balancer Controller<\/strong> has been deployed, use the following command.<\/p>\n<pre><code class=\"language-bash\"> kubectl -n kube-system get all -l app.kubernetes.io\/name=aws-load-balancer-controller<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-28.png\" class=\"kg-image\" alt=\"the status of the load balancer controller pods and service\" loading=\"lazy\" width=\"2000\" height=\"801\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-28.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-28.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-28.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/10\/image-28.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Custom Resource Definitions also be created during the installation.<\/p>\n<pre><code class=\"language-bash\">kubectl get crds | grep -iE \"elbv2\"<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-29.png\" class=\"kg-image\" alt=\"listing the custom resource definitions of the aws load balancer controller\" loading=\"lazy\" width=\"2000\" height=\"517\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-29.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-29.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-29.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/10\/image-29.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The following are the two CRDs used by the LB controller<\/p>\n<ol>\n<li><code>ingressClassParams<\/code> &#8211; Adds optional configurations for the IngressClass.<\/li>\n<li><code>targetgroupbindings<\/code> &#8211; Attach an existing Target Group to the Kubernetes Service to route the traffic.<\/li>\n<\/ol>\n<p>Also, there will be a default ingress class created during the controller installation. Validate it using the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl get ingressclass<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-30.png\" class=\"kg-image\" alt=\"the ingress class of the aws load balancer controller\" loading=\"lazy\" width=\"2000\" height=\"728\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-30.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-30.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-30.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-30.png 2011w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, the setup is completed, we can test it by exposing a demo application using Ingress.<\/p>\n<h2 id=\"expose-an-app-with-ingress-alb\">Expose an App with Ingress (ALB)<\/h2>\n<p>I am deploying an Nginx Deployment object with three replicas for testing purposes.<\/p>\n<pre><code class=\"language-yaml\">cat &gt;nginx-deployment.yaml &lt;&lt;EOF\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: nginx-deployment\n  name: nginx-deployment\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx-deployment\n  template:\n    metadata:\n      labels:\n        app: nginx-deployment\n    spec:\n      containers:\n      - image: nginx\n        name: nginx\n        ports:\n        - containerPort: 80\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: nginx-deployment\n  name: nginx-svc\nspec:\n  ports:\n  - port: 80\n    protocol: TCP\n    targetPort: 80\n  selector:\n    app: nginx-deployment\nEOF<\/code><\/pre>\n<pre><code class=\"language-bash\">kubectl apply -f nginx-deployment.yaml<\/code><\/pre>\n<p>To list the Pods and Services in the current Namespace.<\/p>\n<pre><code class=\"language-bash\">kubectl get po,svc -o wide<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-131-5.png\" class=\"kg-image\" alt=\"the sample deployment to test the aws load balancer controller.\" loading=\"lazy\" width=\"1123\" height=\"480\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-131-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-131-5.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-131-5.png 1123w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The <strong>Service name<\/strong> and the <strong>Port number<\/strong> are required to create the Ingress object.<\/p>\n<p>Now, we can create an <strong>Ingress<\/strong> object to route the external traffic to the Nginx Pod.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; ingress.yaml\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: nginx-ingress\n  namespace: default\n  annotations:\n    alb.ingress.kubernetes.io\/scheme: internet-facing\n    alb.ingress.kubernetes.io\/target-type: ip\nspec:\n  ingressClassName: alb\n  rules:\n    - http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: nginx-svc\n                port:\n                  number: 80\nEOF<\/code><\/pre>\n<p>To create the <strong>Public Load Balancer<\/strong>, the <strong>scheme<\/strong> value should be <strong><code>internet-facing<\/code>.<\/strong><\/p>\n<p>The target type should be <strong><code>ip<\/code><\/strong>, because the application&#8217;s <strong>Service<\/strong> type is <strong>ClusterIP<\/strong> (Internal Communication)<\/p>\n<p>This means that the LB will directly use the <strong>Pod IP addresses<\/strong> to route the traffic.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">For a <b><strong style=\"white-space: pre-wrap;\">NodePort<\/strong><\/b> service, set the <b><strong style=\"white-space: pre-wrap;\">target type<\/strong><\/b> as &#8220;<b><strong style=\"white-space: pre-wrap;\">instance<\/strong><\/b>&#8220;. So, traffic goes from the <b><strong style=\"white-space: pre-wrap;\">Load Balancer<\/strong><\/b> to the <b><strong style=\"white-space: pre-wrap;\">Node&#8217;s Port<\/strong><\/b>, and then reaches the <b><strong style=\"white-space: pre-wrap;\">Pod<\/strong><\/b>.<\/div>\n<\/div>\n<p>To apply the configuration, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f ingress.yaml<\/code><\/pre>\n<p>The <strong>Application Load Balancer<\/strong> will be provisioned when deploying the <strong>Ingress<\/strong> object.<\/p>\n<p>To check this, we can describe the Ingress.<\/p>\n<pre><code class=\"language-bash\">kubectl describe ingress nginx-ingress<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-132-4.png\" class=\"kg-image\" alt=\"the ingress object creation for the demo application with the type of clusterip\" loading=\"lazy\" width=\"1113\" height=\"714\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-132-4.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-132-4.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-132-4.png 1113w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>In the <strong>Backends<\/strong> section of the configuration, we can see that all three <strong>Pod IPs<\/strong> are mapped.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">The Load Balancer Controller will stay in sync with the Ingress object. So, if any Pod is deleted, a new one starts, and the <b><strong style=\"white-space: pre-wrap;\">new Pod&#8217;s IP<\/strong><\/b> will automatically be updated in the Load Balancer target.<\/div>\n<\/div>\n<p>We can ensure the Application Load Balancer is provisioned by using the AWS Console.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-133-3.png\" class=\"kg-image\" alt=\"the load balancer information from the aws console and the resource mapping\" loading=\"lazy\" width=\"1417\" height=\"875\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-133-3.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-133-3.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-133-3.png 1417w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The <strong>resource map<\/strong> section of the Application Load Balancer clearly shows how the traffic flows.<\/p>\n<p>When request come through the Load Balancer, it goes to the <strong>target group<\/strong> and routes to the <strong>Pod&#8217;s IP addresses.<\/strong><\/p>\n<p>But, what if we have more than one Ingress objects but want to utilize a single Load Balancer.<\/p>\n<h2 id=\"share-one-alb-with-ingress-groups\">Share One ALB with Ingress Groups<\/h2>\n<p>The <strong>Ingress Group<\/strong> feature allow to use one <strong>Load Balancer<\/strong> for <strong>multiple Ingress<\/strong> resources.<\/p>\n<p>The following diagram explains how we can use a single Load Balancer for two set of applications.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-32.png\" class=\"kg-image\" alt=\"the workflow diagram of the aws load balancer controller ingress group function\" loading=\"lazy\" width=\"2000\" height=\"1644\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-32.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-32.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-32.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-32.png 2228w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The above diagram explains<\/p>\n<ul>\n<li>If you create multiple Ingress objects with same value for the <strong>group annotation<\/strong>, Load Balancer controller knows that they have to bind together for single Load Balancer.<\/li>\n<li>When it creates the Load Balancer, it add multiple targets based on the host names that we provided on each Ingress.<\/li>\n<li>This is how, when traffic comes to Load Balancer, it goes to the correct destination.<\/li>\n<\/ul>\n<p>To create an <strong>Ingress Group<\/strong>, add this annotation to a new or existing Ingress object.<\/p>\n<p><code>metadata.annotations.alb.ingress.kubernetes.io\/group.name<\/code><\/p>\n<p>This annotation tells the <strong>AWS Load Balancer Controller<\/strong> to group the specific ingress resources.<\/p>\n<p>For demo, creating two different deployments in two different namespaces and, this time, providing the Service type as <code>NodePort<\/code> <\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; 'EOF' &gt; group-demo-deployment.yaml\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: app1\n---\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: app1\n  namespace: app1\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: app1\n  template:\n    metadata:\n      labels:\n        app: app1\n    spec:\n      containers:\n      - name: app1\n        image: nginx\n        ports:\n        - containerPort: 80\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: app1\n  namespace: app1\nspec:\n  selector:\n    app: app1\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 80\n  type: NodePort\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: app2\n---\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: app2\n  namespace: app2\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: app2\n  template:\n    metadata:\n      labels:\n        app: app2\n    spec:\n      containers:\n      - name: app2\n        image: httpd\n        ports:\n        - containerPort: 80\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: app2\n  namespace: app2\nspec:\n  selector:\n    app: app2\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 80\n  type: NodePort\nEOF<\/code><\/pre>\n<pre><code class=\"language-bash\">kubectl apply -f group-demo-deployment.yaml<\/code><\/pre>\n<p>Use the following command to list the Pods, Deployments, and Services.<\/p>\n<pre><code class=\"language-bash\">kubectl -n app1 get po,deploy,svc\nkubectl -n app2 get po,deploy,svc<\/code><\/pre>\n<p>On the above deployment, the service type is Node Port so when we create Ingress, we need to set the target type as &#8220;instance&#8221;<\/p>\n<p>Create Ingress objects for each Namespace.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; group-ingress.yaml\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: nginx-ingress\n  namespace: app1\n  annotations:\n    alb.ingress.kubernetes.io\/group.name: common-ingress-group\n    alb.ingress.kubernetes.io\/scheme: internet-facing\n    alb.ingress.kubernetes.io\/target-type: instance\nspec:\n  ingressClassName: alb\n  rules:\n    - host: app1.techiescamp.com\n      http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: app1\n                port:\n                  number: 80\n---\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: httpd-ingress\n  namespace: app2\n  annotations:\n    alb.ingress.kubernetes.io\/group.name: common-ingress-group\n    alb.ingress.kubernetes.io\/scheme: internet-facing\n    alb.ingress.kubernetes.io\/target-type: instance\nspec:\n  ingressClassName: alb\n  rules:\n    - host: app2.techiescamp.com\n      http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: app2\n                port:\n                  number: 80\nEOF<\/code><\/pre>\n<p>To route the traffic between Pods, we use the domain names.<\/p>\n<p>For the App1 Deployment <code>app1.techiescamp.com<\/code> and the App2 Deployment <code>app2.techiescamp.com<\/code><\/p>\n<pre><code class=\"language-bash\">kubectl apply -f group-ingress.yaml<\/code><\/pre>\n<p>To list the Ingress object.<\/p>\n<pre><code class=\"language-bash\">kubectl -n app1 get ingress\nkubectl -n app2 get ingress<\/code><\/pre>\n<p>Normally, if we create multiple Ingress objects, the controller creates <strong>seperate ALB for each one<\/strong> which is expensive and unnecessary.<\/p>\n<p>With Ingress grouping, we can combine those ingress objects so they share a single load balancer.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-135-5.png\" class=\"kg-image\" alt=\"listing the ingress objects related to the two applications \" loading=\"lazy\" width=\"1455\" height=\"468\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-135-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-135-5.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-135-5.png 1455w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Here, we can see that the DNS name of the Load Balancer is the same for both Ingress objects.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-4-14.png\" class=\"kg-image\" alt=\"the load balancer creation output for the two applications\" loading=\"lazy\" width=\"1187\" height=\"867\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-4-14.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-4-14.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-4-14.png 1187w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The number of the target groups is based on the number of Ingress objects.<\/p>\n<p>The traffic route to these Target groups is based on the hostnames we provided in the Ingress object, which we can view in the AWS console.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-49.png\" class=\"kg-image\" alt=\"the listener details of the application load balancer.\" loading=\"lazy\" width=\"1110\" height=\"591\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-49.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-49.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-49.png 1110w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>We can use the resource map section to see detailed traffic routing information.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-6-12.png\" class=\"kg-image\" alt=\"the resource map details of the provisioned application load balancer.\" loading=\"lazy\" width=\"1304\" height=\"861\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-6-12.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-6-12.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-6-12.png 1304w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>External traffic from the <strong>Load Balancer<\/strong> is send to the <strong>Target groups<\/strong> based on the <strong>Hostname<\/strong>.<\/p>\n<p>The <strong>EKS Nodes<\/strong> are linked to these <strong>Target groups<\/strong>, so traffic goes to the correct  <strong>NodePort<\/strong> that connects to the Service.<\/p>\n<p>From there, the traffic is routed on to the Pod.<\/p>\n<p>To test the traffic routing practically, we have to map the host names to the IP address of the Application Load Balancer.<\/p>\n<p>To get the Application Load Balancer DNS.<\/p>\n<pre><code class=\"language-bash\">ALB_DNS=$(kubectl -n nginx get ingress nginx-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')<\/code><\/pre>\n<p>To get the IP address of the ALB.<\/p>\n<pre><code class=\"language-bash\">dig +short ${ALB_DNS}<\/code><\/pre>\n<p>Add the IP addresses to the <code>\/etc\/hosts<\/code> file with the host names.<\/p>\n<pre><code class=\"language-bash\">vim \/etc\/hosts<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-33.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"2000\" height=\"673\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-33.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-33.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-33.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/10\/image-33.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Save and exit.<\/p>\n<p>The configurations are done; we can use the host names over the browser to get the output.<\/p>\n<p>First, let&#8217;s check with <code>http:\/\/app1.techiescamp.com<\/code><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-46.png\" class=\"kg-image\" alt=\"the demo nginx application output from the browser of the local machine.\" loading=\"lazy\" width=\"1582\" height=\"610\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-46.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-46.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-46.png 1582w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, we can check with the other hostname <code>http:\/\/app2.techiescamp.com<\/code><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-47.png\" class=\"kg-image\" alt=\"The output of the second demo application\" loading=\"lazy\" width=\"559\" height=\"194\"><\/figure>\n<p>Now, we know that how we can use a single LB for multiple ingress objects. <\/p>\n<p>In the next section, we can learn how we can properly use multiple Load Balancers for different environments.<\/p>\n<h2 id=\"manage-multiple-albs-with-ingressclassparams\">Manage Multiple ALBs with IngressClassParams<\/h2>\n<p>We can run <strong>multiple Load Balancers<\/strong> using the AWS Load Balancer Controller. <\/p>\n<p>By default, each ingress object gets its own ALB, unless we use the group annotation.<\/p>\n<p>To manage seperate ALB for different environments, like &#8220;dev&#8221; and &#8220;prod&#8221;, we can use <strong>different Ingress Classes<\/strong>, and each has its own <strong>class parameters<\/strong>.<\/p>\n<p><strong><code>IngressClasses<\/code><\/strong> and <strong><code>IngressClassParams<\/code><\/strong> are the two CRDs of the AWS LB controller.<\/p>\n<p>What actually does is,<\/p>\n<ul>\n<li>Can create <strong>IngressClasses<\/strong> (e.g., <code>alb-dev<\/code> and <code>alb-prod<\/code>) instead of the default (<code>alb<\/code>) one.<\/li>\n<li>Attach <strong>IngressClassParams<\/strong> to each to set of things such as, scheme, IP address type, namespace selector, tags, ingress group and other LB attributes.<\/li>\n<\/ul>\n<p>Let us do a hands on of this.<\/p>\n<p>Assume we have two namespaces, <strong>dev<\/strong> and <strong>prod<\/strong>.<\/p>\n<p>We will create its <strong>own Ingress configurations<\/strong> for each one so both gets its <strong>own isolated Load Balancers.<\/strong><\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; ingress-class-params.yaml\napiVersion: elbv2.k8s.aws\/v1beta1\nkind: IngressClassParams\nmetadata:\n  name: dev-class-params\nspec:\n  namespaceSelector:        \n    matchLabels:\n      kubernetes.io\/metadata.name: dev\n  scheme: internet-facing\n  ipAddressType: ipv4\n  loadBalancerAttributes:\n  - key: deletion_protection.enabled\n    value: \"false\"\n  - key: idle_timeout.timeout_seconds\n    value: \"120\"\n\n---\napiVersion: elbv2.k8s.aws\/v1beta1\nkind: IngressClassParams\nmetadata:\n  name: prod-class-params\nspec:\n  namespaceSelector:        \n    matchLabels:\n      kubernetes.io\/metadata.name: prod\n  scheme: internet-facing\n  ipAddressType: ipv4\n  loadBalancerAttributes:\n  - key: deletion_protection.enabled\n    value: \"true\"\n  - key: idle_timeout.timeout_seconds\n    value: \"160\"\nEOF<\/code><\/pre>\n<p>You can refer to this <a href=\"https:\/\/docs.aws.amazon.com\/elasticloadbalancing\/latest\/application\/application-load-balancers.html?ref=devopscube.com#load-balancer-attributes\">official documentation<\/a> to know the list of Load Balancer attributes.<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f ingress-class-params.yaml<\/code><\/pre>\n<p>To list the Ingress Class Params, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl get ingressclassparams.elbv2.k8s.aws<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-47-4.png\" class=\"kg-image\" alt=\"listing the ingress class params custom resources objects of the aws load balancer controller\" loading=\"lazy\" width=\"1063\" height=\"452\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-47-4.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-47-4.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-47-4.png 1063w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, we can create the <strong>Ingress Class<\/strong> objects with these <strong>Ingress Class Params<\/strong>.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; ingress-class.yaml\napiVersion: networking.k8s.io\/v1\nkind: IngressClass\nmetadata:\n  name: dev-ingress-class\nspec:\n  controller: ingress.k8s.aws\/alb\n  parameters:\n    apiGroup: elbv2.k8s.aws\n    kind: IngressClassParams\n    name: dev-class-params\n\n---\napiVersion: networking.k8s.io\/v1\nkind: IngressClass\nmetadata:\n  name: prod-ingress-class\nspec:\n  controller: ingress.k8s.aws\/alb\n  parameters:\n    apiGroup: elbv2.k8s.aws\n    kind: IngressClassParams\n    name: prod-class-params\nEOF<\/code><\/pre>\n<pre><code class=\"language-bash\">kubectl apply -f ingress-class.yaml<\/code><\/pre>\n<p>To list the deployed Ingress Class, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl get ingressclass<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-48.png\" class=\"kg-image\" alt=\"listing the ingress classes of the aws load balancer controller\" loading=\"lazy\" width=\"2000\" height=\"518\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-48.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-48.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/10\/image-48.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/10\/image-48.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The Ingress Classes are ready, so we can create <strong>Ingress<\/strong> objects with these <strong>Ingress Classes.<\/strong><\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; custom-ingress.yaml\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: dev-ingress\n  namespace: dev\n  annotations:\n    alb.ingress.kubernetes.io\/target-type: instance\nspec:\n  ingressClassName: dev-ingress-class\n  rules:\n    - host: dev.techiescamp.com\n      http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: dev\n                port:\n                  number: 80\n---\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: prod-ingress\n  namespace: prod\n  annotations:\n    alb.ingress.kubernetes.io\/target-type: ip\nspec:\n  ingressClassName: prod-ingress-class\n  rules:\n    - host: prod.techiescamp.com\n      http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: prod\n                port:\n                  number: 80\nEOF<\/code><\/pre>\n<p>Here, you can see that the <strong><code>spec.ingressClassName<\/code><\/strong> is replaced with custom Ingress Classes for each Ingress.<\/p>\n<p>On the next section, we can see that how the AWS LBC works with the CNIs.<\/p>\n<h2 id=\"using-other-cnis-calicocilium-with-aws-load-balancer-controller\">Using Other CNIs (Calico\/Cilium) with AWS Load Balancer Controller<\/h2>\n<p>EKS uses <strong>VPC CNI <\/strong>by default, but we can use other CNIs such as <strong>Calico, Cilium, and Antrea.<\/strong><\/p>\n<p>If we use <strong>Calico<\/strong>, it creates an overlay network that is not part of the VPC. Because of this, a <strong>ClusterIP<\/strong> Service cannot reached by the AWS Load Balancer.<\/p>\n<p>Instead if we use a <strong>NodePort<\/strong> <strong>service<\/strong>, we can still be able to route the traffic to Pods.<\/p>\n<p>The LB sends the traffic to <strong>Node&#8217;s VPC IP<\/strong>, and the node route the traffic through the <strong>NodePort<\/strong> to the Pod.<\/p>\n<blockquote><p>Note: <strong>AWS Load Balancer Controller<\/strong> replaced the old <strong>ALB Ingress Controller<\/strong>.<\/p>\n<p><strong>ALB Ingress Controller<\/strong> support only the <strong>Application Load Balancer<\/strong> but the AWS Load Balancer Controller can able to provision Application\/Network Load Balancer.<\/p>\n<p>If you want to migrate from the ALB Ingress Controller to the AWS Load Balancer Controler, you can follow this <a href=\"https:\/\/kubernetes-sigs.github.io\/aws-load-balancer-controller\/v2.4\/deploy\/upgrade\/migrate_v1_v2\/?ref=devopscube.com\">official documentation.<\/a><\/p><\/blockquote>\n<h2 id=\"possible-errors\">Possible Errors<\/h2>\n<h3 id=\"error-failed-to-fetch-vpc-id-from-instance-metadata\">Error: failed to fetch VPC ID from instance metadata<\/h3>\n<p>This error occurs when the AWS Load Balancer Controller tries to fetch the VPC ID using the AWS Metadata Service.<\/p>\n<p>The default hop limit for <strong>IMDSv2<\/strong> is 1, but the controller pod runs inside the cluster as a Pod.<\/p>\n<p>So, then update the hop limit of the worker nodes of the EKS cluster using the parameter of <code>http_put_response_hop_limit<\/code><\/p>\n<pre><code>aws ec2 modify-instance-metadata-options \\\n  --instance-id i-0abcd1234efgh5678 \\\n  --http-put-response-hop-limit 2\n<\/code><\/pre>\n<p>Change the instance ID with yours.<\/p>\n<p>After you modify the settings of the nodes, restart the controller.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>This will give you a high-level overview of the AWS Load Balancer controller and how we can utilize the resource of a Load Balancer with multiple Ingress Objects.<\/p>\n<p>The annotations allow you to do more, so if you want to know more about the Load Balancer Controller, please visit the <a href=\"https:\/\/kubernetes-sigs.github.io\/aws-load-balancer-controller\/v2.2\/guide\/ingress\/annotations\/?ref=devopscube.com\">official documentation.<\/a><\/p>\n<p>To perform the blue-green deployment with the AWS Load Balancer controller, refer to this <a href=\"https:\/\/aws.amazon.com\/blogs\/containers\/using-aws-load-balancer-controller-for-blue-green-deployment-canary-deployment-and-a-b-testing\/?ref=devopscube.com\" rel=\"noreferrer\">official Documentation<\/a>.<\/p>\n<p>If you want to automate the Blue Green, you can use it with <a href=\"https:\/\/argoproj.github.io\/argo-rollouts\/features\/traffic-management\/alb\/?ref=devopscube.com\" rel=\"noreferrer\">Argo Rollouts.<\/a><\/p>\n<p>In the upcoming blog post, I will explain how to manually and automatically configure the DNS record on Route53 for Service Objects and create and attach TLS certificates through the AWS Certificate Manager.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/aws-load-balancer-controller-on-eks\/\" target=\"_blank\" rel=\"noopener noreferrer\">Setup AWS Load Balancer Controller on EKS (Complete Guide) \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/aws-load-balancer-controller-on-eks\/<\/p>\n","protected":false},"author":1,"featured_media":421,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-420","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/420","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=420"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/420\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/421"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=420"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=420"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=420"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}