Skip to content
How to Use AWS ALB Controller With Kubernetes Gateway API Using Pod Identity?

How to Use AWS ALB Controller With Kubernetes Gateway API Using Pod Identity?

October 21, 2026·anupam
anupam

quoted summary

I wanted to put my 2048 game on EKS using the new Gateway API instead of the old Ingress. Sounds easy, but there are many small pieces: CRDs, a Helm chart, an IAM role, a service account, and one association to tie them all. Miss one name and the controller just sits there quietly doing nothing. So here is how I did it, in the order that actually worked for me.

What We Are Building

Gateway API objects go in, the ALB controller notices them, and a real AWS load balancer comes out.

    flowchart LR
    me["You: kubectl apply"] --> gw["Gateway + HTTPRoute"]
    gw --> ctrl["ALB Controller"]
    ctrl --> alb["AWS ALB"]
    alb --> app["2048 Game Pods"]
  

The controller needs AWS permission to create that ALB. I am giving it permission using Pod Identity. This is the chain I will build:

    flowchart LR
    pod["Controller Pod"] --> sa["ServiceAccount"]
    sa --> assoc["Pod Identity Association"]
    assoc --> role["IAM Role"]
    role --> policy["IAM Policy"]
  

Names matter a lot here. Keep this table open, everything below uses it:

ThingNameNamespace
EKS cluster2048-game-
ALB controller (Helm release)aws-load-balancer-controllerkube-system
ServiceAccountaws-alb-svc-acckube-system
IAM roleaws-alb-role-
IAM policyaws-alb-policy-
The game-game-2048

Get The Cluster Ready

cat > managed-ng-cluster.yaml << EOF
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: 2048-game
  region: ap-south-1

managedNodeGroups:
  - name: managed-ng-1
    minSize: 1
    maxSize: 2
    desiredCapacity: 1
    instanceType: t3.medium
    privateNetworking: true
vpc:
  cidr: "10.0.0.0/24"

autoModeConfig:
  enabled: false
EOF
eksctl create cluster -f managed-ng-cluster.yaml

Go drink coffee (or may be you are tea addict), it takes a while.

Gateway API And ALB CRDs

The controller will not understand Gateway or HTTPRoute unless these CRDs are already there. These are from the prerequisites of the docs:

# gateway api crds
kubectl apply --server-side=true -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml
# alb gateway api crds
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/refs/heads/main/config/crd/gateway/gateway-crds.yaml

Important

Install CRDs before the controller. The controller checks for CRDs only when it starts. If they come late, it silently disables the Gateway feature (you will see this in the logs section below).

Setting Up Pod Identity

I have written how Pod Identity works in another post, so here it is only the setup part.

Install The Pod Identity Addon

aws eks create-addon --cluster-name 2048-game --addon-name eks-pod-identity-agent

Check it:

aws eks list-addons --cluster-name 2048-game
{
    "addons": [
        "coredns",
        "eks-pod-identity-agent",
        "kube-proxy",
        "metrics-server",
        "vpc-cni"
    ]
}

Policy For The Controller

AWS already wrote the big IAM policy for us, no need to type it by hand. The URL says aws-alb-ingress-controller but the same policy works for Gateway API too.

curl -fsSLo iam-policy-aws-alb.json https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/main/docs/install/iam_policy.json
aws iam create-policy --policy-name aws-alb-policy --policy-document file://iam-policy-aws-alb.json

I will keep my account ID in a variable so I don’t copy-paste it everywhere:

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

Role For The Controller

Trust policy says “EKS Pod Identity service can use this role”:

cat > trust_relation.json << EOF
{
    "Version":"2012-10-17",
    "Statement": [
        {
            "Sid": "AllowEksAuthToAssumeRoleForPodIdentity",
            "Effect": "Allow",
            "Principal": {
                "Service": "pods.eks.amazonaws.com"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ]
        }
    ]
}
EOF
aws iam create-role --role-name aws-alb-role --assume-role-policy-document file://trust_relation.json
aws iam attach-role-policy --role-name aws-alb-role --policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/aws-alb-policy"

ServiceAccount

cat > aws-alb-svc-acc.yaml << EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: aws-alb-svc-acc
  namespace: kube-system
EOF
kubectl apply -f aws-alb-svc-acc.yaml

Verify:

kubectl get serviceaccount -n kube-system | grep alb
aws-alb-svc-acc   0         5s

Tie Them Together

Role is ready, service account is ready, but they don’t know each other yet. The association introduces them:

aws eks create-pod-identity-association \
  --cluster-name 2048-game \
  --role-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:role/aws-alb-role" \
  --namespace kube-system \
  --service-account aws-alb-svc-acc
{
    "association": {
        "clusterName": "2048-game",
        "namespace": "kube-system",
        "serviceAccount": "aws-alb-svc-acc",
        "roleArn": "arn:aws:iam::123456789012:role/aws-alb-role",
        "associationId": "a-mhzuyj2a4axiclt3f"
    }
}

Warning

Namespace and service account name in the association must match exactly with the real service account. One wrong letter and the pod will start with no AWS credentials, and you won’t get any loud error.

Install The ALB Controller With Helm

Now the controller. I already created the service account myself, so I tell the chart not to create one, and give it the same name:

helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=2048-game \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-alb-svc-acc
NAME: aws-load-balancer-controller
LAST DEPLOYED: Tue Oct 20 23:35:37 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!

Check everything:

kubectl get crds | grep elbv2.k8s.aws
helm status aws-load-balancer-controller -n kube-system
kubectl get deploy/aws-load-balancer-controller -n kube-system

Note

Not using Pod Identity (or IRSA)? Then skip the last two --set lines and let the chart create its own service account:

helm install aws-load-balancer-controller eks/aws-load-balancer-controller --set clusterName=2048-game -n kube-system

But then you have to give AWS permission to it some other way, otherwise it cannot create the ALB.

Tip

If the controller pods crash with a complaint about VPC ID or metadata, pass them yourself:

VPC_ID=$(aws eks describe-cluster --name 2048-game --query cluster.resourcesVpcConfig.vpcId --output text)
helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --reuse-values --set region=ap-south-1 --set vpcId=$VPC_ID

Apply The Game

Everything is ready, now throw the manifests of the game in:

kubectl apply -f 2048-game/

Check The Logs

The controller logs tell you the truth:

kubectl logs -n kube-system deploy/aws-load-balancer-controller --all-pods=true --tail=200

Bad one, CRDs were missing when the controller started:

{"level":"info","logger":"setup","msg":"Disabling ALBGatewayAPI: missing required CRDs","missing":["Gateway","GatewayClass","HTTPRoute","GRPCRoute","ReferenceGrant"]}

If you see this, install the CRDs and restart the controller:

kubectl rollout restart deploy/aws-load-balancer-controller -n kube-system

Good one, controller found the gateway and you get an address:

kubectl get gateway -n game-2048
NAME             CLASS                   ADDRESS                                                                            PROGRAMMED   AGE
my-alb-gateway   aws-alb-gateway-class   internal-k8s-game2048-myalbgat-c91ec960fa-654476805.ap-south-1.elb.amazonaws.com   Unknown      21m

Internal Or Internet-Facing?

Did you notice that internal- in the address? By default the load balancer is internal, so you can’t open it from your laptop. For a public one, make a LoadBalancerConfiguration:

external-lb.yaml
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
  name: external-lb
  namespace: game-2048
spec:
  scheme: internet-facing

and point your Gateway to it:

gw.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-alb-gateway
  namespace: game-2048
spec:
  gatewayClassName: aws-alb-gateway-class
  infrastructure:
    parametersRef:
      kind: LoadBalancerConfiguration
      name: external-lb # here
      group: gateway.k8s.aws
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same
    flowchart LR
    gw["Gateway"] -->|parametersRef| lbc["LoadBalancerConfiguration"]
    lbc -->|scheme| alb["internet-facing ALB"]
  

Note

An already created ALB can’t switch between internal and internet-facing, so decide this before the first apply.

Clean Up

Delete the game first, so the controller removes the ALB. If you delete the cluster first, the ALB stays behind and keeps charging you.

kubectl delete -f 2048-game/
helm uninstall aws-load-balancer-controller -n kube-system
eksctl delete cluster -f managed-ng-cluster.yaml
aws iam detach-role-policy --role-name aws-alb-role --policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/aws-alb-policy"
aws iam delete-role --role-name aws-alb-role
aws iam delete-policy --policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/aws-alb-policy"
Last updated on