Skip to main content

Cilium ENI Mode + Gateway API Deep-dive Configuration

47 min read
info

This document extends the Gateway API Adoption Guide. It provides practical guidance for combining Cilium ENI mode and Gateway API to configure high-performance networking.

Cilium ENI mode is a high-performance networking solution that uses AWS Elastic Network Interfaces directly to assign VPC IP addresses to Pods. Combined with Gateway API, it provides standardized L7 routing and eBPF-based ultra-low-latency processing.

1. What is Cilium ENI Mode?​

Cilium ENI mode is a high-performance networking solution that uses AWS Elastic Network Interfaces directly to assign VPC IP addresses to Pods. Unlike traditional overlay networks, ENI mode provides the following capabilities.

Key Features​

Direct use of AWS ENIs
Each Pod receives a VPC IP. Security groups apply to ENIs, NACLs apply to subnets, and VPC Flow Logs provide flow visibility. This does not by itself provide per-Pod security group assignment equivalent to AWS VPC CNI Security Groups for Pods.

High-performance networking with eBPF
Cilium implements packet forwarding and Service handling with eBPF in the Linux kernel. Improvement over iptables depends on traffic, rule count, instance type, and configuration; this document does not contain a measured comparison.

Figure 1. iptables-based vs eBPF-based packet processing paths

Expand the diagram to read labels and follow connections.
Diagram

Native routing without overlay overhead
ENI mode uses VPC routing tables directly, without overlay encapsulation such as VXLAN or Geneve. This minimizes network hops and prevents MTU issues caused by encapsulation.

tip

Consider ENI mode for EC2 nodes that need VPC IPs and native routing. The previously attributed Datadog figures of 40% lower latency and 35% higher throughput are not supported by an identified original experiment and must not be used as performance evidence. Measure with the same workload and node configuration.

2. Architecture Overview​

The architecture combining Cilium ENI mode and Gateway API consists of the following components.

Figure 2. Architecture combining Cilium ENI mode and Gateway API

Expand the diagram to read labels and follow connections.
Diagram

Key Components​

1. Network Load Balancer (NLB)

  • AWS managed L4 load balancer
  • Ultra-low latency, measured in microseconds
  • Cross-Zone Load Balancing support
  • Static IP or Elastic IP allocation
  • TLS passthrough mode support

2. eBPF TPROXY (Transparent Proxy)

  • Intercepts Service traffic with eBPF and redirects it to Envoy through TPROXY
  • Envoy performs L7 processing in userspace; this is not a complete kernel bypass
  • Maintains connection tracking and Service state in eBPF maps
  • XDP acceleration is an option for supported L4 forwarding paths, distinct from TPROXY

3. Cilium Envoy (L7 Gateway)

  • L7 processing engine based on Envoy Proxy
  • Implements Gateway API resources such as HTTPRoute and TLSRoute
  • Dynamic listener and route configuration through the xDS API
  • Request/response transformation, header manipulation, and rate limiting

4. Cilium Operator

  • Orchestrates ENI creation and deletion
  • Manages IP address pools, including Prefix Delegation
  • Synchronizes policies across the cluster
  • Manages CiliumNode CRD status

5. Cilium Agent (DaemonSet)

  • Loads and manages eBPF programs on each node
  • Implements the CNI plugin
  • Tracks endpoint status
  • Enforces network policies

6. ENI (Elastic Network Interface)

  • AWS VPC network interface
  • Maximum ENI count depends on the instance type (for example, 3 for m5.large)
  • Maximum IP count per ENI is limited (for example, 10 per ENI for m5.large)
  • IPv4 prefix delegation provides 16 addresses per /28; each prefix consumes one secondary IPv4 address slot on the ENI

7. Hubble (Observability)

  • Real-time visibility into network flows
  • Automatic service dependency map generation
  • L7 protocol visibility (HTTP, gRPC, Kafka, DNS)
  • Prometheus metric export

Four Stages of Traffic Flow​

Figure 3. Four stages of traffic flow from NLB through the eBPF TPROXY

Expand the diagram to read labels and follow connections.
Diagram

Stage 1: L4 load balancing (NLB)

  • Receives TCP connection requests from clients
  • Selects healthy nodes based on target group health checks
  • Maintains connection affinity with a flow hash algorithm based on the 5-tuple

Stage 2: Transparent proxying (eBPF TPROXY)

  • Intercepts Service traffic with eBPF and looks up connection tracking state
  • Transparently redirects new connections to the local Envoy listener
  • Reads destination information from the map for fast forwarding of existing connections
  • Redirects to userspace Envoy for L7 processing; context switches still occur

Stage 3: L7 routing (Cilium Envoy)

  • Parses HTTP/2 and extracts request headers
  • Matches HTTPRoute rules by path, header, and query parameter
  • Transforms requests through URL rewrites and header addition/removal
  • Enforces rate limiting and authentication/authorization policies

Stage 4: Native routing

  • Forwards directly to the backend Pod's ENI IP address
  • Uses VPC routing tables without VXLAN/Geneve encapsulation
  • Does not require disabling EC2 instance source/destination checks
  • Returns response packets along the same path in reverse
info

In this architecture, Cilium Envoy serves as the Gateway API GatewayClass implementation. The Cilium Operator detects changes to HTTPRoute resources and dynamically updates the Envoy configuration on each node.

3. Prerequisites​

The following requirements must be met to deploy Cilium ENI mode successfully.

EKS Cluster Requirements​

⚙️ EKS Cluster Requirements
Required environment setup for Gateway API deployment
EKS Version1.28+ (recommended: 1.32)
Gateway API v1.4 compatibility
Control PlaneDisable kube-proxy
Cilium replaces kube-proxy
Node OSAmazon Linux 2023 or Ubuntu 22.04
eBPF kernel support required (5.10+)
Container Runtimecontainerd 1.6+
CRI compatibility
VPC CNI RemovalRequired
Cilium performs CNI role
warning

For a new standard EKS cluster, set addonsConfig.disableDefaultAddons: true in eksctl to skip the default VPC CNI, kube-proxy, and CoreDNS add-ons. This does not replace Auto Mode networking. Install Cilium and CoreDNS separately afterward.

On an existing cluster, removing the VPC CNI interrupts Pod networking, so downtime is required.

VPC and Subnet Requirements​

IP address availability
In ENI mode, each Pod uses an actual VPC IP address, so sufficient IP address space is required.

# Formula for calculating the required number of IP addresses
Total_required_IPs = (Worker_nodes × Max_Pods_per_node) + Headroom(20%)

# Example: 10 nodes, up to 110 Pods per node
# Total required IPs = (10 × 110) × 1.2 = 1,320
# Recommended subnet: /21 (2,048 IPs) or larger

Subnet configuration

  • At least 1 subnet per Availability Zone (AZ)
  • Required subnet tags:
    kubernetes.io/role/internal-elb = 1
    kubernetes.io/cluster/<cluster-name> = shared
  • Both public and private subnets are supported
  • Private subnets are recommended for stronger security

VPC settings

  • Enable DNS hostnames: enableDnsHostnames: true
  • Enable DNS support: enableDnsSupport: true
  • Configure the correct domain name in the DHCP option set

IAM Permissions​

The Cilium Operator and nodes require the following IAM permissions to manage ENIs.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:AttachNetworkInterface",
"ec2:DeleteNetworkInterface",
"ec2:DetachNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeInstances",
"ec2:ModifyNetworkInterfaceAttribute",
"ec2:AssignPrivateIpAddresses",
"ec2:UnassignPrivateIpAddresses",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs",
"ec2:DescribeRouteTables",
"ec2:DescribeTags",
"ec2:DescribeInstanceTypes",
"ec2:DescribeSecurityGroups",
"ec2:CreateTags"
],
"Resource": "*"
}
]
}

Configure IRSA (IAM Roles for Service Accounts)

# Create an IAM role for the Cilium Operator
eksctl create iamserviceaccount \
--name cilium-operator \
--namespace kube-system \
--cluster <cluster-name> \
--role-name CiliumOperatorRole \
--role-only \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
--approve

# Attach an additional inline policy
aws iam put-role-policy \
--role-name CiliumOperatorRole \
--policy-name CiliumENIPolicy \
--policy-document file://cilium-eni-policy.json

Add permissions to the node IAM role

# Get the node group's IAM role ARN
NODE_ROLE=$(aws eks describe-nodegroup \
--cluster-name <cluster-name> \
--nodegroup-name <node-group-name> \
--query 'nodegroup.nodeRole' \
--output text)

# Attach the policy
aws iam attach-role-policy \
--role-name $(echo $NODE_ROLE | cut -d'/' -f2) \
--policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
EKS Auto Mode and Cilium

AWS documentation explicitly excludes alternate CNIs such as Cilium and Calico on Auto Mode nodes. Do not remove their built-in networking or attempt to replace it with Cilium ENI through a bootstrap flag.

The installation examples here target standard EKS managed/self-managed EC2 nodes outside Auto Mode. Use those node types when Cilium ENI is required, and plan CNI and workload migration separately when moving to Auto Mode. AWS support for Cilium on EKS Hybrid Nodes is distinct from operating upstream Cilium on EC2 nodes.

4. Installation Flow​

The installation procedure for Cilium ENI mode depends on whether the cluster is new or existing.

A new cluster allows Cilium to be deployed before the VPC CNI is installed, providing a clean installation without downtime.

Step 1: Create an EKS cluster with the VPC CNI disabled

# Create a cluster with eksctl
cat <<EOF > cluster-config.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
name: cilium-gateway-cluster
region: ap-northeast-2
version: "1.32"

vpc:
cidr: 10.0.0.0/16
nat:
gateway: HighlyAvailable # Redundant NAT Gateways

# Disable automatic VPC CNI installation (required)
addonsConfig:
disableDefaultAddons: true

managedNodeGroups:
- name: ng-1
instanceType: m7g.xlarge
desiredCapacity: 3
minSize: 3
maxSize: 10
volumeSize: 100
privateNetworking: true
iam:
withAddonPolicies:
autoScaler: true
albIngress: true
cloudWatch: true
labels:
role: worker
tags:
nodegroup-name: ng-1

# disableDefaultAddons also skips kube-proxy and CoreDNS
EOF

# Create the cluster (takes 10-15 minutes)
eksctl create cluster -f cluster-config.yaml
warning

addonsConfig.disableDefaultAddons: true is required for this example. Ordinary Pod networking and DNS are not ready until Cilium and CoreDNS have been installed.

Step 2: Install Gateway API CRDs

# Cilium 1.19.3 compatibility baseline: Gateway API v1.4.1
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml

# Verify the installation
kubectl get crd | grep gateway

Example output:

gatewayclasses.gateway.networking.k8s.io         2026-02-12T00:00:00Z
gateways.gateway.networking.k8s.io 2026-02-12T00:00:00Z
httproutes.gateway.networking.k8s.io 2026-02-12T00:00:00Z
referencegrants.gateway.networking.k8s.io 2026-02-12T00:00:00Z

Step 3: Add the Cilium Helm repository

helm repo add cilium https://helm.cilium.io/
helm repo update

Step 4: Install Cilium with Helm These values target the Cilium 1.19.3 chart. Configure the IRSA role, EKS OIDC provider, AWS Load Balancer Controller, and Prometheus Operator ServiceMonitor CRD first. Replace the account and API endpoint values. The separate inference Gateway versions in section 9.4 do not establish compatibility for this Cilium installation.

# cilium-values.yaml
# Enable ENI mode
eni:
enabled: true
awsEnablePrefixDelegation: true # /28 Prefix Delegation
awsReleaseExcessIPs: true # Automatically release unused IPs
iamRole: "arn:aws:iam::123456789012:role/CiliumOperatorRole"

# Set the IPAM mode to ENI
ipam:
mode: "eni"

# Enable native routing
routingMode: native
autoDirectNodeRoutes: false
ipv4NativeRoutingCIDR: 10.0.0.0/16

# Replace kube-proxy
kubeProxyReplacement: true
k8sServiceHost: <API_SERVER_ENDPOINT> # EKS API server address
k8sServicePort: 443

# Enable Gateway API
gatewayAPI:
enabled: true
hostNetwork:
enabled: false # Set to false when using an NLB

# Hubble observability
hubble:
enabled: true
relay:
enabled: true
replicas: 2
ui:
enabled: true
replicas: 1
ingress:
enabled: false # Expose through a separate HTTPRoute
metrics:
enabled:
- dns
- drop
- tcp
- flow
- port-distribution
- icmp
- httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction

# Operator high availability
operator:
replicas: 2
rollOutPods: true
prometheus:
enabled: true
serviceMonitor:
enabled: true

# Agent settings
prometheus:
enabled: true
serviceMonitor:
enabled: true

# Security hardening
policyEnforcementMode: "default"
encryption:
enabled: false # This example does not enable Pod traffic encryption
type: wireguard # Enable WireGuard when required

# Performance optimization
bpf:
preallocateMaps: true
mapDynamicSizeRatio: 0.0025 # Use 0.25% of memory
monitorAggregation: medium
lbMapMax: 65536 # Load balancer map size

# Maglev load balancing
loadBalancer:
algorithm: maglev
mode: snat
acceleration: disabled # Enable XDP separately after driver/MTU validation
# Get the EKS API server endpoint
API_SERVER=$(aws eks describe-cluster \
--name cilium-gateway-cluster \
--query 'cluster.endpoint' \
--output text | sed 's/https:\/\///')

# Install the Helm chart
helm install cilium cilium/cilium \
--version 1.19.3 \
--namespace kube-system \
--values cilium-values.yaml \
--set k8sServiceHost=${API_SERVER} \
--wait

Step 5: Install CoreDNS

Because disableDefaultAddons also skipped CoreDNS, install it as an EKS add-on after Cilium is ready.

# Deploy CoreDNS
aws eks create-addon --cluster-name cilium-gateway-cluster --addon-name coredns

# Check CoreDNS Pods
kubectl get pods -n kube-system -l k8s-app=kube-dns

Step 6: Verify the installation

# Install the Cilium CLI (macOS)
brew install cilium-cli

# Alternatively, for Linux/macOS
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz{,.sha256sum}
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}

# Check Cilium status (wait up to 5 minutes)
cilium status --wait

# Test connectivity (takes approximately 2-3 minutes)
cilium connectivity test

Example healthy output:

    /¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: OK
\__/¯¯\__/ Hubble Relay: OK
\__/ ClusterMesh: disabled

DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
Deployment hubble-relay Desired: 2, Ready: 2/2, Available: 2/2
Containers: cilium Running: 3
cilium-operator Running: 2
hubble-relay Running: 2

Step 7: Create Gateway resources default/tls-cert must contain a valid certificate and private key for the hostname. Prepare those files before running:

kubectl create secret tls tls-cert -n default --cert=tls.crt --key=tls.key

Cilium 1.19.3 creates a synthetic Gateway EndpointSlice (192.192.192.192:9999), not a list of backend Pod IPs. Use NLB instance targets and the generated Service NodePorts here. The subsequent Envoy-to-Pod hop uses ENI IPs. Do not register the synthetic endpoint as an NLB IP target. Pinned controller source.

# gateway-resources.yaml
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: cilium
spec:
controllerName: io.cilium/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-gateway
namespace: default
spec:
gatewayClassName: cilium
infrastructure:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: tls-cert
# Deploy the Gateway
kubectl apply -f gateway-resources.yaml

# Check Gateway status
kubectl get gateway cilium-gateway -o yaml

Gateway ready status:

status:
conditions:
- type: Accepted
status: "True"
reason: Accepted
- type: Programmed
status: "True"
reason: Programmed
addresses:
- type: Hostname
value: "a1234567890abcdef.elb.ap-northeast-2.amazonaws.com"

Existing Cluster (Downtime Required)​

On an existing cluster, removing the VPC CNI and replacing it with Cilium temporarily interrupts Pod networking.

Downtime Warning

This process interrupts Pod networking across the entire cluster. For production environments, a blue-green cluster cutover or a scheduled maintenance window is strongly recommended.

Downtime has not been measured here. Plan a maintenance window that includes node replacement, Pod recreation, and validation.

Step 1: Create backups

# Back up the current network configuration
kubectl get -A pods -o yaml > backup-pods.yaml
kubectl get -A services -o yaml > backup-services.yaml
kubectl get -A ingress -o yaml > backup-ingress.yaml

# Back up the VPC CNI configuration
kubectl get daemonset aws-node -n kube-system -o yaml > backup-aws-node.yaml

Step 2: Remove the VPC CNI

# Delete the aws-node DaemonSet
kubectl delete daemonset aws-node -n kube-system

# Delete kube-proxy; Cilium replaces it
kubectl delete daemonset kube-proxy -n kube-system

Step 3: Add node taints (optional safeguard)

# Add a NoSchedule taint to every node
kubectl get nodes -o name | xargs -I {} kubectl taint {} key=value:NoSchedule

Step 4: Install Cilium using the new-cluster procedure

Follow Steps 2-7 in the "New Cluster" section above.

Step 5: Restart Pods

Before restarting workloads, verify Cilium and DNS, then remove only the temporary taint added above. This maintenance outline is not a complete migration runbook; managed VPC CNI add-on ownership, StatefulSets, standalone Pods, rollback, and node replacement must be planned for the actual cluster.

kubectl taint nodes --all key:NoSchedule-
# Restart Pods in all namespaces (rolling restart)
kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | \
xargs -n1 -I {} kubectl rollout restart deployment -n {}

# Restart DaemonSets as well
kubectl get daemonsets -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' | \
while read ns ds; do
kubectl rollout restart daemonset $ds -n $ns
done

Step 6: Verify networking

# Test Pod-to-Pod communication
kubectl run test-pod --image=nicolaka/netshoot --rm -it -- /bin/bash
# Inside the Pod:
ping 10.0.1.10 # Another Pod's ENI IP
curl http://kubernetes.default.svc.cluster.local

# Test DNS resolution
nslookup kubernetes.default.svc.cluster.local

# Test external communication
curl https://www.google.com

5. Gateway API Resource Configuration​

The following examples configure practical routing scenarios with Cilium Gateway API.

Basic HTTPRoute​

# basic-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: api-service
port: 8080
weight: 100
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Backend-Version
value: "v1"

Traffic Splitting (Canary Deployment)​

# canary-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2-stable
port: 8080
weight: 90 # 90% of traffic
- name: api-v2-canary
port: 8080
weight: 10 # 10% of traffic

Header-Based Routing​

# header-based-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
# Route beta users to the new version
- matches:
- headers:
- type: Exact
name: X-User-Type
value: beta
backendRefs:
- name: api-v2-beta
port: 8080

# Route regular users to the stable version
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api-v1-stable
port: 8080

URL Rewrite​

# url-rewrite-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rewrite-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-api
backendRefs:
- name: new-api-service
port: 8080

Role Separation Guide​

The following configuration implements role separation, a key benefit of Gateway API, in Cilium.

# role-separation-example.yaml

# 1. Platform team: Manage GatewayClass (cluster-admin)
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: production-gateway
spec:
controllerName: io.cilium/gateway-controller
parametersRef:
group: ""
kind: ConfigMap
name: gateway-config
namespace: kube-system

---
# Platform team: Manage Gateway infrastructure (infra namespace)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: infra
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
spec:
gatewayClassName: production-gateway
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All # Allow attachment from all namespaces
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-tls-cert
namespace: infra

---
# 2. Development team A: Manage HTTPRoute (team-a namespace)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-a-route
namespace: team-a
spec:
parentRefs:
- name: shared-gateway
namespace: infra # Cross-namespace reference
hostnames:
- "team-a.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-a-service
port: 8080

---
# 3. Development team B: Manage HTTPRoute (team-b namespace)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-b-route
namespace: team-b
spec:
parentRefs:
- name: shared-gateway
namespace: infra
hostnames:
- "team-b.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-b-service
port: 9090

---
# Allow cross-namespace references (created by the platform team)
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-team-routes
namespace: infra
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-a
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-b
to:
- group: gateway.networking.k8s.io
kind: Gateway
name: shared-gateway

RBAC configuration:

# rbac-platform-team.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gateway-infrastructure-admin
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses", "gateways"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: platform-team-gateway
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gateway-infrastructure-admin
subjects:
- kind: Group
name: platform-team
apiGroup: rbac.authorization.k8s.io

---
# rbac-dev-team.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: httproute-manager
namespace: team-a
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-httproute
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: httproute-manager
subjects:
- kind: Group
name: team-a-developers
apiGroup: rbac.authorization.k8s.io

6. Performance Optimization​

The latency diagram, comparison table, memory estimates, and instance benefit figures in this section are illustrative, unmeasured assumptions, not benchmark results or service guarantees. Validate them with a stated workload, hardware, Cilium version, and measurement method.

The following tuning options aim to maximize performance in Cilium ENI mode.

Benefits of NLB + Cilium Envoy​

Figure 4. Traditional ALB path vs NLB + Cilium Envoy path

Expand the diagram to read labels and follow connections.
Diagram

Latency comparison:

⚡ Latency Comparison Analysis
ALB+NGINX vs NLB+Cilium performance comparison
ComponentALB + NGINXNLB + CiliumImprovement
L4 Load BalancerALB: 10msNLB: 0.4ms
96%
L7 ProxyNGINX: 5mseBPF+Envoy: 3.1ms
38%
Total Latency15ms3.5ms
77%

ENI and IP Management Optimization​

Enable Prefix Delegation
Allocate /28 blocks of 16 IPs instead of individual addresses. This can improve address allocation efficiency but does not guarantee a fixed reduction in ENI attachments or Pod startup time.

# cilium-values.yaml (ENI section)
eni:
awsEnablePrefixDelegation: true

# Automatically release excess unused IPs to reduce costs
awsReleaseExcessIPs: true

# min-allocate/pre-allocate are CiliumNode spec.ipam.min-allocate /
# spec.ipam.pre-allocate fields, not Helm keys under eni.

Benefits:

  • Requires contiguous /28 blocks and instance support for prefixes
  • Check the CiliumNode IP pool and AWS API metrics to assess allocation efficiency
  • Measure ENI attachments, startup latency, and API throttling for the actual workload

Check ENI/IP limits by instance type:

# Query limits with the AWS CLI
aws ec2 describe-instance-types \
--instance-types m7g.xlarge \
--query 'InstanceTypes[0].NetworkInfo.{MaxENI:MaximumNetworkInterfaces,IPv4PerENI:Ipv4AddressesPerInterface}'

# Example output:
# {
# "MaxENI": 4,
# "IPv4PerENI": 15
# }
# With those limits, theoretical address slots: 4 × (15 - 1) × 16 = 896
# Actual Pod capacity is lower when constrained by kubelet maxPods, ENIs/slots in use, reservations, or available subnet addresses

BPF Tuning​

Enable map preallocation
Preallocate eBPF maps at startup instead of allocating them dynamically to eliminate latency jitter.

# cilium-values.yaml
bpf:
preallocateMaps: true # Preallocate maps

# Adjust map sizes to twice the defaults
lbMapMax: 65536 # Maximum load balancer backend count
natMax: 524288 # Maximum NAT connection tracking entries
neighMax: 524288 # Maximum neighbor table entries
policyMapMax: 16384 # Maximum policy entries

# Monitor aggregation level (CPU usage vs visibility)
monitorAggregation: medium # none, low, medium, maximum

# Connection tracking (CT) table sizes
ctTcpMax: 524288
ctAnyMax: 262144

Calculate memory usage:

# Estimated memory usage = sum of (map size × entry size)
# lbMapMax (65536 × 128B) = 8MB
# natMax (524288 × 64B) = 32MB
# Total estimated memory: ~100-200MB/node

Routing Optimization​

Maglev load balancing algorithm
Developed by Google, Maglev uses consistent hashing to preserve connection affinity as much as possible when backends change.

# cilium-values.yaml
loadBalancer:
algorithm: maglev # Default: random
mode: snat

# Maglev is a top-level key; use the same values on every node
maglev:
tableSize: 65521
hashSeed: "JLfvgnHc2kaSUFaI"

Algorithm comparison:

Cilium random vs maglev algorithm characteristics
Load Balancing Algorithm Comparison
AlgorithmConnection AffinityBackend AdditionBackend RemovalCPU Overhead
randomNo consistent selection for the same 5-tupleNew connections select randomly from the updated backend setRemoved backends are excluded from new selectionsMeasure for the configuration and load
maglev · RecommendedConsistent 5-tuple selection for the same backend set and configurationSome mappings change; no fixed 10% rateSome mappings beyond the removed backend can also changeMeasure for the configuration and load

XDP acceleration (eXpress Data Path)
XDP acceleration applies to supported LoadBalancer/NodePort forwarding paths with a backend on a remote node. It does not bypass the Gateway’s TPROXY and Envoy L7 processing.

# cilium-values.yaml
loadBalancer:
acceleration: native # Verify NIC/driver support, MTU, and channel count first

Check XDP support:

# Run on the node
ethtool -i eth0 | grep driver
# Supported drivers: ixgbe, i40e, mlx4, mlx5, ena (AWS Nitro)

# Verify that XDP is enabled
ip link show eth0 | grep xdp

Performance improvements:

  • No fixed speedup or CPU reduction has been measured for this example
  • AWS ENA requires checks for driver version, supported XDP MTU, and channel limits
  • Verify the actual acceleration state with cilium-dbg status --verbose

Instance Type Considerations​

Recommended instances for network performance:

💻 Instance Type Recommendations
Optimized instance selection for Gateway API node groups
Instance TypevCPUMemoryNetwork BandwidthENIIP/ENIRecommended Use
m7g.xlarge416GB
Up to 12.5Gbps
415General purpose, cost efficient
c7gn.xlarge48GB
Up to 30Gbps
415High-performance gateway
m7g.2xlarge832GB
Up to 15Gbps
415Medium workloads
c7gn.4xlarge1632GB
Up to 50Gbps
830Large-scale traffic
m7g.8xlarge32128GB
25Gbps
830High-density pods
c7gn.12xlarge4896GB
100Gbps
1550Ultra-high performance

Reasons to choose Graviton4 (8g series):

  • 40% better price-performance than x86
  • 60% improvement in energy efficiency
  • eBPF JIT optimization
  • Full compatibility with Cilium
  • Graviton5 (M9g/M9gd, GA in June 2026): ~25% better performance than M8g

When to choose Network Optimized instances (n series):

  • Dedicated Gateway nodes
  • Traffic of 100,000 RPS or more
  • Sub-1ms latency requirements
tip

A hybrid configuration is recommended: a dedicated Gateway node group using the c7gn series, with general workloads on the m7g series.

# Example nodeSelector
nodeSelector:
role: gateway
instance-type: c7gn.xlarge

7. Operations & Observability​

This section covers operations using Hubble, Cilium's observability tool.

Hubble Observability​

Observe flows in real time

# Install the Hubble CLI
brew install hubble

# Alternatively, download it directly
HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum}
sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin

# Set up port forwarding
cilium hubble port-forward &

# Stream flows in real time across all namespaces
hubble observe --all

# Filter flows for a specific Pod
hubble observe --pod default/frontend-5d5c7b6d8-abc12

# Filter HTTP traffic only
hubble observe --protocol http

# Monitor dropped packets
hubble observe --verdict DROPPED

# Observe traffic between specific namespaces
hubble observe --from-namespace production --to-namespace database

Example output:

Feb 12 10:23:45.123: default/frontend-abc12:8080 -> default/backend-xyz34:9090 http-request FORWARDED (HTTP/2 GET /api/users)
Feb 12 10:23:45.127: default/backend-xyz34:9090 <- default/frontend-abc12:8080 http-response FORWARDED (HTTP/2 200 4.2ms)
Feb 12 10:23:45.130: default/frontend-abc12 -> 8.8.8.8:53 dns-request FORWARDED (A query example.com)
Feb 12 10:23:45.145: 8.8.8.8:53 -> default/frontend-abc12 dns-response FORWARDED (A 93.184.216.34)

Generate a service map

# Generate a service dependency map in GraphViz format
hubble observe --all --output jsonpb | \
hubble-flow-graph > service-map.dot

# Convert to a PNG image
dot -Tpng service-map.dot -o service-map.png

# Open the real-time web UI
cilium hubble ui
# Open http://localhost:12000 in a browser

L7 protocol visibility

# Statistics by HTTP method
hubble observe --protocol http --output json | \
jq -r '.l7.http.method' | \
sort | uniq -c | sort -rn

# HTTP response code distribution
hubble observe --protocol http --output json | \
jq -r '.l7.http.code' | \
sort | uniq -c | sort -rn

# Trace gRPC method calls
hubble observe --protocol grpc

# Kafka topic traffic
hubble observe --protocol kafka

Prometheus Metrics​

Agent metrics (per node)

# Packets processed per second
rate(cilium_forward_count_total[5m])

# Dropped packet ratio
sum(rate(cilium_drop_count_total[5m]))
/
(sum(rate(cilium_drop_count_total[5m])) + sum(rate(cilium_forward_count_total[5m])))

# eBPF map utilization
cilium_bpf_map_pressure

# Saturation of the most saturated NAT mapping (%)
cilium_nat_endpoint_max_connection

# Node health probe latency (P99 of the current observation, seconds)
histogram_quantile(0.99, sum by (le, type, protocol, address_type) (cilium_node_health_connectivity_latency_seconds_bucket))

Gateway metrics (Envoy)

# Requests per second (RPS)
rate(envoy_http_downstream_rq_total[5m])

# P95 response latency
histogram_quantile(0.95, rate(envoy_http_downstream_rq_time_bucket[5m]))

# 5xx error rate
sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m]))
/
sum(rate(envoy_http_downstream_rq_xx[5m]))

# Backend connection failures
rate(envoy_cluster_upstream_cx_connect_fail[5m])

# Active connection count
envoy_http_downstream_cx_active

ENI metrics

# Attached interfaces with allocatable IP capacity (operator view)
cilium_operator_ipam_interface_candidates

# Available IP address count
cilium_operator_ipam_available_ips

# IP allocation rate
rate(cilium_operator_ipam_ip_allocation_ops[5m])

# ENI allocation errors
rate(cilium_operator_ipam_allocation_duration_seconds_count{status!="success"}[5m])

Grafana Dashboards​

Import official dashboards

# Official Cilium dashboard (Grafana ID: 16611)
# In Grafana, go to Dashboards > Import and enter 16611

# Alternatively, download the JSON file directly
curl -o cilium-dashboard.json https://grafana.com/api/dashboards/16611/revisions/latest/download

# Hubble dashboard (Grafana ID: 16612)
curl -o hubble-dashboard.json https://grafana.com/api/dashboards/16612/revisions/latest/download

Key dashboard panels:

  • Network Throughput (in/out bytes per second)
  • Packet Drop Rate by Reason
  • Connection Rate (new connections per second)
  • NAT Table Utilization
  • eBPF Map Pressure
  • Gateway Request Rate and Latency
  • Top Talkers (most active pods)
  • Service Dependency Map

Source IP Preservation​

AWS NLB documentation states that client IP preservation is disabled by default for TCP/TLS IP target groups. Set preserve_client_ip.enabled=true on a supported network path for Envoy to see the original client IP. Cilium Gateway preserves the peer address reaching it with either Cluster or Local externalTrafficPolicy, but cannot recover an address already SNATed by the NLB. Envoy sets X-Forwarded-For and X-Envoy-External-Address. Proxy Protocol v2 is a separate binary protocol, not an XFF-generation option.

The Gateway examples here use instance targets; TCP instance targets preserve client IP by default. The IP-target default above applies to a different target type. The explicit attribute below keeps the intent visible.

Add the X-Forwarded-For header

# gateway-with-xff.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-gateway
spec:
gatewayClassName: cilium
infrastructure:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: preserve_client_ip.enabled=true
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: tls-cert

Read the client IP in the backend (Python example) This example assumes that only the trusted Cilium Gateway can reach the backend and that Envoy’s trusted-hop configuration matches the actual proxy chain. Do not trust headers from clients that can reach the backend directly. TLS passthrough does not let Envoy set HTTP headers.

from flask import Flask, request

app = Flask(__name__)

@app.route('/api/info')
def get_client_ip():
# Trusted Gateway-only backend; do not trust the leftmost client-supplied XFF entry.
client_ip = request.headers.get('X-Envoy-External-Address')
return {
"client_ip": client_ip,
"proxy_peer_ip": request.remote_addr
}

Key Verification Commands​

# 1. Check Cilium status
cilium status --wait

# 2. Check Gateway status
kubectl get gateway cilium-gateway -o jsonpath='{.status.conditions[?(@.type=="Programmed")].status}'
# Output: True

# 3. Check HTTPRoute status
kubectl get httproute -A -o wide

# 4. Inspect Envoy listeners
kubectl exec -n kube-system ds/cilium -- cilium-dbg envoy admin listeners

# 5. Check backend endpoints
kubectl exec -n kube-system ds/cilium -- cilium-dbg service list

# 6. Check ENI allocation status
kubectl get ciliumnodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.eni.available}{"\t"}{.status.ipam.used}{"\n"}{end}'

# 7. Monitor flows for 30 seconds
hubble observe --all --since 30s

# 8. Verify network policies
cilium endpoint list

# 9. Inspect BPF map statistics
kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf metrics list

# 10. Test connectivity
cilium connectivity test --test egress-gateway,to-cidr

8. BGP Control Plane v2​

Cilium BGP Control Plane v2 advertises LoadBalancer IPs through BGP in on-premises data centers and hybrid environments.

info

This does not advertise AWS NLB addresses through Cilium BGP. The example assumes ownership and routing of a separate Service VIP pool reachable through BGP. It requires bgpControlPlane.enabled=true, selected nodes, TCP/179 connectivity, return paths, and router configuration. Direct Connect/VPN alone does not configure VIP routing. See the Cilium v2 configuration.

CiliumBGPPeeringPolicy CRD​

CiliumBGPPeeringPolicy belongs to the legacy v1 API. The v2 example uses the following three resources. Replace peer addresses and ASNs for your environment.

apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
name: bgp-policy
spec:
nodeSelector:
matchLabels:
role: gateway
bgpInstances:
- name: gateway
localASN: 64512
peers:
- name: router-1
peerAddress: 192.168.1.1
peerASN: 64500
peerConfigRef:
name: on-premises
- name: router-2
peerAddress: 192.168.1.2
peerASN: 64500
peerConfigRef:
name: on-premises
---
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
name: on-premises
spec:
ebgpMultihop: 10
timers:
connectRetryTimeSeconds: 120
holdTimeSeconds: 90
keepAliveTimeSeconds: 30
families:
- afi: ipv4
safi: unicast
advertisements:
matchLabels:
advertise: bgp
---
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: gateway-vips
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: Service
service:
addresses:
- LoadBalancerIP
selector:
matchLabels:
bgp-advertise: "true"

LoadBalancer IP Advertisement​

The pool’s 10.0.100.50/32 illustrates routing only. Replace it with a VIP you own that does not overlap actual VPC, Pod, or Service CIDRs and that routers can forward to the nodes. An existing workload labeled app: gateway-backend is assumed to listen on port 443. This manifest does not replace the Service generated by Cilium Gateway.

apiVersion: cilium.io/v2
kind: CiliumLoadBalancerIPPool
metadata:
name: gateway-vips
spec:
blocks:
- cidr: 10.0.100.50/32
serviceSelector:
matchLabels:
bgp-advertise: "true"
---
apiVersion: v1
kind: Service
metadata:
name: gateway-service
namespace: default
labels:
bgp-advertise: "true"
spec:
type: LoadBalancer
loadBalancerClass: io.cilium/bgp-control-plane
selector:
app: gateway-backend
ports:
- name: https
port: 443
targetPort: 443
protocol: TCP

Hybrid Environment Support​

Figure 5. Hybrid environment connecting on-premises and cloud nodes

Expand the diagram to read labels and follow connections.
Diagram

Traffic flow:

  1. An on-premises client requests the owned Service VIP
  2. The on-premises router selects the BGP route
  3. The preconfigured network and return paths deliver traffic to a Cilium node
  4. The Cilium Service datapath forwards to a ready backend

Check BGP status:

# Check BGP peer status
kubectl get ciliumbgpnodeconfigs

# Inspect advertised routes
cilium bgp routes advertised ipv4 unicast

# Check peer connection status
cilium bgp peers

Example output:

Local AS   Peer AS   Peer Address   Status    Uptime    Prefixes
64512 64500 192.168.1.1 Established 2h34m 1
64512 64500 192.168.1.2 Established 2h34m 1

Advertised Routes:
10.0.100.50/32 via 172.31.1.10 (self)

9. Hybrid Nodes Architecture and AI/ML Workloads​

When EKS Hybrid Nodes combine cloud and on-premises infrastructure, including dedicated GPU data centers, Cilium plays a key role in unifying the CNI and observability.

9.1 Why Cilium Is Needed for Hybrid Nodes​

The AWS VPC CNI runs only on EC2 instances inside a VPC. When on-premises GPU servers join a cluster through EKS Hybrid Nodes, they cannot use the VPC CNI, which results in separate CNIs for cloud and on-premises nodes.

There are three main approaches to CNI configuration in a hybrid node environment.

Table 1. Comparison of hybrid node CNI configurations

CategoryVPC CNI + CalicoVPC CNI + CiliumCilium Only (requires separate validation)
Cloud node CNIVPC CNIVPC CNICilium on standard EC2 nodes; excludes Auto Mode
On-premises node CNICalicoCilium for Hybrid NodesRequires compatible cluster-wide IPAM/routing design
IPAMSeparate by node typeVPC CNI separate from hybrid cluster-poolDo not assume one installation can mix ENI and cluster-pool
ObservabilityCNI-specific toolsCloud tools + hybrid HubbleHubble according to Cilium deployment scope
Gateway APIVerify a compatible controllerVerify Gateway features on Cilium nodesCNI choice alone does not establish Gateway feature support
Operational assessmentValidate routing and policiesCheck AWS Hybrid Nodes support scopeUnification alone does not establish lower complexity
Overlay Networking on On-Premises Nodes

On-premises Pod CIDRs can communicate through an overlay or a routed underlay. Native routing requires bidirectional Pod CIDR reachability. BGP can automate this, but static routing and other routing arrangements are also possible; BGP is not mandatory.

Admission Webhook Routing Issues and Solutions

For the EKS control plane, located in an AWS VPC, to reach webhook Pods on hybrid nodes, the Pod CIDRs must be routable. The official AWS documentation describes two approaches.

When Pod CIDRs are routable:

  • Advertise on-premises Pod CIDRs through BGP (recommended), static routes, or custom routing

When Pod CIDRs are not routable (without BGP):

  • Run webhooks on cloud nodes (the official AWS recommendation) — Pin webhook Pods to cloud nodes with nodeSelector or nodeAffinity. The API server can access them directly within the VPC
  • An overlay alone does not establish control-plane reachability — The EKS control plane is not a Cilium VXLAN endpoint. Even with an overlay, provide a supported route from the control plane to webhook Pods or place those webhooks on cloud nodes as described by AWS.
IPAM Considerations for a Cilium-Only Configuration

Cilium's ipam.mode=eni works only on AWS EC2 instances. There are three ways to implement a Cilium-only configuration in a hybrid cluster that includes on-premises nodes.

  1. Separate clusters + ClusterMesh: A cloud ENI cluster and an on-premises cluster-pool cluster can be operated separately. This is not one EKS Hybrid Nodes cluster; validate Pod/node reachability and support scope.
  2. Multi-pool IPAM: ipam.mode=multi-pool allocates CIDRs from CiliumPodIPPool resources. It does not mix the ENI and cluster-pool allocators by node label.
  3. One cluster-pool design: Using compatible cluster-pool/routing across all nodes requires separate validation. It gives up EC2 ENI IPAM benefits and still requires a solution for control-plane-to-Pod reachability.

9.2 Deployment option: Cilium + Gateway + llm-d​

Using Cilium as the CNI and supporting InferencePool in Cilium Gateway are separate capabilities. The following is an optional topology that retains the existing Cilium ingress and adds a compatible inference Gateway. Validate the cost and latency of two proxy hops, as well as timeout, retry, streaming, and authentication header forwarding behavior. Cloud and hybrid node locations are illustrative; validate each node's CNI/IPAM and Pod connectivity separately.

llm-d's v0.8.1 Gateway Mode can also handle ordinary Services and InferencePools through a single compatible Gateway. The EPP is an endpoint selection service, not a second Gateway.

Figure 6. Topology adding a compatible inference Gateway and llm-d EPP to the existing ingress

Expand the diagram to read labels and follow connections.
Diagram

9.3 Alternative architectures​

Table 2. Comparison of alternative architectures for inference Gateway integration

OptionConfigurationSelection Criteria / Trade-offs
Retain existing ingressCilium CNI + Cilium Gateway + compatible inference Gateway/EPPPreserves ingress ownership; requires managing an additional Gateway, policies, and observability
Single compatible GatewayCilium CNI + a Gateway that supports InferencePool/EPP integrationRoutes ordinary Services and inference pools through the same Gateway; verify support and security policies for the specific implementation and version
Direct integration with Cilium GatewayCilium CNI + Cilium Gateway + EPPChoose only after verifying InferencePool backends and EPP integration in the specific Cilium release; using Envoy alone does not establish support

The Cilium v1.18.0 Gateway API documentation does not establish that the InferencePool/EPP integration required here has been verified. This document does not claim to have validated direct Cilium integration. Installing a CRD alone does not enable a Gateway controller to handle a new backend kind.

9.4 Gateway API Inference Extension resources​

The baseline is llm-d v0.8.1 (2026-06-26), router v0.9.0, GIE v1.5.0, and Gateway API v1.5.1. InferencePool is the GIE inference.networking.k8s.io/v1 API, while InferenceObjective is llm-d's llm-d.ai/v1alpha2 alpha policy API. InferenceModel is the historical name of the earlier policy API. Older v1alpha1 examples and planned GA dates are not current deployment guidance.

Primary schemas: llm-d v0.8.1, InferencePool v1, InferenceObjective v1alpha2, HTTPRoute v1.

Define images, model arguments, replica counts, and GPU requests in workloads such as Deployments or LeaderWorkerSets. The Pool connects those Pods through labels, ports, and the EPP; the Objective expresses request priority.

This example defines routing resources only. It requires an existing llm-d namespace, inference-gateway Gateway, inference-epp Service (with an EPP on port 9002 configured to integrate with gpu-pool), and model server Pods with the app: vllm label and port 8000. It also requires Gateway API v1.5.1, the InferencePool CRD from GIE v1.5.0, the InferenceObjective CRD from router v0.9.0, and compatible controllers.

apiVersion: inference.networking.k8s.io/v1
kind: InferencePool
metadata:
name: gpu-pool
namespace: llm-d
spec:
selector:
matchLabels:
app: vllm
targetPorts:
- number: 8000
endpointPickerRef:
name: inference-epp
kind: Service
port:
number: 9002
failureMode: FailClose
---
apiVersion: llm-d.ai/v1alpha2
kind: InferenceObjective
metadata:
name: interactive
namespace: llm-d
spec:
poolRef:
group: inference.networking.k8s.io
kind: InferencePool
name: gpu-pool
priority: 10
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: inference-route
namespace: llm-d
spec:
parentRefs:
- name: inference-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /v1
backendRefs:
- group: inference.networking.k8s.io
kind: InferencePool
name: gpu-pool
port: 8000

priority: 10 is an example policy that expresses higher processing priority than requests with priority 0 in the same pool. When using flow control, enable the flowControl feature gate in the EPP and have a trusted authentication layer set the x-llm-d-inference-objective: interactive header. Creating an Objective alone does not automatically apply it to every request. Validate and reset the header so external users cannot raise their priority arbitrarily. This value does not reserve dedicated GPUs or act as a Pod PriorityClass.