{"id":165780,"date":"2026-04-12T11:49:50","date_gmt":"2026-04-12T08:49:50","guid":{"rendered":"https:\/\/computingforgeeks.com\/aws-load-balancer-controller-eks-guide\/"},"modified":"2026-04-12T23:20:53","modified_gmt":"2026-04-12T20:20:53","slug":"aws-load-balancer-controller-eks-guide","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/aws-load-balancer-controller-eks-guide\/","title":{"rendered":"Install AWS Load Balancer Controller on EKS (2026 Guide)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Every Kubernetes workload that needs external traffic eventually hits the same question: how do ALBs and NLBs get created? On EKS, the AWS Load Balancer Controller handles this automatically. It watches for Ingress resources and Service objects, then calls the AWS API to provision Application Load Balancers or Network Load Balancers with the right target groups, listeners, and security group rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide walks through the full setup: IAM policy, IRSA role, Helm install, and a working ALB Ingress that serves traffic. We also cover NLB for TCP workloads, HTTPS with ACM certificates, TargetGroupBinding for existing infrastructure, and the errors you will actually hit in production. If you have used <a href=\"https:\/\/computingforgeeks.com\/iam-roles-for-service-accounts-irsa-eks-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">IRSA on EKS<\/a> before, the IAM plumbing will feel familiar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Tested April 2026 | EKS 1.33 (v1.33.8-eks-f69f56f), AWS Load Balancer Controller v2.13.1, eu-west-1<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before starting, confirm the following are in place:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A running EKS cluster (1.27 or later) with <code>kubectl<\/code> configured<\/li>\n<li>An OIDC provider associated with the cluster (required for IRSA)<\/li>\n<li>AWS CLI v2 and Helm 3 installed on your workstation<\/li>\n<li>IAM permissions to create policies and roles<\/li>\n<li>Tested on: EKS 1.33.8, Helm 3.17, AWS CLI 2.27<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the OIDC provider exists for your cluster:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>aws eks describe-cluster --name cfg-lab-eks --query \"cluster.identity.oidc.issuer\" --output text<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a URL like this:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>https:\/\/oidc.eks.eu-west-1.amazonaws.com\/id\/A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If that returns empty, create the OIDC provider first with <code>eksctl utils associate-iam-oidc-provider --cluster cfg-lab-eks --approve<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How the Controller Works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The AWS Load Balancer Controller runs as a Deployment in <code>kube-system<\/code>. It uses Kubernetes informers to watch for Ingress resources (with <code>ingressClassName: alb<\/code>) and Services of type LoadBalancer. When it detects one, it calls the AWS Elastic Load Balancing API to create the corresponding ALB or NLB, configure target groups, register pod IPs, and set up health checks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The controller needs AWS API access, which it gets through an IAM role attached via IRSA. No access keys are stored in the cluster. The trust relationship between the Kubernetes service account and the IAM role is handled by the OIDC provider.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create the IAM Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Download the official IAM policy document. This policy grants the controller permissions to manage ALBs, NLBs, target groups, security groups, and WAF associations:<\/p>\n\n<!-- \/wp:post-content -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>curl -o iam_policy.json https:\/\/raw.githubusercontent.com\/kubernetes-sigs\/aws-load-balancer-controller\/v2.13.1\/docs\/install\/iam_policy.json<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Create the IAM policy from the downloaded document:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>aws iam create-policy \\\n  --policy-name AWSLoadBalancerControllerIAMPolicy \\\n  --policy-document file:\/\/iam_policy.json<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The output confirms the policy ARN:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>{\n    \"Policy\": {\n        \"PolicyName\": \"AWSLoadBalancerControllerIAMPolicy\",\n        \"PolicyId\": \"ANPA3EXAMPLE7POLICY\",\n        \"Arn\": \"arn:aws:iam::ACCOUNT_ID:policy\/AWSLoadBalancerControllerIAMPolicy\",\n        \"Path\": \"\/\",\n        \"DefaultVersionId\": \"v1\",\n        \"AttachmentCount\": 0,\n        \"CreateDate\": \"2026-04-10T14:22:31+00:00\"\n    }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading -->\n<h2>Create the IRSA Role<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The controller needs an IAM role that trusts the EKS OIDC provider. This is the <a href=\"https:\/\/computingforgeeks.com\/iam-roles-for-service-accounts-irsa-eks-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">IRSA pattern<\/a>: the Kubernetes service account gets annotated with the role ARN, and the OIDC provider validates the token at runtime.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>First, grab the OIDC ID from your cluster:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>OIDC_ID=$(aws eks describe-cluster --name cfg-lab-eks --query \"cluster.identity.oidc.issuer\" --output text | sed 's|https:\/\/||')<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Create a trust policy that allows the <code>aws-load-balancer-controller<\/code> service account in <code>kube-system<\/code> to assume this role:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>cat > trust-policy.json &lt;&lt;'TRUST'\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::ACCOUNT_ID:oidc-provider\/OIDC_PROVIDER_URL\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"OIDC_PROVIDER_URL:aud\": \"sts.amazonaws.com\",\n          \"OIDC_PROVIDER_URL:sub\": \"system:serviceaccount:kube-system:aws-load-balancer-controller\"\n        }\n      }\n    }\n  ]\n}\nTRUST<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Replace <code>ACCOUNT_ID<\/code> with your AWS account ID and <code>OIDC_PROVIDER_URL<\/code> with the value from the <code>$OIDC_ID<\/code> variable. Then create the role and attach the policy:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>aws iam create-role \\\n  --role-name AmazonEKSLoadBalancerControllerRole \\\n  --assume-role-policy-document file:\/\/trust-policy.json\n\naws iam attach-role-policy \\\n  --role-name AmazonEKSLoadBalancerControllerRole \\\n  --policy-arn arn:aws:iam::ACCOUNT_ID:policy\/AWSLoadBalancerControllerIAMPolicy<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading -->\n<h2>Install via Helm<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Add the EKS Helm chart repository and install the controller:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>helm repo add eks https:\/\/aws.github.io\/eks-charts\nhelm repo update<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Install the chart with IRSA configuration:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>helm install aws-load-balancer-controller eks\/aws-load-balancer-controller \\\n  -n kube-system \\\n  --set clusterName=cfg-lab-eks \\\n  --set serviceAccount.create=true \\\n  --set serviceAccount.name=aws-load-balancer-controller \\\n  --set serviceAccount.annotations.\"eks\\.amazonaws\\.com\/role-arn\"=arn:aws:iam::ACCOUNT_ID:role\/AmazonEKSLoadBalancerControllerRole \\\n  --set region=eu-west-1 \\\n  --set vpcId=vpc-0a1b2c3d4e5f67890<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Verify two controller pods are running in <code>kube-system<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl get pods -n kube-system -l app.kubernetes.io\/name=aws-load-balancer-controller<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Both pods should show <code>Running<\/code> with 1\/1 ready:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>NAME                                            READY   STATUS    RESTARTS   AGE\naws-load-balancer-controller-6b8d9c7f4d-k2x9n   1\/1     Running   0          45s\naws-load-balancer-controller-6b8d9c7f4d-m7p3q   1\/1     Running   0          45s<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The controller image is <code>public.ecr.aws\/eks\/aws-load-balancer-controller:v3.2.1<\/code> (shipped with chart version 1.13.1).<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Deploy a Test App with ALB Ingress<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Create a namespace and deploy a simple nginx application to test the ALB provisioning:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl create namespace alb-demo<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Apply the Deployment and Service:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl apply -f - &lt;&lt;'EOF'\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: nginx-demo\n  namespace: alb-demo\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: nginx-demo\n  template:\n    metadata:\n      labels:\n        app: nginx-demo\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:1.27\n        ports:\n        - containerPort: 80\n        resources:\n          requests:\n            cpu: 100m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-demo-svc\n  namespace: alb-demo\nspec:\n  type: ClusterIP\n  selector:\n    app: nginx-demo\n  ports:\n  - port: 80\n    targetPort: 80\n    protocol: TCP\nEOF<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Now create the Ingress resource. The annotations tell the controller to create an internet-facing ALB with IP-mode targets:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl apply -f - &lt;&lt;'EOF'\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: nginx-alb-ingress\n  namespace: alb-demo\n  annotations:\n    alb.ingress.kubernetes.io\/scheme: internet-facing\n    alb.ingress.kubernetes.io\/target-type: ip\n    alb.ingress.kubernetes.io\/healthcheck-path: \/\nspec:\n  ingressClassName: alb\n  rules:\n  - http:\n      paths:\n      - path: \/\n        pathType: Prefix\n        backend:\n          service:\n            name: nginx-demo-svc\n            port:\n              number: 80\nEOF<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>After about 2 minutes, the ALB should be provisioned. Check the Ingress for its DNS name:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl get ingress -n alb-demo<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The ADDRESS column shows the ALB DNS name:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>NAME                CLASS   HOSTS   ADDRESS                                                                      PORTS   AGE\nnginx-alb-ingress   alb     *       k8s-albdemo-nginxalb-EXAMPLE-1234567890.eu-west-1.elb.amazonaws.com          80      2m15s<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Curl the ALB endpoint to confirm traffic is flowing:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>curl -s -o \/dev\/null -w \"%{http_code}\" http:\/\/k8s-albdemo-nginxalb-EXAMPLE-1234567890.eu-west-1.elb.amazonaws.com<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>A <code>200<\/code> response means the ALB, target group, and pod targets are all wired up correctly. In the AWS console, you will see a target group named something like <code>k8s-albdemo-nginxdem-40e93a20d3<\/code> with your pod IPs registered on port 80 using the HTTP protocol.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>NLB for TCP\/UDP Workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>For TCP or UDP services (databases, gRPC, custom protocols), use a Network Load Balancer instead. The controller creates NLBs when it sees a Service of type <code>LoadBalancer<\/code> with the right annotations:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>apiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-nlb\n  namespace: alb-demo\n  annotations:\n    service.beta.kubernetes.io\/aws-load-balancer-type: external\n    service.beta.kubernetes.io\/aws-load-balancer-nlb-target-type: ip\n    service.beta.kubernetes.io\/aws-load-balancer-scheme: internet-facing\nspec:\n  type: LoadBalancer\n  selector:\n    app: nginx-demo\n  ports:\n  - port: 80\n    targetPort: 80\n    protocol: TCP<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The key difference: <code>aws-load-balancer-type: external<\/code> tells the controller (not the legacy in-tree cloud provider) to handle this Service. Without that annotation, you get the old-style Classic Load Balancer, which is not what you want.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>HTTPS with ACM Certificates<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>For production, terminate TLS at the ALB using an AWS Certificate Manager (ACM) certificate. Add these annotations to your Ingress:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>metadata:\n  annotations:\n    alb.ingress.kubernetes.io\/scheme: internet-facing\n    alb.ingress.kubernetes.io\/target-type: ip\n    alb.ingress.kubernetes.io\/certificate-arn: arn:aws:acm:eu-west-1:ACCOUNT_ID:certificate\/CERTIFICATE_ID\n    alb.ingress.kubernetes.io\/listen-ports: '[{\"HTTPS\":443}]'\n    alb.ingress.kubernetes.io\/ssl-redirect: \"443\"<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The <code>ssl-redirect<\/code> annotation creates an HTTP-to-HTTPS redirect rule on port 80 automatically. The ACM certificate must be in the same region as the ALB. If you need multiple certificates (for different domains on the same ALB), pass a comma-separated list of ARNs in <code>certificate-arn<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>TargetGroupBinding for Existing Target Groups<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Sometimes you already have an ALB or NLB created outside Kubernetes (by Terraform, CloudFormation, or manually), and you just want Kubernetes pods registered as targets. The <code>TargetGroupBinding<\/code> custom resource does exactly this:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>apiVersion: elbv2.k8s.aws\/v1beta1\nkind: TargetGroupBinding\nmetadata:\n  name: nginx-tgb\n  namespace: alb-demo\nspec:\n  serviceRef:\n    name: nginx-demo-svc\n    port: 80\n  targetGroupARN: arn:aws:elasticloadbalancing:eu-west-1:ACCOUNT_ID:targetgroup\/my-existing-tg\/50dc6c495c0c9188\n  targetType: ip<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The controller watches this resource and keeps the target group in sync with your pod IPs. When pods scale up or get replaced, the target group updates automatically. This is useful when your load balancer is managed by a separate Terraform stack, which is common in organizations that separate platform infrastructure from application workloads.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Troubleshooting<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Error: &#8220;Failed to create security group rules: AccessDenied&#8221;<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This means the controller&#8217;s IAM role is missing the <code>ec2:AuthorizeSecurityGroupIngress<\/code> permission. The most common cause is using an outdated IAM policy document. Redownload the policy from the official URL (it gets updated with new releases) and update your IAM policy:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>aws iam create-policy-version \\\n  --policy-arn arn:aws:iam::ACCOUNT_ID:policy\/AWSLoadBalancerControllerIAMPolicy \\\n  --policy-document file:\/\/iam_policy.json \\\n  --set-as-default<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Ingress stuck in &#8220;Pending&#8221; with no ADDRESS<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Check the controller logs for the actual error:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl logs -n kube-system -l app.kubernetes.io\/name=aws-load-balancer-controller --tail=50<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Common causes: the VPC ID is wrong in the Helm values, the subnet auto-discovery tags are missing, or the IRSA role trust policy has the wrong OIDC provider URL. Subnets need the tag <code>kubernetes.io\/role\/elb=1<\/code> for internet-facing ALBs and <code>kubernetes.io\/role\/internal-elb=1<\/code> for internal ones.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Error: &#8220;TargetGroup not found&#8221; after deleting and recreating an Ingress<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>When you delete an Ingress and recreate it quickly, the controller sometimes tries to reference target groups that AWS has not fully cleaned up yet. Wait 30 seconds after deletion before recreating. If the error persists, check for orphaned target groups in the AWS console under EC2 > Target Groups and delete any with the <code>k8s-<\/code> prefix that are no longer in use.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Pods showing &#8220;Unhealthy&#8221; in the target group<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Verify the health check path returns HTTP 200. The default health check path is <code>\/<\/code>, which works for nginx but not for applications that serve their root on a different path. Set it explicitly in the Ingress annotations:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>alb.ingress.kubernetes.io\/healthcheck-path: \/healthz<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Also check that the pod&#8217;s security group allows inbound traffic from the ALB&#8217;s security group on the target port.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Cleanup<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Delete resources in reverse order. Remove the Ingress first so the controller deletes the ALB and target groups, then remove the application and Helm release:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>kubectl delete ingress nginx-alb-ingress -n alb-demo\nkubectl delete namespace alb-demo\nhelm uninstall aws-load-balancer-controller -n kube-system<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Then clean up the IAM resources:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"className\":\"code\"} -->\n<pre class=\"wp-block-code code\"><code>aws iam detach-role-policy \\\n  --role-name AmazonEKSLoadBalancerControllerRole \\\n  --policy-arn arn:aws:iam::ACCOUNT_ID:policy\/AWSLoadBalancerControllerIAMPolicy\n\naws iam delete-role --role-name AmazonEKSLoadBalancerControllerRole\naws iam delete-policy --policy-arn arn:aws:iam::ACCOUNT_ID:policy\/AWSLoadBalancerControllerIAMPolicy<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Wait about 60 seconds after deleting the Ingress before deleting the namespace. This gives the controller time to deprovision the ALB cleanly. If you delete the controller first, the ALB and associated target groups, security group rules, and listeners become orphaned and you will need to delete them manually from the AWS console. Orphaned resources cost money, so always delete Ingress objects before uninstalling the controller. For more on managing <a href=\"https:\/\/computingforgeeks.com\/aws-costs-explained-real-numbers\/\" target=\"_blank\" rel=\"noreferrer noopener\">AWS costs<\/a>, see our breakdown of EKS pricing components.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Frequently Asked Questions<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>What is the difference between the AWS Load Balancer Controller and the in-tree cloud provider?<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The in-tree cloud provider creates Classic Load Balancers (CLBs) for Services of type LoadBalancer. It is built into the Kubernetes controller manager and does not support ALBs, NLBs, or IP-mode targets. The AWS Load Balancer Controller is an external controller that creates ALBs and NLBs with full feature support including WAF integration, authentication, and target group binding. AWS recommends migrating away from the in-tree provider.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Can I use the controller with Fargate pods?<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Yes. Set <code>target-type: ip<\/code> in your Ingress annotations. Fargate pods do not support instance-mode targets because they do not run on EC2 instances that can be registered directly. IP mode registers the pod ENI IP in the target group, which works with both Fargate and managed node groups.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>How do I restrict ALB access to specific IP ranges?<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Use the <code>alb.ingress.kubernetes.io\/inbound-cidrs<\/code> annotation with a comma-separated list of CIDR blocks. The controller creates security group rules that only allow traffic from those ranges. For example: <code>alb.ingress.kubernetes.io\/inbound-cidrs: 10.0.0.0\/8,192.168.1.0\/24<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Does the controller support gRPC health checks?<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Yes, as of v2.6+. Set <code>alb.ingress.kubernetes.io\/healthcheck-protocol: GRPC<\/code> and <code>alb.ingress.kubernetes.io\/backend-protocol-version: GRPC<\/code>. The ALB performs gRPC health checks using the gRPC health checking protocol defined in the <code>grpc.health.v1.Health<\/code> service.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Can I run the controller alongside the NGINX Ingress Controller?<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Yes. The AWS Load Balancer Controller only processes Ingress resources with <code>ingressClassName: alb<\/code>. The NGINX Ingress Controller handles <code>ingressClassName: nginx<\/code>. Both can coexist in the same cluster without conflict. Use the appropriate class name on each Ingress to route it to the right controller.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a GitOps approach to managing these Ingress resources, see our guide on <a href=\"https:\/\/computingforgeeks.com\/argocd-eks-gitops-complete-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">deploying ArgoCD on EKS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:html -->\n<script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What is the difference between the AWS Load Balancer Controller and the in-tree cloud provider?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"The in-tree cloud provider creates Classic Load Balancers (CLBs) for Services of type LoadBalancer. The AWS Load Balancer Controller is an external controller that creates ALBs and NLBs with full feature support including WAF integration, authentication, and target group binding. AWS recommends migrating away from the in-tree provider.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Can I use the AWS Load Balancer Controller with Fargate pods?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. Set target-type: ip in your Ingress annotations. Fargate pods do not support instance-mode targets because they do not run on EC2 instances. IP mode registers the pod ENI IP in the target group, which works with both Fargate and managed node groups.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How do I restrict ALB access to specific IP ranges?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Use the alb.ingress.kubernetes.io\/inbound-cidrs annotation with a comma-separated list of CIDR blocks. The controller creates security group rules that only allow traffic from those ranges.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Does the AWS Load Balancer Controller support gRPC health checks?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes, as of v2.6+. Set alb.ingress.kubernetes.io\/healthcheck-protocol: GRPC and alb.ingress.kubernetes.io\/backend-protocol-version: GRPC. The ALB performs gRPC health checks using the gRPC health checking protocol.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Can I run the AWS Load Balancer Controller alongside the NGINX Ingress Controller?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. The AWS Load Balancer Controller only processes Ingress resources with ingressClassName: alb. The NGINX Ingress Controller handles ingressClassName: nginx. Both can coexist in the same cluster without conflict.\"\n      }\n    }\n  ]\n}\n<\/script>\n<!-- \/wp:html -->","protected":false},"excerpt":{"rendered":"<p>Tested guide to installing the AWS Load Balancer Controller on EKS 1.33 with IRSA. ALB Ingress, NLB Services, HTTPS with ACM, TargetGroupBinding, and real troubleshooting.<\/p>\n","protected":false},"author":3,"featured_media":165781,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[511,2680,316,317],"tags":[513,21795,318],"cfg_series":[39810],"class_list":["post-165780","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aws","category-cloud","category-containers","category-kubernetes","tag-aws","tag-eks","tag-kubernetes","cfg_series-aws-eks-platform"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165780","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=165780"}],"version-history":[{"count":1,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165780\/revisions"}],"predecessor-version":[{"id":165850,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165780\/revisions\/165850"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/165781"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=165780"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=165780"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=165780"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=165780"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}