Symptom
kubectl get isvc shows READY as False or Unknown, URL is empty or points at an old revision, and clients get 404, 503 or connection resets. A large LLM is often involved, because the default timeouts and disk sizes in KServe and Knative were set for small models.
Work top-down: conditions first, then the pod, then the layer the conditions point to.
Check 1: read the conditions
NS=llm; ISVC=llama-chat
kubectl get isvc "$ISVC" -n "$NS"
kubectl get isvc "$ISVC" -n "$NS" -o jsonpath=\
'{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'
The message field is the most useful single line KServe gives you. Map it to a layer:
| Condition false | Usual layer | Go to |
|---|---|---|
PredictorReady with RevisionMissing or RevisionFailed | Knative revision never became ready | Check 3 |
PredictorReady, pod in Init | storage-initializer download | Check 2 |
PredictorReady, pod Running but not ready | Model server still loading or crashing | Check 4 |
PredictorReady, pod Pending | GPU or node scheduling | Check 5 |
IngressReady with ReconcileError | Ingress config (Istio, Gateway API, Kourier) | Check 6 |
Condition names and reasons vary by KServe version; check your build's output rather than matching strings in alerts.
Check 2: the storage-initializer
KServe injects an init container named storage-initializer that copies the model from storageUri into /mnt/models before the server starts.
kubectl get pods -n "$NS" -l serving.kserve.io/inferenceservice="$ISVC"
POD=$(kubectl get pods -n "$NS" -l serving.kserve.io/inferenceservice="$ISVC" -o name | head -1)
kubectl logs -n "$NS" "$POD" -c storage-initializer --tail=50
kubectl describe -n "$NS" "$POD" | sed -n '/Init Containers/,/Containers:/p'
| Log or state | Cause | Fix |
|---|---|---|
403, AccessDenied, NoCredentialProviders | The service account has no storage credentials | Set spec.predictor.serviceAccountName to an account with an annotated S3/Azure secret or workload identity |
401 or GatedRepoError on hf:// | Missing Hugging Face token | Mount HF_TOKEN from a secret; confirm the account accepted the model licence |
Init container OOMKilled | Default init container limits are too small for large archives | Raise the storage initializer resources in the inferenceservice-config ConfigMap |
Pod Evicted, ephemeral-storage | 15-140 GB of weights in an emptyDir on a small node disk | Larger node OS disk, or switch to a PVC (pvc://) or OCI modelcar (oci://) |
| Download restarts from zero after every crash | Weights are re-copied on each pod start | Pre-stage weights on a PVC or a modelcar image so restarts are cheap |
kubectl get configmap inferenceservice-config -n kserve -o jsonpath='{.data.storageInitializer}'
Tip
For models over ~20 GB, stop downloading on every pod start. A ReadWriteMany PVC populated once, or a modelcar image cached on the node, cuts cold start from many minutes to seconds and removes the init container as a failure point. Check that your KServe version has modelcar enabled before you use
oci://.
Check 3: Knative revision never becomes ready (Serverless mode)
In Serverless mode each spec change creates a Knative revision. If the revision is not ready inside the progress deadline, Knative marks it failed and the ISVC reports RevisionMissing or keeps serving the previous revision.
kubectl get ksvc -n "$NS"
kubectl get revision -n "$NS" -l serving.knative.dev/service="${ISVC}-predictor"
kubectl describe revision -n "$NS" <revision-name> | sed -n '/Conditions/,$p'
The Knative service name is <isvc>-predictor on recent versions and <isvc>-predictor-default on older ones.
| Revision message | Cause | Fix |
|---|---|---|
Initial scale was never achieved or ProgressDeadlineExceeded | Model load (download plus GPU load) takes longer than the deadline | Raise serving.knative.dev/progress-deadline on the predictor |
Unable to fetch image | Knative resolves image tags to digests and cannot reach your private registry | Add the registry to registries-skipping-tag-resolving in config-deployment, or give the controller pull credentials |
ContainerMissing | Image does not exist or is not readable | Fix the image reference or pull secret |
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llama-chat
namespace: llm
annotations:
serving.knative.dev/progress-deadline: "1800s"
autoscaling.knative.dev/min-scale: "1"
spec:
predictor:
minReplicas: 1
model:
modelFormat:
name: huggingface
storageUri: pvc://models-pvc/llama-3.1-8b-instruct
resources:
limits:
nvidia.com/gpu: "1"
memory: 48Gi
requests:
cpu: "6"
memory: 48Gi
Warning
With
minReplicas: 0, every scale-from-zero runs the full model download and load path. For LLMs this means minutes of 503s or held requests. Keep at least one replica for anything user-facing.
Many teams running LLMs move to RawDeployment mode (called Standard on newer releases). It removes Knative revisions and the queue-proxy sidecar, so a plain Deployment and HPA or KEDA handle scaling. You lose scale-to-zero and revision-based canaries.
kubectl annotate isvc "$ISVC" -n "$NS" serving.kserve.io/deploymentMode=RawDeployment --overwrite
Check whether your version accepts the mode change on an existing ISVC; some versions need the resource deleted and recreated.
Check 4: running but never ready
The model server is up but its readiness probe fails, or it crash-loops after loading.
kubectl logs -n "$NS" "$POD" -c kserve-container --tail=100
kubectl logs -n "$NS" "$POD" -c kserve-container --previous --tail=100
kubectl get pod -n "$NS" "${POD#pod/}" -o jsonpath='{.status.containerStatuses[*].restartCount}'
| Log | Cause | Fix |
|---|---|---|
CUDA out of memory during load | Model plus KV cache exceed GPU memory | Lower --max-model-len, use a quantized model, or more GPUs with tensor parallel |
No CUDA GPUs are available | GPU requested but runtime or driver missing on node | Check the GPU Operator and the node's nvidia.com/gpu allocatable |
| Killed by liveness probe mid-load | Probe fires before weights finish loading | Add a startupProbe with a long enough window |
trust_remote_code errors | Model needs custom code | Prefer a model that doesn't; if you must, pin the revision and pass the flag deliberately |
Pass runtime flags as args on the predictor. With the Hugging Face runtime and the vLLM backend, engine flags pass through, but which flags are accepted depends on the runtime image version.
Check 5: pending on GPUs
kubectl describe pod -n "$NS" "$POD" | sed -n '/Events/,$p'
kubectl get nodes -l nvidia.com/gpu.present=true \
-o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\\.com/gpu
Insufficient nvidia.com/gpu means every GPU is allocated or the cluster autoscaler can't add a node. untolerated taint means the predictor is missing the GPU node pool toleration. Add tolerations and a nodeSelector under spec.predictor.
A subtle case: during a Serverless rollout, the new revision needs its own GPU while the old one still holds one. On a fully packed pool the new revision stays Pending until the progress deadline expires, so each update fails. Keep one spare GPU of headroom, or use RawDeployment with maxSurge: 0 and accept a short capacity dip.
Check 6: ingress
kubectl get configmap inferenceservice-config -n kserve -o jsonpath='{.data.ingress}'
kubectl get virtualservice,httproute -n "$NS" 2>/dev/null
IngressReady=False with ReconcileError usually means the ingress config expects Istio while the cluster runs something else, or the ingress gateway named there doesn't exist. Fix the ConfigMap, then restart the KServe controller.
Prevent it next time
- Stage weights on a PVC or modelcar image, not a per-pod download.
- Set
progress-deadline,startupProbeandminReplicasfrom the measured cold-start time of each model, plus a margin. - Keep one spare GPU per node pool used for rollouts, or use RawDeployment for large models.
- Alert on an ISVC that stays not Ready for more than 15 minutes, using kube-state-metrics custom resource state or a simple CronJob that reads
.status.conditions. - Record image digest,
storageUriand runtime version for every ISVC so you can diff a failing rollout against the last good one.