$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY PREV LATEST AGE
sklearn-iris False 100 8m # READY=False with no URL = KServe controller did not complete ingress setup.
# No Knative Route was created. No external URL was assigned.
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY PREV LATEST AGE
sklearn-iris False 100 8m # READY=False with no URL = KServe controller did not complete ingress setup.
# No Knative Route was created. No external URL was assigned.
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY PREV LATEST AGE
sklearn-iris False 100 8m # READY=False with no URL = KServe controller did not complete ingress setup.
# No Knative Route was created. No external URL was assigned.
$ -weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
...
Status: Conditions: Message: Failed to reconcile ingress Reason: ReconcileError Status: False Type: IngressReady $ -weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
...
ERROR controller.inferenceservice Failed to reconcile ingress {"error": "virtual -weight: 500;">service not found: sklearn-iris.default.svc.cluster.local"}
$ -weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
...
Status: Conditions: Message: Failed to reconcile ingress Reason: ReconcileError Status: False Type: IngressReady $ -weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
...
ERROR controller.inferenceservice Failed to reconcile ingress {"error": "virtual -weight: 500;">service not found: sklearn-iris.default.svc.cluster.local"}
$ -weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
...
Status: Conditions: Message: Failed to reconcile ingress Reason: ReconcileError Status: False Type: IngressReady $ -weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
...
ERROR controller.inferenceservice Failed to reconcile ingress {"error": "virtual -weight: 500;">service not found: sklearn-iris.default.svc.cluster.local"}
# infrastructure/serving-stack/patches/inferenceservice-config-ingress.yaml
apiVersion: v1
kind: ConfigMap
metadata: name: inferenceservice-config namespace: kserve
data: ingress: |- { "ingressGateway": "knative-serving/knative-ingress-gateway", "ingressDomain": "example.com", "ingressClassName": "istio", "urlScheme": "http", "disableIstioVirtualHost": true, "disableIngressCreation": false }
# infrastructure/serving-stack/patches/inferenceservice-config-ingress.yaml
apiVersion: v1
kind: ConfigMap
metadata: name: inferenceservice-config namespace: kserve
data: ingress: |- { "ingressGateway": "knative-serving/knative-ingress-gateway", "ingressDomain": "example.com", "ingressClassName": "istio", "urlScheme": "http", "disableIstioVirtualHost": true, "disableIngressCreation": false }
# infrastructure/serving-stack/patches/inferenceservice-config-ingress.yaml
apiVersion: v1
kind: ConfigMap
metadata: name: inferenceservice-config namespace: kserve
data: ingress: |- { "ingressGateway": "knative-serving/knative-ingress-gateway", "ingressDomain": "example.com", "ingressClassName": "istio", "urlScheme": "http", "disableIstioVirtualHost": true, "disableIngressCreation": false }
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 2m
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 2m
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 2m
$ -weight: 500;">kubectl -n argocd get application serving-stack
NAME SYNC STATUS HEALTH STATUS
serving-stack OutOfSync Degraded $ -weight: 500;">kubectl -n argocd describe application serving-stack
...
Message: CustomResourceDefinition "services.serving.knative.dev" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes
$ -weight: 500;">kubectl -n argocd get application serving-stack
NAME SYNC STATUS HEALTH STATUS
serving-stack OutOfSync Degraded $ -weight: 500;">kubectl -n argocd describe application serving-stack
...
Message: CustomResourceDefinition "services.serving.knative.dev" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes
$ -weight: 500;">kubectl -n argocd get application serving-stack
NAME SYNC STATUS HEALTH STATUS
serving-stack OutOfSync Degraded $ -weight: 500;">kubectl -n argocd describe application serving-stack
...
Message: CustomResourceDefinition "services.serving.knative.dev" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes
# infrastructure/serving-stack/kustomization.yaml # 1. Use server-side apply to bypass the annotation size limit
commonAnnotations: argocd.argoproj.io/sync-options: ServerSideApply=true # 2. Ignore runtime-mutated fields on Knative CRDs
# (In ArgoCD Application spec)
ignoreDifferences: - group: apiextensions.k8s.io kind: CustomResourceDefinition name: services.serving.knative.dev jsonPointers: - /spec/preserveUnknownFields
# infrastructure/serving-stack/kustomization.yaml # 1. Use server-side apply to bypass the annotation size limit
commonAnnotations: argocd.argoproj.io/sync-options: ServerSideApply=true # 2. Ignore runtime-mutated fields on Knative CRDs
# (In ArgoCD Application spec)
ignoreDifferences: - group: apiextensions.k8s.io kind: CustomResourceDefinition name: services.serving.knative.dev jsonPointers: - /spec/preserveUnknownFields
# infrastructure/serving-stack/kustomization.yaml # 1. Use server-side apply to bypass the annotation size limit
commonAnnotations: argocd.argoproj.io/sync-options: ServerSideApply=true # 2. Ignore runtime-mutated fields on Knative CRDs
# (In ArgoCD Application spec)
ignoreDifferences: - group: apiextensions.k8s.io kind: CustomResourceDefinition name: services.serving.knative.dev jsonPointers: - /spec/preserveUnknownFields
$ -weight: 500;">kubectl -n argocd describe application ai-model-alpha
...
Message: admission webhook "inferenceservice.kserve-webhook-server.validator.webhook" denied the request: no endpoints available for -weight: 500;">service "kserve-webhook-server--weight: 500;">service" $ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-xxx 1/2 Running # only 1 of 2 ready $ -weight: 500;">kubectl -n kserve describe pod kserve-controller-manager-xxx kube-rbac-proxy: State: Waiting Reason: ImagePullBackOff Image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
Events: Warning Failed kubelet Failed to pull image: unexpected -weight: 500;">status code 403 Forbidden
$ -weight: 500;">kubectl -n argocd describe application ai-model-alpha
...
Message: admission webhook "inferenceservice.kserve-webhook-server.validator.webhook" denied the request: no endpoints available for -weight: 500;">service "kserve-webhook-server--weight: 500;">service" $ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-xxx 1/2 Running # only 1 of 2 ready $ -weight: 500;">kubectl -n kserve describe pod kserve-controller-manager-xxx kube-rbac-proxy: State: Waiting Reason: ImagePullBackOff Image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
Events: Warning Failed kubelet Failed to pull image: unexpected -weight: 500;">status code 403 Forbidden
$ -weight: 500;">kubectl -n argocd describe application ai-model-alpha
...
Message: admission webhook "inferenceservice.kserve-webhook-server.validator.webhook" denied the request: no endpoints available for -weight: 500;">service "kserve-webhook-server--weight: 500;">service" $ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-xxx 1/2 Running # only 1 of 2 ready $ -weight: 500;">kubectl -n kserve describe pod kserve-controller-manager-xxx kube-rbac-proxy: State: Waiting Reason: ImagePullBackOff Image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
Events: Warning Failed kubelet Failed to pull image: unexpected -weight: 500;">status code 403 Forbidden
# infrastructure/serving-stack/patches/
# kserve-controller-kube-rbac-proxy-image.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: kserve-controller-manager namespace: kserve
spec: template: spec: containers: - name: kube-rbac-proxy $patch: delete
# infrastructure/serving-stack/patches/
# kserve-controller-kube-rbac-proxy-image.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: kserve-controller-manager namespace: kserve
spec: template: spec: containers: - name: kube-rbac-proxy $patch: delete
# infrastructure/serving-stack/patches/
# kserve-controller-kube-rbac-proxy-image.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: kserve-controller-manager namespace: kserve
spec: template: spec: containers: - name: kube-rbac-proxy $patch: delete
$ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-yyy 1/1 Running # fixed $ -weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
NAME ENDPOINTS AGE
kserve-webhook-server--weight: 500;">service 10.42.0.23:9443 45s
$ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-yyy 1/1 Running # fixed $ -weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
NAME ENDPOINTS AGE
kserve-webhook-server--weight: 500;">service 10.42.0.23:9443 45s
$ -weight: 500;">kubectl -n kserve get pods
NAME READY STATUS
kserve-controller-manager-yyy 1/1 Running # fixed $ -weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
NAME ENDPOINTS AGE
kserve-webhook-server--weight: 500;">service 10.42.0.23:9443 45s
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris \ -o jsonpath='{.-weight: 500;">status.url}'
http://sklearn-iris.default.example.com $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://sklearn-iris.default.example.com/v1/models/sklearn-iris:predict
<html><head><title>405 Not Allowed</title></head>... # The request hit the public example.com server, not our Kourier gateway.
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris \ -o jsonpath='{.-weight: 500;">status.url}'
http://sklearn-iris.default.example.com $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://sklearn-iris.default.example.com/v1/models/sklearn-iris:predict
<html><head><title>405 Not Allowed</title></head>... # The request hit the public example.com server, not our Kourier gateway.
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris \ -o jsonpath='{.-weight: 500;">status.url}'
http://sklearn-iris.default.example.com $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://sklearn-iris.default.example.com/v1/models/sklearn-iris:predict
<html><head><title>405 Not Allowed</title></head>... # The request hit the public example.com server, not our Kourier gateway.
# Step 1: Get the predictor pod name
-weight: 500;">kubectl -n default get pods \ -l serving.knative.dev/revision=sklearn-iris-predictor-00001 # Step 2: Port-forward directly to the predictor container
-weight: 500;">kubectl -n default port-forward \ pod/sklearn-iris-predictor-00001-deployment-<hash> 18080:8080 # Step 3: Predict (no Host header, no Kourier, no DNS needed)
-weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
# Step 1: Get the predictor pod name
-weight: 500;">kubectl -n default get pods \ -l serving.knative.dev/revision=sklearn-iris-predictor-00001 # Step 2: Port-forward directly to the predictor container
-weight: 500;">kubectl -n default port-forward \ pod/sklearn-iris-predictor-00001-deployment-<hash> 18080:8080 # Step 3: Predict (no Host header, no Kourier, no DNS needed)
-weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
# Step 1: Get the predictor pod name
-weight: 500;">kubectl -n default get pods \ -l serving.knative.dev/revision=sklearn-iris-predictor-00001 # Step 2: Port-forward directly to the predictor container
-weight: 500;">kubectl -n default port-forward \ pod/sklearn-iris-predictor-00001-deployment-<hash> 18080:8080 # Step 3: Predict (no Host header, no Kourier, no DNS needed)
-weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
-weight: 500;">kubectl -n kourier-system port-forward svc/kourier 18080:80 -weight: 500;">curl -sS \ -H 'Host: sklearn-iris-predictor.default.127.0.0.1.sslip.io' \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict
-weight: 500;">kubectl -n kourier-system port-forward svc/kourier 18080:80 -weight: 500;">curl -sS \ -H 'Host: sklearn-iris-predictor.default.127.0.0.1.sslip.io' \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict
-weight: 500;">kubectl -n kourier-system port-forward svc/kourier 18080:80 -weight: 500;">curl -sS \ -H 'Host: sklearn-iris-predictor.default.127.0.0.1.sslip.io' \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 45m $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 45m $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
$ -weight: 500;">kubectl -n default get inferenceservice sklearn-iris
NAME URL READY AGE
sklearn-iris http://sklearn-iris.default.example.com True 45m $ -weight: 500;">curl -sS \ -H "Content-Type: application/json" \ -d '{"instances":[[5.1,3.5,1.4,0.2],[6.2,3.4,5.4,2.3]]}' \ http://127.0.0.1:18080/v1/models/sklearn-iris:predict {"predictions":[0,2]}
-weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager -c manager --tail=50
-weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager -c manager --tail=50
-weight: 500;">kubectl -n default describe inferenceservice sklearn-iris
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager --tail=50
-weight: 500;">kubectl -n kserve logs deploy/kserve-controller-manager -c manager --tail=50
-weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n kserve describe endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n default get ksvc
-weight: 500;">kubectl -n default get route
-weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n kserve describe endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n default get ksvc
-weight: 500;">kubectl -n default get route
-weight: 500;">kubectl -n kserve get endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n kserve describe endpoints kserve-webhook-server--weight: 500;">service
-weight: 500;">kubectl -n default get ksvc
-weight: 500;">kubectl -n default get route
-weight: 500;">kubectl -n kserve get configmap inferenceservice-config -o yaml
-weight: 500;">kubectl -n kserve get pods -o wide
-weight: 500;">kubectl -n kserve describe pod <pod-name>
-weight: 500;">kubectl -n kserve get configmap inferenceservice-config -o yaml
-weight: 500;">kubectl -n kserve get pods -o wide
-weight: 500;">kubectl -n kserve describe pod <pod-name>
-weight: 500;">kubectl -n kserve get configmap inferenceservice-config -o yaml
-weight: 500;">kubectl -n kserve get pods -o wide
-weight: 500;">kubectl -n kserve describe pod <pod-name> - No Istio -weight: 500;">service mesh: No mTLS between services, no advanced traffic management. Acceptable for local dev; requires a replacement security layer in production.
- kube-rbac-proxy removed: Prometheus metrics from the KServe controller are unavailable. Re-add this sidecar from a working registry before any production deployment.
- Port-forward for inference: The Host-header workaround is local only. Cloud deployment requires a real ingress with DNS and TLS. On EKS, swap Kourier for an ALB and set ingressDomain to your real domain. See the Cloud Promotion Guide in the repository. - infrastructure/serving-stack/patches/inferenceservice-config-ingress.yaml — Kourier config patch
- infrastructure/serving-stack/patches/kserve-controller-kube-rbac-proxy-image.yaml — sidecar removal patch
- infrastructure/kserve/sklearn-runtime.yaml — ClusterServingRuntime definition
- docs/CLOUD_PROMOTION_GUIDE.md — how to replace Kourier with ALB/NGINX on EKS/GKE
- docs/REALITY_CHECK_MILESTONE_3_GOLDEN_PATH.md — nine Backstage failures documented at the same depth
- docs/REALITY_CHECK_MILESTONE_4_GUARDRAILS.md — how kyverno-cli exits 0 on violations and why $PIPESTATUS[0] matters