From 28f800b95ed475519275164d7e22f145e4c3cd32 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 1 Jun 2026 19:20:36 +0200 Subject: [PATCH] fix(prepare): enable containerd device_ownership_from_security_context for CDI block imports KubeVirt's CDI importer writes VM disk images into raw block volumes from a non-root pod. containerd only chowns the block device to the pod's SecurityContext when device_ownership_from_security_context is enabled on the CRI plugin, and k3s ships it disabled. Without it the importer fails with 'cannot open /dev/cdi-block-volume: Permission denied', the DataVolume hangs in ImportInProgress, and VMs referencing the disk stay Pending. Add a k3s containerd drop-in (config-v3.toml.d/10-cozystack-cri.toml) to all three prepare playbooks, gated behind cozystack_enable_kubevirt and overridable via cozystack_k3s_containerd_dropin_dir. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- CHANGELOG.rst | 20 +++++++++++++ README.md | 12 ++++++++ examples/rhel/prepare-rhel.yml | 48 ++++++++++++++++++++++++++++++ examples/suse/prepare-suse.yml | 48 ++++++++++++++++++++++++++++++ examples/ubuntu/prepare-ubuntu.yml | 48 ++++++++++++++++++++++++++++++ galaxy.yml | 2 +- 6 files changed, 177 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4aa8599..ff21ac8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,26 @@ cozystack.installer Release Notes ================================= +v1.4.3 +====== + +Bugfixes +-------- + +- Prepare playbooks now enable + ``device_ownership_from_security_context`` on the containerd CRI + plugin (k3s drop-in + ``config-v3.toml.d/10-cozystack-cri.toml``). KubeVirt's CDI importer + writes disk images into raw block volumes as a non-root pod, which + requires containerd to chown the block device to the pod's + SecurityContext; k3s disables this by default. Without it the + importer failed with ``blockdev: cannot open /dev/cdi-block-volume: + Permission denied``, the ``DataVolume`` hung in ``ImportInProgress``, + and VMs referencing the disk stayed ``Pending``. Gated behind + ``cozystack_enable_kubevirt``; drop-in directory overridable via + ``cozystack_k3s_containerd_dropin_dir`` for containerd 1.x clusters. + + v1.4.0 ====== diff --git a/README.md b/README.md index 5bf6a19..c260d01 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,18 @@ tun kvm_intel # or kvm_amd depending on the CPU ``` +#### Enabled by default: containerd device ownership for CDI block imports + +When KubeVirt is enabled, the prepare playbook drops a containerd CRI config that sets `device_ownership_from_security_context = true`. KubeVirt's CDI (Containerized Data Importer) writes VM disk images into raw **block** volumes from a non-root importer pod; containerd only chowns the block device to the pod's `SecurityContext` UID/GID when this option is on, and k3s ships it disabled. Without it the importer fails with `blockdev: cannot open /dev/cdi-block-volume: Permission denied`, the `DataVolume` is stuck in `ImportInProgress`, and every VM that references the disk stays `Pending` — one of the silent "VMs stuck in Pending" failure modes called out above. + +Written as a drop-in that containerd merges on top of k3s's generated `config.toml`: + +```text +/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d/10-cozystack-cri.toml +``` + +`config-v3.toml.d` and the `io.containerd.cri.v1.runtime` plugin table are the containerd 2.x (config version 3) paths shipped by current k3s. On a containerd 1.x cluster override `cozystack_k3s_containerd_dropin_dir` (and adjust the plugin table to `io.containerd.grpc.v1.cri`). The drop-in is read at first k3s start in the full pipeline; on a re-run against a running cluster a handler restarts k3s so the change takes effect. + #### Known limitations ZFS support depends on the OS ecosystem and kernel flavor. The prepare diff --git a/examples/rhel/prepare-rhel.yml b/examples/rhel/prepare-rhel.yml index f37b01a..40b83aa 100644 --- a/examples/rhel/prepare-rhel.yml +++ b/examples/rhel/prepare-rhel.yml @@ -122,6 +122,19 @@ state: restarted failed_when: false # tolerated: same reason as the enable task below + - name: Restart k3s to apply containerd config + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: + - k3s + - k3s-agent + # Only the unit matching this node's role exists; the other is + # absent, and on the full-pipeline run prepare executes before + # k3s is installed (the drop-in is then read at first k3s start). + # failed_when: false tolerates both — a missing unit is not an error. + failed_when: false + tasks: - name: Create k3s_cluster group for k3s.orchestration ansible.builtin.group_by: @@ -188,6 +201,41 @@ | map(attribute='item') | list }} + # CDI (Containerized Data Importer) streams VM disk images into raw + # block volumes from a NON-root importer pod. containerd only chowns + # the block device to the pod's SecurityContext UID/GID when + # device_ownership_from_security_context is enabled on the CRI + # plugin, and k3s ships it disabled. Without it the importer dies + # with "blockdev: cannot open /dev/cdi-block-volume: Permission + # denied", the DataVolume hangs in ImportInProgress, and every VM + # that references the disk stays Pending. + # + # The drop-in is merged by containerd on top of k3s's generated + # config.toml via the config-v3.toml.d import glob — read at first + # k3s start (full pipeline) or applied by the handler on re-runs + # against a running cluster. config-v3.toml.d and + # io.containerd.cri.v1.runtime are the containerd 2.x (config + # version 3) paths shipped by current k3s; override + # cozystack_k3s_containerd_dropin_dir for a containerd 1.x cluster. + - name: Ensure k3s containerd config drop-in directory exists + ansible.builtin.file: + path: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}" + state: directory + mode: "0755" + when: cozystack_enable_kubevirt | default(true) | bool + + - name: Enable device_ownership_from_security_context for CDI block imports + ansible.builtin.copy: + dest: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}/10-cozystack-cri.toml" + mode: "0644" + content: | + version = 3 + + [plugins.'io.containerd.cri.v1.runtime'] + device_ownership_from_security_context = true + when: cozystack_enable_kubevirt | default(true) | bool + notify: Restart k3s to apply containerd config + - name: Ensure multipath drop-in directory exists ansible.builtin.file: path: /etc/multipath/conf.d diff --git a/examples/suse/prepare-suse.yml b/examples/suse/prepare-suse.yml index a6e3b91..ad5f6ec 100644 --- a/examples/suse/prepare-suse.yml +++ b/examples/suse/prepare-suse.yml @@ -117,6 +117,19 @@ state: restarted failed_when: false # tolerated: same reason as the enable task below + - name: Restart k3s to apply containerd config + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: + - k3s + - k3s-agent + # Only the unit matching this node's role exists; the other is + # absent, and on the full-pipeline run prepare executes before + # k3s is installed (the drop-in is then read at first k3s start). + # failed_when: false tolerates both — a missing unit is not an error. + failed_when: false + tasks: - name: Create k3s_cluster group for k3s.orchestration ansible.builtin.group_by: @@ -183,6 +196,41 @@ | map(attribute='item') | list }} + # CDI (Containerized Data Importer) streams VM disk images into raw + # block volumes from a NON-root importer pod. containerd only chowns + # the block device to the pod's SecurityContext UID/GID when + # device_ownership_from_security_context is enabled on the CRI + # plugin, and k3s ships it disabled. Without it the importer dies + # with "blockdev: cannot open /dev/cdi-block-volume: Permission + # denied", the DataVolume hangs in ImportInProgress, and every VM + # that references the disk stays Pending. + # + # The drop-in is merged by containerd on top of k3s's generated + # config.toml via the config-v3.toml.d import glob — read at first + # k3s start (full pipeline) or applied by the handler on re-runs + # against a running cluster. config-v3.toml.d and + # io.containerd.cri.v1.runtime are the containerd 2.x (config + # version 3) paths shipped by current k3s; override + # cozystack_k3s_containerd_dropin_dir for a containerd 1.x cluster. + - name: Ensure k3s containerd config drop-in directory exists + ansible.builtin.file: + path: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}" + state: directory + mode: "0755" + when: cozystack_enable_kubevirt | default(true) | bool + + - name: Enable device_ownership_from_security_context for CDI block imports + ansible.builtin.copy: + dest: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}/10-cozystack-cri.toml" + mode: "0644" + content: | + version = 3 + + [plugins.'io.containerd.cri.v1.runtime'] + device_ownership_from_security_context = true + when: cozystack_enable_kubevirt | default(true) | bool + notify: Restart k3s to apply containerd config + - name: Ensure multipath drop-in directory exists ansible.builtin.file: path: /etc/multipath/conf.d diff --git a/examples/ubuntu/prepare-ubuntu.yml b/examples/ubuntu/prepare-ubuntu.yml index dea9fbc..df5b681 100644 --- a/examples/ubuntu/prepare-ubuntu.yml +++ b/examples/ubuntu/prepare-ubuntu.yml @@ -138,6 +138,19 @@ # IS consulted downstream.) failed_when: false + - name: Restart k3s to apply containerd config + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: + - k3s + - k3s-agent + # Only the unit matching this node's role exists; the other is + # absent, and on the full-pipeline run prepare executes before + # k3s is installed (the drop-in is then read at first k3s start). + # failed_when: false tolerates both — a missing unit is not an error. + failed_when: false + tasks: - name: Create k3s_cluster group for k3s.orchestration ansible.builtin.group_by: @@ -229,6 +242,41 @@ | map(attribute='item') | list }} + # CDI (Containerized Data Importer) streams VM disk images into raw + # block volumes from a NON-root importer pod. containerd only chowns + # the block device to the pod's SecurityContext UID/GID when + # device_ownership_from_security_context is enabled on the CRI + # plugin, and k3s ships it disabled. Without it the importer dies + # with "blockdev: cannot open /dev/cdi-block-volume: Permission + # denied", the DataVolume hangs in ImportInProgress, and every VM + # that references the disk stays Pending. + # + # The drop-in is merged by containerd on top of k3s's generated + # config.toml via the config-v3.toml.d import glob — read at first + # k3s start (full pipeline) or applied by the handler on re-runs + # against a running cluster. config-v3.toml.d and + # io.containerd.cri.v1.runtime are the containerd 2.x (config + # version 3) paths shipped by current k3s; override + # cozystack_k3s_containerd_dropin_dir for a containerd 1.x cluster. + - name: Ensure k3s containerd config drop-in directory exists + ansible.builtin.file: + path: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}" + state: directory + mode: "0755" + when: cozystack_enable_kubevirt | default(true) | bool + + - name: Enable device_ownership_from_security_context for CDI block imports + ansible.builtin.copy: + dest: "{{ cozystack_k3s_containerd_dropin_dir | default('/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d') }}/10-cozystack-cri.toml" + mode: "0644" + content: | + version = 3 + + [plugins.'io.containerd.cri.v1.runtime'] + device_ownership_from_security_context = true + when: cozystack_enable_kubevirt | default(true) | bool + notify: Restart k3s to apply containerd config + - name: Ensure multipath drop-in directory exists ansible.builtin.file: path: /etc/multipath/conf.d diff --git a/galaxy.yml b/galaxy.yml index 52a6a5a..4d13523 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: cozystack name: installer -version: 1.4.2 +version: 1.4.3 readme: README.md authors: - Aleksei Sviridkin