Repository navigation
k8s-aibom: validate h100-gke-cos-inference end to end on real GKE #2310
Description
Part of #2271.
Goal
Prove the full AICR workflow for k8s-aibom end to end on a real GKE cluster with H100 GPUs, using h100-gke-cos-inference as the target environment: generate the recipe, render the bundle, deploy it, pass validation, and produce a genuine CycloneDX ML-BOM from a real GPU inference workload.
This satisfies ADR-019's Follow-Up Decisions requirement for managed-cluster qualification and measured controller/API-server cost, which is the one requirement in that list that cannot be met by inspection or by a simulated cluster.
Why this is not covered by what already exists
tools/k8s-aibom-test/runs on Kind. Kind is a useful behavioral proof and it is what qualified the component, but it is a simulation of a managed cluster, not one. The control plane, node images, GPU stack, and API-server behavior all differ.- The operational envelope recorded in ADR-019 (14s convergence at 1,000 workloads, 230.4 mCPU / 47.5 MiB burst) was measured on Kind. GKE's managed control plane is the thing that actually needs qualifying.
- Registry adoption ([Epic]: k8s-aibom — registry-only component adoption (ADR-019) #2234) deliberately proved the component is safe to offer. It proved nothing about a real cluster running it under a real workload.
Explicitly not a stock-recipe change
Per the ADR-019 amendment, registry presence asserts the component is qualified and offerable; stock recipe presence asserts AICR ships it by default, and that is gated on requalification plus the remaining Follow-Up requirements.
This issue runs from a custom overlay or an unmerged branch. It does not modify recipes/overlays/h100-gke-cos-inference.yaml on main, and no stock recipe changes. That keeps the demonstration honest about what has and has not been committed to.
Scope
On a live GKE cluster with H100 nodes:
aicr recipeforservice: gke, accelerator: h100, os: cos, intent: inference, withk8s-aibomdeclared in a custom overlay.aicr bundlerenders through every supported deployer; confirm the rendered output is what actually deploys.- Deploy the bundle and confirm the component installs cleanly against the managed control plane, including CRD application (see below).
aicr validatepasses, including thek8s-aibomhealth check — the Deployment rollout terms, theAIBOMControllerConfigReady condition, and the CRD storage-version assertions added in feat(recipes): assert k8s-aibom CRD storage version in the health check #2305.- Label a namespace
aibom.k8saibom.dev/enabled=true, run a real GPU inference workload, and confirm a correct CycloneDX 1.6 ML-BOM is produced for it. Inline vs. truncated behavior at the 256 KiB threshold should be observed rather than assumed. - Measure controller CPU/memory and API-server impact on GKE, and compare against the Kind-measured envelope in ADR-019. A material divergence is a finding, not a footnote.
Depends on
- k8s-aibom: requalify and re-pin against the v1beta1 graduation release #2282 (requalify and re-pin to v1.3.0) — run against the qualified pin, not a stale one.
- The CRD-apply step: the registry now pins chart 1.3.0, whose CRDs differ from 1.2.0. On Helm, Helmfile, and Flux the CRDs must be applied before the bundle upgrade (see the component catalog). Whether that step is needed here depends on whether the cluster is fresh; a fresh install applies
crds/automatically.
Out of scope
- Merging
k8s-aibominto the stockh100-gke-cos-inferenceoverlay. That is the epic's later step and needs the remaining Follow-Up requirements. - Upgrade, rollback, and uninstall evidence — tracked separately; it gates the overlay merge, not this.
- Any AIBOM-as-AICR-evidence integration (ADR-007). Still a separate decision.
Done when
- Every step above is executed on a real GKE cluster and the commands and outputs are recorded on this issue.
- A real ML-BOM produced from a real inference workload is attached or quoted.
- Measured controller and API-server cost is recorded and compared to the ADR-019 envelope.
- Any divergence from the Kind-based expectations is written down, including "none observed" if that is the result.
Activity
mchmarny commentedon Aug 20, 2026
Workload runtime: use Triton or vLLM, not NIM
Recording a decision so it is not rediscovered on the cluster.
Upstream flagged that k8s-aibom's runtime attribution has named patterns for Triton (including the trtllm variants) and vLLM, but not for NIM (
nvcr.io/nim/*). A NIM-served workload would produce weaker runtime attribution than their 1,000-workload qualification showed. That is a pattern gap on their side, not a detection failure, and they have offered a same-day pattern addition if we hit it.This does not constrain the recipe, because
h100-gke-cos-inferenceships no serving runtime. Its resolved components aregpu-operator,nvsentinel,nvidia-dra-driver-gpu,nodewright-operator,nodewright-customizations,kube-prometheus-stack, andnfd. It is the GPU platform. The inference workload in step 5 above is one we choose and deploy, so the choice is entirely ours and costs nothing.Decision: serve the pilot workload with Triton or vLLM. Both are covered patterns, so runtime attribution in the resulting ML-BOM reflects the component working as intended rather than a known pattern gap. There is no
nvcr.io/nimimage anywhere in this recipe's rendered BOM, so nothing has to be avoided — just chosen deliberately.If the workload changes to NIM for any reason, expect weaker attribution and send the image refs upstream rather than filing it as a defect.
Related gaps, tracked here so they are not lost
Neither blocks this issue.
k8s-nim-operator:h100-eks-ubuntu-inference-nim,rtx-pro-6000-eks-ubuntu-inference-nim,ocp-inference-nim, andocp. Anyone pairing k8s-aibom with those hits the same gap regardless of what happens here. Worth asking upstream to add the pattern on its own merits.dynamo-platformappears in 13 overlays, more than three times NIM's. It orchestrates vLLM, TRT-LLM, or SGLang backends, but the served container is selected at runtime rather than chart-pinned, so our BOM cannot tell us whether a Dynamo-served pod matches upstream's existing patterns. Upstream's heads-up covered Triton and vLLM as runtimes and did not mention Dynamo either way.mchmarny commentedon Aug 20, 2026
Live GKE run: core claim proven
Executed on a real GKE cluster,
aicr-uat-day-gh1-0-32392558596(us-central1), Kubernetes v1.35.6-gke.1250000, 6 nodes with 2 ×NVIDIA-H100-80GB-HBM3(nvidia-h100-mega-80gb, 8 GPUs each). AICR inference stack already deployed: gpu-operator, kai-scheduler, nvsentinel, nfd, nvidia-dra-driver, cert-manager, kube-prometheus-stack, dynamo-system.Steps 1-3: generation, bundling, install
k8s-aibomwas added through a custom overlay, not a stock recipe change, exactly as the ADR-019 amendment requires. The stock recipe resolved 14 components; adding a narrow overlay scoped toservice: gke, accelerator: h100, os: cos, intent: inferenceproduced 15.The resolved pin is the requalified set from #2282:
Install pulled chart digest
sha256:4ffa933e272a977e0b60f2eca1c4326176e6196e2ee69e1bf4f72c8b5a511c90, matching the qualified value exactly. Rendered values carried the secure defaults: no sinks, empty discovery map, 256 KiB inline threshold. Deployment reached1/1.Step 4: the CRD storage-version assertion, verified against a real cluster
This is the first real-world exercise of the assertion added in #2305 and flipped in #2309:
Both match what the health check asserts.
AIBOMControllerConfig/defaultreportedReady=TruewithobservedGeneration == generation.Step 5: a real ML-BOM from a real GPU workload
Namespace
aibom-pilotlabeledaibom.k8saibom.dev/enabled=true, running vLLM on an H100 (vllm/vllm-openai:v0.6.3,facebook/opt-125m, 1 GPU, pod1/1 Running).The emitted CycloneDX 1.6 document (inline, 2776 bytes,
sha256:3c249239…):k8s-aibom v1.3.0workload:aibom-pilot/Deployment/vllm-opt125minferredinference.specComponents:
The model was extracted from the container args rather than declared, which is the substantive capability this pilot set out to demonstrate. Runtime attribution resolved to
vllm, consistent with upstream's statement that vLLM is a covered pattern.One behavioral note worth recording: the AIBOM was generated while the pod was still
Pendingon a scheduling failure. Generation reads the workload spec, not running pods, so a workload that never schedules still produces inventory.Finding:
aicr validatecannot schedule its orchestrator Job on tainted GPU nodesaicr validateagainst the full recipe failed:The GPU nodes on this cluster carry
dedicated=gpu-workload:NoSchedule. The orchestrator Job gets an auto-injectednvidia.com/gpu.present=truenode selector, so it targets exactly those tainted nodes and cannot schedule. My vLLM Deployment hit the same wall until given a matching toleration.The suggested remedy does not apply to the failure.
aicr validate --helpstates for both flags:So the error recommends
--node-selectorfor a failure in the one component that flag explicitly cannot influence, and there is no flag that adds a toleration to the orchestrator Job. Tainting GPU nodes is a common pattern, so this is likely to be hit by real users rather than being specific to this UAT cluster.Worth filing separately. It does not block this issue: steps 1-5 were verified directly with
kubectlandhelm, and the health-check assertions were confirmed by hand against live cluster state.Remaining for this issue
mchmarny commentedon Aug 20, 2026
Step 6: measured cost on GKE, and the truncation threshold
Same cluster, same install. All figures from
kubectl topagainst the live controller.Controller cost
Convergence: 51 AIBOMs in 24 seconds from a single
kubectl apply, all reachingReady=True, all attributedruntime=vllm.Deployed requests and limits match ADR-019 exactly:
requests 50m/128Mi,limits 1 CPU/256Mi.Compared to the ADR-019 Kind envelope
ADR-019 recorded quiet CPU mean/p95/max of 17.82/24.88/32.10 mCPU with memory 47.98/48.98/48.98 MiB, and a 1,000-workload run converging in 14s at 230.4 mCPU / 47.5 MiB burst.
No material divergence. Every GKE measurement sits at or below the corresponding Kind figure, and peak memory (48Mi) lands exactly on the Kind quiet memory number. Extrapolating the 51-workload CPU figure linearly to 1,000 workloads gives roughly 200 mCPU against Kind's measured 230.4 mCPU burst, so the envelope holds on a managed control plane. Observed usage stays well inside the configured requests, so the 50m/128Mi request is not tight.
Scale here was 51 workloads rather than 1,000, so this confirms the envelope rather than re-deriving it. Recording the difference rather than implying equivalence.
Inline vs truncated at the 256 KiB threshold
Observed directly by growing a workload's spec until the document crossed the boundary:
Above the threshold the
inlinekey is absent and the status carriestruncated: true, the bytesize, thesha256of the full document, and a reason:Conditions stay honest about what happened rather than reporting failure:
The document is still identified by digest and size when truncated, so an operator can tell that inventory exists and is unretrievable, rather than it silently appearing absent. This is ADR-019's documented behavior for the sinkless secure default, now observed rather than assumed.
Garbage collection
Deleting the 52 synthetic workloads dropped the AIBOM count from 53 to 1 without intervention, so controller-owned resources are reclaimed when their workload goes away.
Status of this issue
Steps 1-6 are complete and evidenced. Remaining known gap:
aicr validatecould not run on this cluster because its orchestrator Job cannot tolerate tainted GPU nodes (detailed in the previous comment). Health-check assertions were verified by hand against live cluster state instead.mchmarny commentedon Aug 20, 2026
Correction: the model is
declared, not inferredUpstream flagged a terminology error in my write-up above, and the BOM I captured proves them right. I wrote:
That is backwards in k8s-aibom's confidence model. Reading the emitted document back rather than relying on the summary row:
aibom.confidenceaibom.evidence.sourcemachine-learning-model facebook/opt-125mdeclaredcontainer_argspec.template.spec.containers[0].args[0 1](--model)application vllm(runtime)inferredimage_pattern^vllm/.*container vllm/vllm-openaiunresolvedimage_referenceA
--modelflag is the workload declaring its own model, the same tier asHF_MODEL_ID, and the component carries the arg locator as evidence.inferredis reserved for what the controller deduces, such as runtime from an image pattern — which is exactly what thevllmapplication component shows.The document-level
aibom.confidence = inferredis the aggregate across components and is what I misread as applying to the model.The capability demonstrated is still the interesting one, just named correctly: the controller resolved a declared model identity out of the workload spec with a precise evidence locator, and separately inferred the runtime from the image. Two different tiers, and the document distinguishes them.
No AICR documentation carries this error; it was confined to the comment above.
fix(recipe): carry the selection through copy, query, and the API schema
mchmarny commentedon Aug 21, 2026
Evidence: end to end on real GKE
GKE
aicr-uat-day-gh1, K8s v1.35.6-gke.1250000, 2xa3-megagpu-8g(16x H100), converted to theh100-gke-cos-inferencecomponent set. Run from unmerged branchfeat/aibom-stock-overlay-gke-inference(#2328). No stock recipe onmainchanged.1. Recipe selection.
--service gke --accelerator h100 --os cos --intent inferenceresolves 15 components includingk8s-aibom.--runtime-inventory disabledrecordsconfiguration.runtimeInventory.mode: disabled, setsinstall: false, and the bundler then omits the component.2. Deployer rendering. All five staged and version-pinned correctly. Deployed and observed on
helm,helmfile,flux.argocdandargocd-helmrendered but not deployed — neither controller was installed here.3. Install. Fresh install of chart 1.3.0 via the stock helm bundle applied
crds/automatically: both CRDsv1beta1storage,storedVersions ["v1beta1"], controller1/1. An upgraded cluster shows["v1alpha1","v1beta1"]; a fresh install shows["v1beta1"]alone.4. Validation.
5. ML-BOM from a real GPU workload. Namespace
aibom-inferencelabeledaibom.k8saibom.dev/enabled=true, vLLM servingQwen/Qwen2.5-0.5B-Instructon one H100.AIBOM/apps-deployment-vllm-qwen:Ready/SyncedTrue,SinkFailed/StaleFalse.applicationcontainermachine-learning-modelInline vs truncated, observed rather than assumed: 2,885 bytes against the 262,144-byte threshold.
6. Cost vs the ADR-019 envelope. Replicas-zero fixtures, ADR-019's methodology.
Not covered:
argocd/argocd-helmdeployment; AIBOM as ADR-007 evidence, out of scope per ADR-019.mchmarny commentedon Aug 21, 2026
API-server impact (completes step 6)
Measured via
apiserver_request_total{resource=~"aiboms|aibomcontrollerconfigs"}, which is directly attributable since nothing else on the cluster touches those resources. Read through Prometheus rather thankubectl get --raw /metrics, because the latter round-robins across apiserver replicas and its per-replica counters make deltas meaningless.Across a 0 to 1,001 workload burst:
The remaining ~88% is the deployment and replicaset controllers reacting to 1,000 new Deployments, not this component.
Two limits worth stating: the controller's informer watches on Deployments, Pods, Jobs and DaemonSets are not separable from kube-controller-manager's, since those resources are shared. And the shipped chart exposes no metrics endpoint and no option to enable one, so per-client attribution is unavailable by design.
docs(aibom): correct the record left stale by the v1.3.0 re-pin
docs(aibom): record the measured GKE envelope in the component values
docs(aibom): correct the record left stale by the v1.3.0 re-pin
feat(recipes): adopt k8s-aibom in the h100-gke-cos-inference recipe
Docs: re-baseline the performance envelope on live-GKE dual-sampled runs
fix(recipes): flag k8s-aibom's resource envelope as pending v1.5.1 re…