First step toward local AI inference on the cluster: make the on-board Intel iGPU schedulable.
What this does
Adds the Intel GPU device plugin as a DaemonSet in kube-system, advertising gpu.intel.com/i915 on all three nodes. Pods can then request the iGPU with a plain resource request — no /dev/dri hostPath, no privilege escalation, no hostNetwork. The plugin passes the render node through the kubelet device-plugin API.
The resource is generic to the iGPU, so this also unlocks QuickSync video transcoding for any future workload, not just GPU compute.
Verified before writing
Hardware: all three nodes are MS-01 / i9-12900H (Alder Lake-P), iGPU at 0000:00:02.0, PCI 8086:46a6, Iris Xe 96 EU. Confirmed via talosctl read /proc/cpuinfo and dmesg | grep i915.
Driver: talosctl get extensions shows i915 20260810-v1.13.9 already installed on every node. No machineconfig or schematic change needed.
Device nodes: /dev/dri/{card0,renderD128} present on all three.
Placement: the API server's PodSecurity config is enforce: baseline with kube-system as the only namespace exemption. Baseline forbids hostPath volumes, which this plugin needs — hence kube-system. Confirmed with kubectl apply --dry-run=server, which the API server accepted.
Network policy: none needed. default-deny-ingress is scoped to the apps namespace, not cluster-wide.
Flag name: -shared-dev-num read from cmd/gpu_plugin/gpu_plugin.go at the v0.36.0 tag, not from recall.
Why vendored instead of the Helm chart
The intel-device-plugins-gpu chart does not ship a DaemonSet — helm template renders a GpuDevicePlugin custom resource (deviceplugin.intel.com/v1). That only functions with the Intel Device Plugin Operator, which brings CRDs, a controller and a cert-manager webhook. With one device type on three identical nodes the operator manages nothing this DaemonSet does not, so it would be pure failure surface.
The manifest is copied verbatim from deployments/gpu_plugin/base/intel-gpu-plugin.yaml at tag v0.36.0 (the release targeting Kubernetes 1.36, which matches this cluster), with two deliberate changes: namespace: kube-system and the -shared-dev-num=4 arg. Upgrade path is documented in the file header.
Notes
NFD is not deployed, so upstream's nfd_labeled_nodes overlay and its intel.feature.node.kubernetes.io/gpu nodeSelector are omitted. Every node has the same iGPU so there is nothing to discover, and this sidesteps a known historical Talos gap where a kernel-built-in i915 broke NFD's module-detection rule.
Upstream's seLinuxOptions.type: container_device_plugin_t is kept as shipped. SELinux is enabled on the nodes (selinux=1) but runs permissive (/sys/fs/selinux/enforce = 0), so it is advisory today.
-shared-dev-num=4 is a slot count, not fractional accounting — nothing partitions the device, so sharers contend for it.
Verification after merge
kubectl -n kube-system rollout status ds/intel-gpu-plugin
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.gpu\.intel\.com/i915}{"\n"}{end}'
Expect 4 on each of k8s-1/2/3.
Next
With the resource schedulable, the follow-up is a llama-bench run on k8s-1 comparing the llama.cpp Vulkan and SYCL builds against CPU on the same 8B Q4_K_M model. No published benchmark exists for Alder Lake-P iGPU inference, and no recent SYCL-vs-Vulkan comparison exists for any laptop iGPU, so that measurement picks the backend before any inference manifests get written.
First step toward local AI inference on the cluster: make the on-board Intel iGPU schedulable.
## What this does
Adds the Intel GPU device plugin as a DaemonSet in `kube-system`, advertising `gpu.intel.com/i915` on all three nodes. Pods can then request the iGPU with a plain resource request — no `/dev/dri` hostPath, no privilege escalation, no `hostNetwork`. The plugin passes the render node through the kubelet device-plugin API.
The resource is generic to the iGPU, so this also unlocks QuickSync video transcoding for any future workload, not just GPU compute.
## Verified before writing
- **Hardware**: all three nodes are MS-01 / i9-12900H (Alder Lake-P), iGPU at `0000:00:02.0`, PCI `8086:46a6`, Iris Xe 96 EU. Confirmed via `talosctl read /proc/cpuinfo` and `dmesg | grep i915`.
- **Driver**: `talosctl get extensions` shows `i915 20260810-v1.13.9` already installed on every node. **No machineconfig or schematic change needed.**
- **Device nodes**: `/dev/dri/{card0,renderD128}` present on all three.
- **Placement**: the API server's PodSecurity config is `enforce: baseline` with `kube-system` as the only namespace exemption. Baseline forbids hostPath volumes, which this plugin needs — hence `kube-system`. Confirmed with `kubectl apply --dry-run=server`, which the API server accepted.
- **Network policy**: none needed. `default-deny-ingress` is scoped to the `apps` namespace, not cluster-wide.
- **Flag name**: `-shared-dev-num` read from `cmd/gpu_plugin/gpu_plugin.go` at the v0.36.0 tag, not from recall.
## Why vendored instead of the Helm chart
The `intel-device-plugins-gpu` chart does **not** ship a DaemonSet — `helm template` renders a `GpuDevicePlugin` custom resource (`deviceplugin.intel.com/v1`). That only functions with the Intel Device Plugin Operator, which brings CRDs, a controller and a cert-manager webhook. With one device type on three identical nodes the operator manages nothing this DaemonSet does not, so it would be pure failure surface.
The manifest is copied verbatim from `deployments/gpu_plugin/base/intel-gpu-plugin.yaml` at tag `v0.36.0` (the release targeting Kubernetes 1.36, which matches this cluster), with two deliberate changes: `namespace: kube-system` and the `-shared-dev-num=4` arg. Upgrade path is documented in the file header.
## Notes
- **NFD is not deployed**, so upstream's `nfd_labeled_nodes` overlay and its `intel.feature.node.kubernetes.io/gpu` nodeSelector are omitted. Every node has the same iGPU so there is nothing to discover, and this sidesteps a known historical Talos gap where a kernel-built-in `i915` broke NFD's module-detection rule.
- Upstream's `seLinuxOptions.type: container_device_plugin_t` is kept as shipped. SELinux is enabled on the nodes (`selinux=1`) but runs **permissive** (`/sys/fs/selinux/enforce` = 0), so it is advisory today.
- `-shared-dev-num=4` is a slot count, not fractional accounting — nothing partitions the device, so sharers contend for it.
## Verification after merge
```
kubectl -n kube-system rollout status ds/intel-gpu-plugin
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.gpu\.intel\.com/i915}{"\n"}{end}'
```
Expect `4` on each of k8s-1/2/3.
## Next
With the resource schedulable, the follow-up is a `llama-bench` run on k8s-1 comparing the llama.cpp Vulkan and SYCL builds against CPU on the same 8B Q4_K_M model. No published benchmark exists for Alder Lake-P iGPU inference, and no recent SYCL-vs-Vulkan comparison exists for any laptop iGPU, so that measurement picks the backend before any inference manifests get written.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the Intel GPU device plugin as a DaemonSet in kube-system, making the
on-board Iris Xe of each MS-01 node schedulable as `gpu.intel.com/i915`.
Pods can then request the iGPU without a /dev/dri hostPath or privilege
escalation — the plugin passes the render node through the kubelet
device-plugin API. The resource covers QuickSync transcoding as well as GPU
compute.
Vendored from upstream v0.36.0 rather than installed from the
`intel-device-plugins-gpu` Helm chart: that chart renders a `GpuDevicePlugin`
custom resource requiring the Intel Device Plugin Operator, its CRDs and a
cert-manager webhook. One device type on three identical nodes gives the
operator nothing to manage that the DaemonSet does not.
Runs in kube-system because the plugin mounts hostPaths; the cluster enforces
PodSecurity `baseline`, which forbids those, and kube-system is the only
namespace in the API server's exemption list. NFD is not deployed, so
upstream's nfd_labeled_nodes overlay and its nodeSelector are omitted.
`-shared-dev-num=4` lets several containers share the single iGPU.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖Automated review — model CLI default (complexity: complex), commit c868b8cd9d794018a04458344a9bdd5cfdd9f5a8.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Adds a vendored intel-gpu-plugin DaemonSet to kube-system that advertises gpu.intel.com/i915 via the kubelet device-plugin API, plus the kustomize wiring to include it. The manifest is well-documented, the placement/PodSecurity/network-policy reasoning in the header comments checks out against the actual cluster config, and the security context is hardened beyond what baseline requires. I found no blocking issues.
🟡 Suggestions
No Flux image-automation for the new image (kubernetes/infrastructure/intel-gpu-plugin/daemonset.yaml:66, image: intel/intel-gpu-plugin:0.36.0). Every other third-party image in this repo gets an ImageRepository/ImagePolicy pair under kubernetes/infrastructure/image-automation/ plus a {"$imagepolicy": ...} marker comment (see image-automation/audiobookshelf.yaml, cnpg-barman-plugin.yaml, etc.), so Flux and the update-audit tooling track drift automatically. This PR adds neither, so the tag will only ever move by hand. That may well be deliberate — the file header already says upgrading means re-syncing the whole manifest against the new tag, not just bumping a version string — but it's worth confirming that's the intended long-term maintenance path rather than an oversight, since it's a deviation from an otherwise repo-wide convention.
No NoExecute tolerations (daemonset.yaml:56-124, template.spec). Other node-level DaemonSets in this repo (e.g. kubernetes/infrastructure/multus/multus-daemonset.yaml:159-164) carry blanket operator: Exists tolerations for NoSchedule/NoExecute so they aren't evicted on transient node conditions (not-ready, unreachable). This plugin doesn't need a NoSchedule toleration — allowSchedulingOnControlPlanes: true in kubernetes/talos/patches/cluster.yaml:92 means the nodes carry no scheduling taint — but without a NoExecute toleration, a brief node hiccup could evict the pod and drop gpu.intel.com/i915 allocatable to 0 on that node until the DaemonSet controller reschedules it. Low impact on a 3-node homelab cluster, but worth a look given the resource is meant to back inference workloads.
Tests
This is a static Kubernetes manifest; the repo's ci-manifests-test.ymlkustomize-build step (which runs kubectl kustomize over every kustomization in the tree, gated on kubernetes/** changes) is the applicable check here and covers both new files. I don't have kubectl/kustomize available in this environment to re-run it, but manual inspection of daemonset.yaml and kustomization.yaml shows well-formed YAML, matching selector.matchLabels/template.metadata.labels, and correct resources: wiring in both the new kustomization.yaml and the parent kubernetes/infrastructure/kustomization.yaml:13. The PR's own "Verification after merge" section (rollout status + allocatable check) is a reasonable functional smoke test given this can't be verified against real hardware in CI.
One thing I could not verify from this checkout: whether daemonset.yaml is truly byte-for-byte faithful to upstream deployments/gpu_plugin/base/intel-gpu-plugin.yaml at tag v0.36.0 aside from the two documented changes (namespace, -shared-dev-num=4) — I have no network access in this environment to diff against the upstream repo. Everything internally consistent (volume mounts, security context, flag usage) reads as plausible for a device-plugin manifest, but if this hasn't already been diffed against upstream byte-for-byte, that's the one remaining check I'd recommend before merge.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `c868b8cd9d794018a04458344a9bdd5cfdd9f5a8`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Adds a vendored `intel-gpu-plugin` DaemonSet to `kube-system` that advertises `gpu.intel.com/i915` via the kubelet device-plugin API, plus the kustomize wiring to include it. The manifest is well-documented, the placement/PodSecurity/network-policy reasoning in the header comments checks out against the actual cluster config, and the security context is hardened beyond what `baseline` requires. I found no blocking issues.
## 🟡 Suggestions
- **No Flux image-automation for the new image** (`kubernetes/infrastructure/intel-gpu-plugin/daemonset.yaml:66`, `image: intel/intel-gpu-plugin:0.36.0`). Every other third-party image in this repo gets an `ImageRepository`/`ImagePolicy` pair under `kubernetes/infrastructure/image-automation/` plus a `{"$imagepolicy": ...}` marker comment (see `image-automation/audiobookshelf.yaml`, `cnpg-barman-plugin.yaml`, etc.), so Flux and the update-audit tooling track drift automatically. This PR adds neither, so the tag will only ever move by hand. That may well be deliberate — the file header already says upgrading means re-syncing the whole manifest against the new tag, not just bumping a version string — but it's worth confirming that's the intended long-term maintenance path rather than an oversight, since it's a deviation from an otherwise repo-wide convention.
- **No `NoExecute` tolerations** (`daemonset.yaml:56-124`, `template.spec`). Other node-level DaemonSets in this repo (e.g. `kubernetes/infrastructure/multus/multus-daemonset.yaml:159-164`) carry blanket `operator: Exists` tolerations for `NoSchedule`/`NoExecute` so they aren't evicted on transient node conditions (`not-ready`, `unreachable`). This plugin doesn't need a `NoSchedule` toleration — `allowSchedulingOnControlPlanes: true` in `kubernetes/talos/patches/cluster.yaml:92` means the nodes carry no scheduling taint — but without a `NoExecute` toleration, a brief node hiccup could evict the pod and drop `gpu.intel.com/i915` allocatable to 0 on that node until the DaemonSet controller reschedules it. Low impact on a 3-node homelab cluster, but worth a look given the resource is meant to back inference workloads.
## Tests
This is a static Kubernetes manifest; the repo's `ci-manifests-test.yml` `kustomize-build` step (which runs `kubectl kustomize` over every kustomization in the tree, gated on `kubernetes/**` changes) is the applicable check here and covers both new files. I don't have `kubectl`/`kustomize` available in this environment to re-run it, but manual inspection of `daemonset.yaml` and `kustomization.yaml` shows well-formed YAML, matching `selector.matchLabels`/`template.metadata.labels`, and correct `resources:` wiring in both the new `kustomization.yaml` and the parent `kubernetes/infrastructure/kustomization.yaml:13`. The PR's own "Verification after merge" section (rollout status + allocatable check) is a reasonable functional smoke test given this can't be verified against real hardware in CI.
One thing I could not verify from this checkout: whether `daemonset.yaml` is truly byte-for-byte faithful to upstream `deployments/gpu_plugin/base/intel-gpu-plugin.yaml` at tag `v0.36.0` aside from the two documented changes (namespace, `-shared-dev-num=4`) — I have no network access in this environment to diff against the upstream repo. Everything internally consistent (volume mounts, security context, flag usage) reads as plausible for a device-plugin manifest, but if this hasn't already been diffed against upstream byte-for-byte, that's the one remaining check I'd recommend before merge.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=c868b8cd9d794018a04458344a9bdd5cfdd9f5a8 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First step toward local AI inference on the cluster: make the on-board Intel iGPU schedulable.
What this does
Adds the Intel GPU device plugin as a DaemonSet in
kube-system, advertisinggpu.intel.com/i915on all three nodes. Pods can then request the iGPU with a plain resource request — no/dev/drihostPath, no privilege escalation, nohostNetwork. The plugin passes the render node through the kubelet device-plugin API.The resource is generic to the iGPU, so this also unlocks QuickSync video transcoding for any future workload, not just GPU compute.
Verified before writing
0000:00:02.0, PCI8086:46a6, Iris Xe 96 EU. Confirmed viatalosctl read /proc/cpuinfoanddmesg | grep i915.talosctl get extensionsshowsi915 20260810-v1.13.9already installed on every node. No machineconfig or schematic change needed./dev/dri/{card0,renderD128}present on all three.enforce: baselinewithkube-systemas the only namespace exemption. Baseline forbids hostPath volumes, which this plugin needs — hencekube-system. Confirmed withkubectl apply --dry-run=server, which the API server accepted.default-deny-ingressis scoped to theappsnamespace, not cluster-wide.-shared-dev-numread fromcmd/gpu_plugin/gpu_plugin.goat the v0.36.0 tag, not from recall.Why vendored instead of the Helm chart
The
intel-device-plugins-gpuchart does not ship a DaemonSet —helm templaterenders aGpuDevicePlugincustom resource (deviceplugin.intel.com/v1). That only functions with the Intel Device Plugin Operator, which brings CRDs, a controller and a cert-manager webhook. With one device type on three identical nodes the operator manages nothing this DaemonSet does not, so it would be pure failure surface.The manifest is copied verbatim from
deployments/gpu_plugin/base/intel-gpu-plugin.yamlat tagv0.36.0(the release targeting Kubernetes 1.36, which matches this cluster), with two deliberate changes:namespace: kube-systemand the-shared-dev-num=4arg. Upgrade path is documented in the file header.Notes
nfd_labeled_nodesoverlay and itsintel.feature.node.kubernetes.io/gpunodeSelector are omitted. Every node has the same iGPU so there is nothing to discover, and this sidesteps a known historical Talos gap where a kernel-built-ini915broke NFD's module-detection rule.seLinuxOptions.type: container_device_plugin_tis kept as shipped. SELinux is enabled on the nodes (selinux=1) but runs permissive (/sys/fs/selinux/enforce= 0), so it is advisory today.-shared-dev-num=4is a slot count, not fractional accounting — nothing partitions the device, so sharers contend for it.Verification after merge
Expect
4on each of k8s-1/2/3.Next
With the resource schedulable, the follow-up is a
llama-benchrun on k8s-1 comparing the llama.cpp Vulkan and SYCL builds against CPU on the same 8B Q4_K_M model. No published benchmark exists for Alder Lake-P iGPU inference, and no recent SYCL-vs-Vulkan comparison exists for any laptop iGPU, so that measurement picks the backend before any inference manifests get written.🤖 Generated with Claude Code
🤖 Automated review — model
CLI default(complexity: complex), commitc868b8cd9d794018a04458344a9bdd5cfdd9f5a8.Summary
Adds a vendored
intel-gpu-pluginDaemonSet tokube-systemthat advertisesgpu.intel.com/i915via the kubelet device-plugin API, plus the kustomize wiring to include it. The manifest is well-documented, the placement/PodSecurity/network-policy reasoning in the header comments checks out against the actual cluster config, and the security context is hardened beyond whatbaselinerequires. I found no blocking issues.🟡 Suggestions
kubernetes/infrastructure/intel-gpu-plugin/daemonset.yaml:66,image: intel/intel-gpu-plugin:0.36.0). Every other third-party image in this repo gets anImageRepository/ImagePolicypair underkubernetes/infrastructure/image-automation/plus a{"$imagepolicy": ...}marker comment (seeimage-automation/audiobookshelf.yaml,cnpg-barman-plugin.yaml, etc.), so Flux and the update-audit tooling track drift automatically. This PR adds neither, so the tag will only ever move by hand. That may well be deliberate — the file header already says upgrading means re-syncing the whole manifest against the new tag, not just bumping a version string — but it's worth confirming that's the intended long-term maintenance path rather than an oversight, since it's a deviation from an otherwise repo-wide convention.NoExecutetolerations (daemonset.yaml:56-124,template.spec). Other node-level DaemonSets in this repo (e.g.kubernetes/infrastructure/multus/multus-daemonset.yaml:159-164) carry blanketoperator: Existstolerations forNoSchedule/NoExecuteso they aren't evicted on transient node conditions (not-ready,unreachable). This plugin doesn't need aNoScheduletoleration —allowSchedulingOnControlPlanes: trueinkubernetes/talos/patches/cluster.yaml:92means the nodes carry no scheduling taint — but without aNoExecutetoleration, a brief node hiccup could evict the pod and dropgpu.intel.com/i915allocatable to 0 on that node until the DaemonSet controller reschedules it. Low impact on a 3-node homelab cluster, but worth a look given the resource is meant to back inference workloads.Tests
This is a static Kubernetes manifest; the repo's
ci-manifests-test.ymlkustomize-buildstep (which runskubectl kustomizeover every kustomization in the tree, gated onkubernetes/**changes) is the applicable check here and covers both new files. I don't havekubectl/kustomizeavailable in this environment to re-run it, but manual inspection ofdaemonset.yamlandkustomization.yamlshows well-formed YAML, matchingselector.matchLabels/template.metadata.labels, and correctresources:wiring in both the newkustomization.yamland the parentkubernetes/infrastructure/kustomization.yaml:13. The PR's own "Verification after merge" section (rollout status + allocatable check) is a reasonable functional smoke test given this can't be verified against real hardware in CI.One thing I could not verify from this checkout: whether
daemonset.yamlis truly byte-for-byte faithful to upstreamdeployments/gpu_plugin/base/intel-gpu-plugin.yamlat tagv0.36.0aside from the two documented changes (namespace,-shared-dev-num=4) — I have no network access in this environment to diff against the upstream repo. Everything internally consistent (volume mounts, security context, flag usage) reads as plausible for a device-plugin manifest, but if this hasn't already been diffed against upstream byte-for-byte, that's the one remaining check I'd recommend before merge.Recommendation
Looks reasonable — no blocking issues; comments above are optional.