Repository navigation
Let engines serve on another port and report health on another path - #573
Draft
dennis-upbound wants to merge 4 commits into
Draft
dennis-upbound wants to merge 4 commits into
dennis-upbound wants to merge 4 commits into
Conversation
|
Docs preview: https://modelplane-docs-pr-573.vercel.app (ready once the site's Content workflow finishes) |
4 tasks done
dennis-upbound
force-pushed
the
dennis/engine-port-probe
branch
2 times, most recently
from
October 26, 2026 20:40
0267e84 to
175742e
Compare
A ModelDeployment could only run an engine that listens on port 8000 and answers GET /health. The XRDs pinned requests to 8000 in their port rules and rejected a readinessProbe on the engine container, so an engine on another port or with another health path never went Ready. This adds serving.port, the port every serving pod receives requests on, and lets the engine container set its own readinessProbe. Both are optional. Unset, the port is 8000 and Modelplane probes /health as before. serving.port has no schema default, so a deployment that doesn't set it stores and propagates exactly what it did. The serving port is one value for the whole replica because a replica's serving pods share one InferencePool, so it lives on serving rather than on each engine. The port rules that checked 8000 move up to the spec, where they can see serving.port. Probe paths must start with "/", and probe ports must be a name or a number from 1 to 65535. A Worker has no http port. Its engine's probe must refer to a port that the engine container declares, or the worker pod never goes Ready. Towards modelplaneai#462. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
compose-model-replica fixed the engine's default http port, its readiness probe and the InferencePool's targetPorts at 8000, and always probed GET /health. It now reads serving.port, defaulting to 8000, for all three, in the native, llm-d and Grove backends alike. An engine that sets its own readinessProbe gets it as written in place of the default probe, the same way a sidecar's probe passes through. Modelplane doesn't probe a Worker, so a probe set on a Worker's engine passes through as written, like its ports. Replicas that set neither compose the same resources as before. Towards modelplaneai#462. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
The e2e deployment's mock engine listened on 8000 and answered /health, so the end-to-end run only covered the default port and probe. It now listens on 8080, sets serving.port to match, and reports readiness at /v1/models through its own readinessProbe. It no longer answers /health. The pod goes Ready only if the engine's probe replaces the default one, and a request through the gateway succeeds only if the InferencePool targets 8080. Towards modelplaneai#462. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
The docs said a serving pod receives requests on port 8000, and some recipe comments said the engine must listen there. Deploy a Model now describes serving.port and the engine's readinessProbe, with an example of an engine on 8080 that reports readiness at /v1/models. The routing guide's port rules refer to the serving port, and the recipe comments call 8000 the default. Fixes modelplaneai#462. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
dennis-upbound
force-pushed
the
dennis/engine-port-probe
branch
from
October 27, 2026 04:01
175742e to
d483b10
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of your changes
Fixes #462.
A
ModelDeploymentcould only run an engine that listens on port 8000 and answersGET /health. The engine's defaulthttpport, its readiness probe, and the InferencePool'stargetPortswere all fixed at 8000, the probe always asked/health, and the XRD rejected areadinessProbeon the engine container. An engine like the one in the issue, on 8080 with readiness at/v1/models, never went Ready.This adds
serving.port, the port every serving pod receives requests on, and lets the engine container set its ownreadinessProbe. Both mirror what's already there:portsandreadinessProbealready exist on member containers for sidecars. The engine's probe uses the same corev1-shaped field, and a probe set there replaces the default whole. Before, the engine had to be on 8000 and answer/health. After:The port is on
servingrather than on the engine because a replica's serving pods share one InferencePool, and itstargetPortsapplies to every pod. WithPrefillDecode, the prefill engine and the decode sidecar both serve on it. The XRD port rules that hardcoded 8000 now compare againstserving.port, so they move from each member up to the spec, whereservingis visible. A rejection now points at the spec rather than at the member. The engine'shttpport defaults to the serving port, so the collector's scrape (by port name) and the default probe follow it. Neither ModelEndpoint URLs nor the gateway include the engine port, so neither changes.serving.porthas no schema default, so a deployment that doesn't set it stores and propagates exactly what it did before, and its replicas compose the same resources. New XRD validation rejects a probe path that doesn't start with/and a probe port outside 1 to 65535. These apply to sidecar probes too.Decisions:
httpport. The XRD rejects a Worker probe that refers to a port its engine container doesn't declare, because the worker pod would never go Ready.self.?serving.?port.orValue(8000). CRD validation rules accept CEL optional types from Kubernetes 1.29:k8s.io/apiserveraddscel.OptionalTypes()to the base environment at version 1.28, and new rules compile against the previous minor version. The install guide requires a Kubernetes release that the project still supports, and the oldest of those is newer than 1.29.serving.portneeds nothing else.modedefaults toUnifiedbefore validation, soserving: {port: 8080}is enough.serving.portset. Its probe asks/v1/models, and it no longer answers/health. It's the only end-to-end coverage of a port other than 8000, and the run fails if the engine's probe or the pool's port isn't applied. The unit tests and the live regression scenario cover the default port and probe.Alternatives I considered:
httpport when no sidecar declares 8000, with no new field. That breaks withPrefillDecode, where the decode pod's front port is the sidecar's, and it infers intent from which ports happen to be declared.portandhealth(a path) on the engine. That covers fewer probes than areadinessProbe, and it would duplicate theportsandreadinessProbefields containers already have.The second limit in the issue is out of scope: the endpoint picker rejects request paths it has no parser for, so a non-chat engine now goes Ready but still isn't reachable through the gateway. A follow-up issue will track it.
I haven't run e2e on this branch. I checked the CEL rules against a kube-apiserver 1.34 (envtest) with
kubectl apply --dry-run=server, using valid and invalidModelDeployments andModelReplicas and everyModelDeploymentunderdocs/manifestsande2e/manifests.I have:
nix flake check(or./nix.sh flake check) and made sure it passes.git commit -s.🤖 Generated with Claude Code