Troubleshooting and teardown¶
Symptoms you may meet at each step, what causes them and where to look, followed by the teardown in reverse order of deployment.
Before you start¶
Have the chapter that produced the symptom open; the Check column names the command or page.
Focus of this page: Most symptoms are a stale rule (pods or the EPP Service were recreated) or a gateway build without epp.
Topology and execution locations¶
flowchart TD
C["Client<br/>sends to VIP:8080"] --> G["loxilb-igw on the control-plane node<br/>rule: mode 4, eppEndpoint"]
G -->|"ext_proc gRPC :9002<br/>one question per request"| E["Endpoint Picker<br/>Deployment + ClusterIP Service"]
E -.->|"watches"| P["InferencePool<br/>selector → model-server pods"]
E -->|"answers with one pod address"| G
G -->|"forwards to the pod the EPP named, :8000"| V["vLLM pods<br/>one per GPU node"]
O["Operator shell"] -.->|"REST :11111, kubectl, pod :8000/metrics"| G
style G fill:#e1f5fe,stroke:#0288d1,color:#1f2937
style E fill:#fff8e1,stroke:#f9a825,color:#1f2937
style V fill:#e8f5e9,stroke:#43a047,color:#1f2937
| Where | What happens here |
|---|---|
| Operator shell (the control-plane node in this guide) | kubectl, curl against the gateway's REST API on :11111, the harness, pod :8000/metrics reads |
| Client | sends OpenAI-style requests to the gateway VIP, 192.0.2.10:8080 |
| loxilb-igw (DaemonSet, hostNetwork on the control-plane node) | holds the rule, asks the EPP per request, forwards to the pod it names |
| Endpoint Picker (Deployment + ClusterIP Service) | watches the InferencePool, keeps the prefix-cache index, answers over ext_proc gRPC :9002 |
| vLLM pods (one per GPU node) | serve the model on :8000; their metrics are the independent attribution of which pod served a request |
Reading path and reference¶
Symptoms¶
| Symptom | Cause | Check |
|---|---|---|
503 {"error":"epp_unavailable","detail":"the endpoint picker did not answer"} |
the EPP is not reachable from the gateway node: no pod behind the Service, wrong ClusterIP or port in eppEndpoint, TLS mismatch (eppPlaintext), or the decision took longer than eppTimeoutMs |
kubectl -n $NS get pod,svc -l app.kubernetes.io/name=pd-disaggregation-epp; loxilb_ai_epp_duration_seconds buckets; rule.sh show |
503 {"error":"epp_no_endpoint", ...} |
the EPP named a pod that is not in the rule's endpoints, or is marked down: pods were recreated and the rule still lists the old IPs |
compare kubectl get pod -o wide with rule.sh show; re-run rule.sh epp |
| rule POST rejected | mode is not 4, or eppEndpoint is longer than 255 bytes |
the error body names the field |
loxilb_ai_epp_requests_total does not move |
the request hit a different VIP:port, or the rule has no eppEndpoint |
rule.sh show; curl $LB/config/loadbalancer/all |
no Request handled lines |
the EPP runs at --v=2, or the log was read from the wrong container |
Smoke test's patch; -c epp |
| all rules gone after the gateway pod restarted | /etc/loxilb is not mounted from the host |
kubectl -n kube-system get ds loxilb-lb -o jsonpath='{.spec.template.spec.volumes}' |
503 no_route from the VIP |
the last rule on that VIP:port was deleted and the listener is kept open | create the rule |
kube-loxilb: no <pool>-inference Service |
kube-loxilb runs without --inferenceExtension, or the pool is not attached by an HTTPRoute to a Gateway of class loxilb-gc |
kubectl -n kube-system logs deploy/kube-loxilb; kubectl -n $NS get gateway,httproute |
kube-loxilb rejects --inferenceExtension at start |
the image is not :epp-annotation (an older tag has no InferencePool manager), or --gatewayAPI is missing |
kubectl -n kube-system get deploy kube-loxilb -o jsonpath='{.spec.template.spec.containers[0].image}'; kubectl -n kube-system logs deploy/kube-loxilb |
kube-loxilb: InferenceGatewayRequired event on the pool's Service |
the gateway behind --loxiURL is plain loxilb, not loxilb-igw |
Deploy loxilb-igw's /version check: product must be loxilb-inference-gateway |
kube-loxilb: ExternalIPUnavailable event right after applying the Gateway |
the Gateway's ingress Service held the listener port for a moment before the route was seen | wait; the pool's Service takes the port once it is released |
kube-loxilb: ResolvedRefs=False, reason InvalidExtensionRef |
the Service is missing from the pool's namespace, headless or ExternalName, or port is missing or not on the Service |
the condition's message names the cause |
kube-loxilb: pool Accepted=False, reason NotSupportedByParent |
lbmode is not fullproxy, pd-disagg is set, or the pool has more than one targetPorts entry |
the condition's message |
kube-loxilb: LoxiLBRejected, externalIP <VIP> is not an address of this gateway |
the VIP in --cidrPools is not configured on the gateway node |
ip addr on that node |
kube-loxilb: EndpointPickerUnsupported event |
the gateway build does not list epp |
Deploy loxilb-igw's /version check |
| guidellm's prefix-cache reuse is far from the post's numbers | TOKENIZER differs from the served model |
config.env |
| the SSH session to the gateway node drops when the gateway pod restarts | eBPF programs reload on the node's interfaces | 26 § Restarting the gateway on a node that carries other traffic |
Teardown¶
bash "$MF/rule.sh" delete # direct rule
kubectl delete -f "$MF/50-gateway.yaml" # kube-loxilb path: the generated Service and rule go with these; kube-loxilb stays
kubectl -n $NS patch deploy pd-disaggregation-epp --type=json -p "[{\"op\":\"replace\",\"path\":\"$P\",\"value\":\"--v=2\"}]" # $P from the smoke test
kubectl delete -f "$MF/40-loxilb-igw.yaml"
kubectl delete -f "$MF/30-inferencepool.yaml" -f "$MF/23-epp-service.yaml" -f "$MF/22-epp.yaml" -f "$MF/21-epp-rbac.yaml" -f "$MF/20-epp-config.yaml" -f "$MF/10-vllm.yaml"
kubectl delete -f "$MF/01-namespace.yaml"
The harness's result folders under ~/epp_repro_* are left in place.
Check your result¶
After teardown: GET /config/loadbalancer/all lists no rule on the VIP:port; kubectl get ns llm-d-pd-disaggregation reports not found; kubectl -n kube-system get ds loxilb-lb reports not found.
Next steps and cleanup¶
The harness's result folders under ~/epp_repro_* are left in place. kube-loxilb, if you used it, is untouched.