feat: add the OCI image source to ModelArtifact - #666
Conversation
A fourth source union member, Image, carrying a digest-pinned OCI image reference. The digest is the artifact's whole identity: it pins the image's bytes, not the hub manifest digest, and the operator never reads the registry, so status.resolved stays claim-shaped. Delivery mounts the image root read-only through a Kubernetes image volume (apiserver and kubelet >= 1.35, containerd >= 2.1), outside the node cache plugin chain. ModelDeploymentModelDelivery gains the Image value for the consumer status. Signed-off-by: thxCode <thxcode0824@gmail.com>
Admission accepts the image member: exactly-one names it, the reference must pin a digest (a tag is mutable, so one artifact could deliver different weights on different pulls), patterns are refused because an image is mounted whole, and creation is refused on an apiserver below the ImageVolume default-on floor (1.35), where the volume field would be dropped silently. The gate reads the startup version snapshot through a swappable function because the snapshot's Configure ignores later calls, and an unreadable version refuses. ModelPrefetch refuses an image artifact: it never enters the node cache, so there is nothing to warm. Mutation-validated: the floor check, the gate, the digest case rule and the prefetch refusal each shown able to go red. Signed-off-by: thxCode <thxcode0824@gmail.com>
An image artifact resolves claim-shaped: no network, no nodes aggregation, the pinned reference as the whole identity, and a KV identity that falls back to the artifact UID. Both consumers render the weights as one image volume mounted read-only at the model path - no cache, no download environment, no revision argument - and the status names the Image delivery. An Instance pinned to a node checks that node's kubelet and containerd against the feature floors before rendering, and the soft placement preference names the nodes whose status.images lists the reference, hostname-sorted and capped. Mutation-validated: the render branch, the node pre-check and the preference match each shown able to go red. Signed-off-by: thxCode <thxcode0824@gmail.com>
One page owns the image source: the digest contract and what the operator does not promise, the build recipe measured byte-equal to its hub commit, image-volume delivery on both consumers, the version floors with the PSA correction, the double-storage accounting and why the CRI size must not be used for capacity, the three image-GC rules, and registry mirrors. model-artifact.md gains the union member, the delivery table column, the status wording and the requirements line, linking the new page; the docs index and the docs skill's routing tables follow. Signed-off-by: thxCode <thxcode0824@gmail.com>
The admission and renderer halves of the image source landed, but the artifact reconciler's source switch still answered UnsupportedSource: an image artifact resolved nowhere, so no consumer could ever mount it. reconcileImage mirrors a claim: the resolution is the pinned spec being stored, with no registry read, no revalidation and no nodes aggregation. Found by case-112 on a live cluster, mutation-validated. Signed-off-by: thxCode <thxcode0824@gmail.com>
Signed-off-by: thxCode <thxcode0824@gmail.com>
The five draft questions were adjudicated on 2026-09-27 and the AC13 ordering wording records the implementation (hostname-sorted, name-only: Node.status.images carries no referenced-now signal). Found during the live e2e: the artifact reconciler's own source switch still answered UnsupportedSource; reconcileImage closed it. Signed-off-by: thxCode <thxcode0824@gmail.com>
|
🔍 OpenCodeReview found 3 issue(s) in this PR.
📄
|
Stating the floors in the requirements bullet repeated what model-image-source.md owns; the bullet keeps one clause and the link. Signed-off-by: thxCode <thxcode0824@gmail.com>
What type of PR is this?
/kind enhancement
/kind api-change
/area worker
/area testing
What this PR does / why we need it:
Adds an
imagesource toModelArtifact: weights identified by a digest-pinned OCI imagereference (
registry/repository@sha256:<64 hex>) and delivered by mounting a Kubernetes imagevolume — kubelet pulls the pinned image on the node that needs it, and nothing enters the
model-manager plugin's cache chain.
image,the reference must pin a digest (a mutable tag would let one artifact deliver different weights
on different pulls), patterns are refused because an image is mounted whole, and creation is
refused on an apiserver below the ImageVolume default-on floor (1.35), where the volume field
would be dropped silently. The gate reads the startup version snapshot through a swappable
function; an unreadable version refuses.
ModelPrefetchrefuses an image artifact: there isnothing to warm.
Resolved=Trueon thefirst pass with a claim-shaped status — no revision, no manifest digest (the pinned reference is
the whole identity; the KV identity falls back to the artifact UID), no
status.nodes, norevalidation.
ModelDeploymentand anInstancemount one imagevolume, read-only, at
/var/lib/gpustack/model— no cache volume, no download environment, no--revision, no ephemeral-storage raise — andstatus.model.deliveryreportsImage. AnInstance pinned to a node checks that node's kubelet (≥ 1.35) and containerd (≥ 2.1) before
rendering. The soft placement preference names the nodes whose
Node.status.imageslist thereference, hostname-sorted and capped, never a filter.
its hub commit, version floors, double storage 1.79×/2.00×/2.26× and why the CRI size must not
be used for capacity, the three image-GC rules, registry mirrors);
model-artifact.md, the docsindex and the skill routing tables follow. No new Setting.
claim-shaped resolution, and an Instance reading the image's fixture byte for byte — with the
fixture generated and pushed into an in-cluster registry over the node's loopback, so no
containerd configuration is touched.
Deviation from the adjudicated draft, recorded in the spec: AC13's preference ordering is
hostname-sorted, name-only —
Node.status.imagescarries no referenced-now or last-used signal,so the digest preference's serving-now ordering has nothing to sort on. Weight, cap and softness
stand.
Which issue(s) this PR links to:
None
Special notes for your reviewer:
installing
gpustack/gpustack-operator:chart-testbuilt from this branch; v1.33.12's first twoattempts failed on Docker Hub anonymous rate limiting — the chart's mirrored dependency images
pulled from docker.io — and passed with the images preloaded into the nodes, zero docker.io
pull events in the final run).
make generatewas verified in a clean checkout: zero diff.Does this PR introduce a user-facing change?