VPC Lattice on EKS with Gateway API

Gateway API objects → Amazon VPC Lattice Gateway API Controller → Lattice services and target groups.

Order: kubeconfig → Lattice prefix-list SG rules → IAM (Pod Identity or IRSA) → Gateway API CRDs → Helm controller → service network + VPC association → GatewayClass → Gateway → workload + HTTPRoute → TargetGroupPolicy → verify Lattice DNS.

Pair with the app lab: VPC Lattice application networking (service network name lattice-sn). This page uses the same network name so EKS inventory shares the network with ECS/EC2 services.

Official walkthrough: Getting started (upstream examples use service network my-hotel — name is arbitrary).


1. Cluster context and Lattice security groups

export AWS_REGION=<eks_cluster_region>
export EKS_CLUSTER_NAME=<EKS_CLUSTER_NAME>

aws eks update-kubeconfig --name "${EKS_CLUSTER_NAME}" --region "${AWS_REGION}"
eksctl utils associate-iam-oidc-provider --cluster "${EKS_CLUSTER_NAME}" --approve --region "${AWS_REGION}"

Allow inbound from VPC Lattice managed prefix lists on the security group that protects your pod ENIs (often the cluster security group or a custom node group SG). Without this, targets stay unhealthy or connections time out.

CLUSTER_SG=$(aws eks describe-cluster --name "${EKS_CLUSTER_NAME}" --region "${AWS_REGION}" \
  --output json | jq -r '.cluster.resourcesVpcConfig.clusterSecurityGroupId')

PREFIX_LIST_ID=$(aws ec2 describe-managed-prefix-lists --region "${AWS_REGION}" \
  --query "PrefixLists[?PrefixListName=='com.amazonaws.${AWS_REGION}.vpc-lattice'].PrefixListId" \
  --output text)

aws ec2 authorize-security-group-ingress --region "${AWS_REGION}" \
  --group-id "${CLUSTER_SG}" \
  --ip-permissions "PrefixListIds=[{PrefixListId=${PREFIX_LIST_ID}}],IpProtocol=-1" 2>/dev/null || true

PREFIX_LIST_ID_IPV6=$(aws ec2 describe-managed-prefix-lists --region "${AWS_REGION}" \
  --query "PrefixLists[?PrefixListName=='com.amazonaws.${AWS_REGION}.ipv6.vpc-lattice'].PrefixListId" \
  --output text)

aws ec2 authorize-security-group-ingress --region "${AWS_REGION}" \
  --group-id "${CLUSTER_SG}" \
  --ip-permissions "PrefixListIds=[{PrefixListId=${PREFIX_LIST_ID_IPV6}}],IpProtocol=-1" 2>/dev/null || true

Also allow TCP 8080 (app port) from the IPv4 prefix list on the same SG if your lab apps listen on 8080. See Allow traffic from Amazon VPC Lattice.

Controller IAM: create VPCLatticeControllerIAMPolicy from recommended-inline-policy.json, namespace aws-application-networking-system, then EKS Pod Identity (recommended) or IRSA for ServiceAccount gateway-api-controller. Follow Set up IAM permissions through namespace + role binding. Skipping IAM → controller pods crash on Lattice API calls.


2. Gateway API CRDs

Pin a release from Gateway API releases. Match a version compatible with your controller chart (see deploy guide).

kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.1/standard-install.yaml

3. Install the controller (Helm)

Chart version: use the value in Install the controller (currently v2.1.3). Set serviceAccount.create=false because the SA already exists from Pod Identity / IRSA.

On Amazon EKS, the chart reads account, Region, VPC, and cluster name from the control plane — do not pass empty clusterVpcId / awsAccountId placeholders.

aws ecr-public get-login-password --region us-east-1 | \
  helm registry login --username AWS --password-stdin public.ecr.aws

helm upgrade --install gateway-api-controller \
  oci://public.ecr.aws/aws-application-networking-k8s/aws-gateway-controller-chart \
  --version=v2.1.3 \
  --namespace aws-application-networking-system \
  --set serviceAccount.create=false \
  --set log.level=info

kubectl get pods -n aws-application-networking-system
Self-hosted Kubernetes (not EKS)

When the controller is not on EKS, set Helm values explicitly: clusterVpcId, awsAccountId, awsRegion, clusterName. See Environment variables in the deploy guide.


4. GatewayClass

GatewayClass tells Kubernetes which controller owns Gateways of that class. Your Gateway must set spec.gatewayClassName: amazon-vpc-lattice to match this object.

Apply the manifest from the controller repo (after Helm pods are running):

kubectl apply -f https://raw.githubusercontent.com/aws/aws-application-networking-k8s/main/files/controller-installation/gatewayclass.yaml

Same content (cluster-scoped — no namespace):

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: amazon-vpc-lattice
spec:
  controllerName: application-networking.k8s.aws/gateway-api-controller

Verify the class exists and is accepted:

kubectl get gatewayclass amazon-vpc-lattice
kubectl describe gatewayclass amazon-vpc-lattice

controllerName must match the running controller. If GatewayClass is missing, kubectl apply on a Gateway succeeds but status stays unprogrammed and no Lattice resources are created.

Upstream file may still use apiVersion: gateway.networking.k8s.io/v1beta1; either v1 or v1beta1 is fine once Gateway API CRDs are installed.


5. Service network (required)

The controller attaches new Gateways to a VPC Lattice service network. Create the network (once per Region/account lab) and associate the EKS cluster VPC.

If you already created lattice-sn in application networking, reuse it. Otherwise:

export SERVICE_NETWORK_NAME=lattice-sn

aws vpc-lattice create-service-network --name "${SERVICE_NETWORK_NAME}" --region "${AWS_REGION}" 2>/dev/null || true

SERVICE_NETWORK_ID=$(aws vpc-lattice list-service-networks --region "${AWS_REGION}" \
  --query "items[?name=='${SERVICE_NETWORK_NAME}'].id" --output text)

CLUSTER_VPC_ID=$(aws eks describe-cluster --name "${EKS_CLUSTER_NAME}" --region "${AWS_REGION}" \
  --query 'cluster.resourcesVpcConfig.vpcId' --output text)

aws vpc-lattice create-service-network-vpc-association \
  --region "${AWS_REGION}" \
  --service-network-identifier "${SERVICE_NETWORK_ID}" \
  --vpc-identifier "${CLUSTER_VPC_ID}"

Wait until association status is ACTIVE:

aws vpc-lattice list-service-network-vpc-associations \
  --region "${AWS_REGION}" \
  --vpc-id "${CLUSTER_VPC_ID}"

Tell the controller which service network to use (Helm):

helm upgrade gateway-api-controller \
  oci://public.ecr.aws/aws-application-networking-k8s/aws-gateway-controller-chart \
  --version=v2.1.3 \
  --reuse-values \
  --namespace aws-application-networking-system \
  --set defaultServiceNetwork="${SERVICE_NETWORK_NAME}"

6. Gateway

Create namespace networking (optional; keeps Gateway separate from app namespaces):

kubectl create namespace networking

Minimal Gateway (matches upstream my-hotel-gateway.yaml). Custom hostname and cross-namespace routes are optional — see collapsible note below.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: lattice-gateway
  namespace: networking
spec:
  gatewayClassName: amazon-vpc-lattice
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
kubectl apply -f lattice-gateway.yaml
kubectl get gateway -n networking

PROGRAMMED=True in status when the controller has linked the Gateway to Lattice.

hostname vs allowedRoutes (custom domains)
FieldFilters
listeners[].hostnameRequest Host header
allowedRoutes.namespaces.fromWhich namespaces may attach an HTTPRoute

from: Same (Gateway ns only), All, or Selector.

For a custom domain, Gateway listener hostname and HTTPRoute hostnames must overlap or the route is not accepted (Accepted=False).


7. Workload (inventory lab)

Deploy the sample app from the repo (Service port 80 → pod 8080, health GET /health):

  1. Build/push eks-inventory-service/Dockerfile to ECR.
  2. Edit eks-inventory-service/k8s/deployment.yaml (REPLACE_WITH_ECR_IMAGE).
  3. Create namespace and apply:
kubectl create namespace inventory
kubectl apply -n inventory -f deployment.yaml

8. HTTPRoute

Example route to inventory-service on path prefix /inventory (matches the lab app).

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: inventory-route
  namespace: inventory
spec:
  parentRefs:
    - name: lattice-gateway
      namespace: networking
      sectionName: http
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /inventory
      backendRefs:
        - name: inventory-service
          port: 80
parentRefs, hostnames, backendRefs
  • parentRefs — Gateway name; sectionName binds listener http. Cross-namespace attach requires Gateway allowedRoutes.namespaces.from: All.
  • hostnames — optional; must overlap Gateway listener hostname when both are set.
  • backendRefs — Kubernetes Service name and Service port (80 here, not 8080).

9. TargetGroupPolicy

Attach to the Service, not the HTTPRoute. Health check must hit the pod path and port the app exposes.

apiVersion: application-networking.k8s.aws/v1alpha1
kind: TargetGroupPolicy
metadata:
  name: inventory-policy
  namespace: inventory
spec:
  targetRef:
    group: ""
    kind: Service
    name: inventory-service
  protocol: HTTP
  protocolVersion: HTTP1
  healthCheck:
    enabled: true
    path: /health
    port: 8080
    intervalSeconds: 30
    healthyThresholdCount: 3
    unhealthyThresholdCount: 3

Wrong healthCheck.path (for example /healthz) → targets unhealthy → no traffic even when DNS resolves.


10. Verify

kubectl get httproute -n inventory
kubectl get httproute inventory-route -n inventory -o jsonpath='{.metadata.annotations.application-networking\.k8s\.aws/lattice-assigned-domain-name}{"\n"}'

Call the Lattice-assigned hostname from a client in a VPC associated with lattice-sn (HTTP 80, path /inventory/...):

curl -s "http://${LATTICE_FQDN}/inventory/SKU-001"

Controller logs:

kubectl logs -n aws-application-networking-system -l app.kubernetes.io/name=aws-gateway-controller --tail=50

Troubleshooting

SymptomLikely cause
GatewayClass not foundApply §4 before Gateway; name must be amazon-vpc-lattice
Gateway not PROGRAMMEDMissing defaultServiceNetwork or VPC association not ACTIVE
HTTPRoute not acceptedparentRefs namespace/name wrong; hostname mismatch with Gateway
503 / no healthy targetsTargetGroupPolicy path/port wrong; SG missing Lattice prefix list on pod SG
Helm env errors on Fargate-onlyController needs EC2 or hybrid; or set explicit Helm env for non-EKS

References