diff --git a/apps/vms/unifi/20-datavolume.yaml b/apps/vms/unifi/20-datavolume.yaml index 91b6fd4..601842c 100644 --- a/apps/vms/unifi/20-datavolume.yaml +++ b/apps/vms/unifi/20-datavolume.yaml @@ -21,6 +21,11 @@ # the importer runs in THIS default-deny namespace, unlike the conformance test whose ephemeral # ns has no baseline). No checksum is verified on an http source; pin a dated build for full # reproducibility if drift matters (see README "Open questions"). +# +# NODE PREREQ (Block-mode CDI): the non-root importer opens the raw block device, which k8s +# never chowns for it. The k3s nodes set `nonroot-devices: true` +# (infra/ansible/roles/k3s_server) so containerd hands the device to the pod's UID/GID — +# without it the import dies with `blockdev: cannot open /dev/cdi-block-volume: Permission denied`. apiVersion: cdi.kubevirt.io/v1beta1 kind: DataVolume metadata: diff --git a/infra/ansible/roles/k3s_server/tasks/main.yml b/infra/ansible/roles/k3s_server/tasks/main.yml index ea570d8..d39c83c 100644 --- a/infra/ansible/roles/k3s_server/tasks/main.yml +++ b/infra/ansible/roles/k3s_server/tasks/main.yml @@ -26,10 +26,11 @@ # On an ALREADY-installed cluster the install task above is idempotent (creates: # k3s.yaml) and won't re-apply new --tls-san flags. Persist the SAN list in # /etc/rancher/k3s/config.yaml, which a running k3s reads on the next restart — -# it then regenerates the serving cert when the SAN set changes. Apply the -# restart ROLLING, one node at a time (kube-vip keeps the API reachable; etcd -# quorum holds at 2/3). See playbooks note: run with `serial: 1`. -- name: Write /etc/rancher/k3s/config.yaml with the tls-san list +# it then regenerates the serving cert when the SAN set changes, and regenerates +# its containerd config.toml (so nonroot-devices takes effect). Apply the restart +# ROLLING, one node at a time (kube-vip keeps the API reachable; etcd quorum holds +# at 2/3). See playbooks note: run with `serial: 1`. +- name: Write /etc/rancher/k3s/config.yaml (tls-san + nonroot-devices) ansible.builtin.copy: dest: /etc/rancher/k3s/config.yaml owner: root @@ -42,6 +43,25 @@ {% for san in k3s_tls_sans %} - "{{ san }}" {% endfor %} + + # Let non-root pods own the raw block devices bind-mounted into them — + # sets containerd's `device_ownership_from_security_context = true` + # (k3s >= v1.31.3+k3s1 exposes this as the --nonroot-devices flag). + # + # REQUIRED for KubeVirt/CDI on Block-mode PVCs (ADR-0005): CDI's importer, + # uploader and virt-launcher pods run non-root (UID 107, runAsNonRoot, + # cap-drop ALL) and open the volume's raw block device (/dev/cdi-block-volume, + # then the guest disk). Kubernetes only applies fsGroup ownership to + # *filesystem* volumes — it never chowns a raw block device — and Longhorn's + # CSIDriver (fsGroupPolicy: ReadWriteOnceWithFSType) skips fsGroup on RWX/Block + # volumes anyway, so the device stays root:root and the pod fails with + # `blockdev: cannot open /dev/cdi-block-volume: Permission denied`. This flag + # makes containerd set the in-container device node's owner from the pod's + # securityContext, which is the fix the CDI project documents for this case + # (doc/block_cri_ownership_config.md). Node-scoped and security-neutral: the + # pod stays non-root, only the device node it already mounts follows its UID/GID. + # Preserves the longhorn-vm RWX + Block + migratable disk contract unchanged. + nonroot-devices: true notify: Restart k3s # --- primary: found the cluster --------------------------------------------