diff --git a/internal/cri/config/config.go b/internal/cri/config/config.go index a5c6b1d5b4..aa0b0db360 100644 --- a/internal/cri/config/config.go +++ b/internal/cri/config/config.go @@ -32,6 +32,7 @@ import ( runcoptions "github.com/containerd/containerd/api/types/runc/options" runtimeoptions "github.com/containerd/containerd/api/types/runtimeoptions/v1" "github.com/containerd/containerd/v2/internal/cri/annotations" + "github.com/containerd/containerd/v2/internal/cri/opts" "github.com/containerd/containerd/v2/pkg/deprecation" "github.com/containerd/containerd/v2/plugins" ) @@ -102,6 +103,8 @@ type Runtime struct { // to the runtime spec when the container when PrivilegedWithoutHostDevices is already enabled. Requires // PrivilegedWithoutHostDevices to be enabled. Defaults to false. PrivilegedWithoutHostDevicesAllDevicesAllowed bool `toml:"privileged_without_host_devices_all_devices_allowed" json:"privileged_without_host_devices_all_devices_allowed"` + // CgroupWritable enables writable cgroups in non-privileged containers + CgroupWritable bool `toml:"cgroup_writable" json:"cgroupWritable"` // BaseRuntimeSpec is a json file with OCI spec to use as base spec that all container's will be created from. BaseRuntimeSpec string `toml:"base_runtime_spec" json:"baseRuntimeSpec"` // NetworkPluginConfDir is a directory containing the CNI network information for the runtime class. @@ -527,6 +530,10 @@ func ValidateRuntimeConfig(ctx context.Context, c *RuntimeConfig) ([]deprecation } for k, r := range c.ContainerdConfig.Runtimes { + if r.CgroupWritable && !opts.IsCgroup2UnifiedMode() { + return warnings, fmt.Errorf("runtime %s: `cgroup_writable` is only supported on cgroup v2", k) + } + if !r.PrivilegedWithoutHostDevices && r.PrivilegedWithoutHostDevicesAllDevicesAllowed { return warnings, errors.New("`privileged_without_host_devices_all_devices_allowed` requires `privileged_without_host_devices` to be enabled") } diff --git a/internal/cri/config/config_test.go b/internal/cri/config/config_test.go index f7a5f30dfe..efb7a2c125 100644 --- a/internal/cri/config/config_test.go +++ b/internal/cri/config/config_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" runtime "k8s.io/cri-api/pkg/apis/runtime/v1" + "github.com/containerd/containerd/v2/internal/cri/opts" "github.com/containerd/containerd/v2/pkg/deprecation" ) @@ -189,6 +190,40 @@ func TestValidateConfig(t *testing.T) { }, warnings: []deprecation.Warning{deprecation.CRIRegistryConfigs}, }, + "cgroup_writable enabled": { + runtimeConfig: &RuntimeConfig{ + ContainerdConfig: ContainerdConfig{ + DefaultRuntimeName: RuntimeDefault, + Runtimes: map[string]Runtime{ + RuntimeDefault: { + CgroupWritable: true, + }, + }, + }, + }, + runtimeExpectedErr: func() string { + if !opts.IsCgroup2UnifiedMode() { + return "`cgroup_writable` is only supported on cgroup v2" + } + return "" + }(), + runtimeExpected: func() *RuntimeConfig { + if !opts.IsCgroup2UnifiedMode() { + return nil + } + return &RuntimeConfig{ + ContainerdConfig: ContainerdConfig{ + DefaultRuntimeName: RuntimeDefault, + Runtimes: map[string]Runtime{ + RuntimeDefault: { + CgroupWritable: true, + Sandboxer: string(ModePodSandbox), + }, + }, + }, + } + }(), + }, "privileged_without_host_devices_all_devices_allowed without privileged_without_host_devices": { runtimeConfig: &RuntimeConfig{ ContainerdConfig: ContainerdConfig{ diff --git a/internal/cri/opts/spec_linux_opts.go b/internal/cri/opts/spec_linux_opts.go index 295cda01c5..348e699496 100644 --- a/internal/cri/opts/spec_linux_opts.go +++ b/internal/cri/opts/spec_linux_opts.go @@ -39,8 +39,7 @@ import ( "github.com/containerd/log" ) -// WithMounts sorts and adds runtime and CRI mounts to the spec -func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string, handler *runtime.RuntimeHandler) oci.SpecOpts { +func withMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string, handler *runtime.RuntimeHandler, cgroupWritable bool) oci.SpecOpts { return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) (err error) { // mergeMounts merge CRI mounts with extra mounts. If a mount destination // is mounted by both a CRI mount and an extra mount, the CRI mount will @@ -67,12 +66,15 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru // shadow other mounts. sort.Stable(orderedMounts(mounts)) - // Mount cgroup into the container as readonly, which inherits docker's behavior. + mode := "ro" + if cgroupWritable { + mode = "rw" + } s.Mounts = append(s.Mounts, runtimespec.Mount{ Source: "cgroup", Destination: "/sys/fs/cgroup", Type: "cgroup", - Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"}, + Options: []string{"nosuid", "noexec", "nodev", "relatime", mode}, }) // Copy all mounts from default mounts, except for @@ -214,6 +216,17 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru } } +// WithMounts sorts and adds runtime and CRI mounts to the spec +func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string, handler *runtime.RuntimeHandler) oci.SpecOpts { + return withMounts(osi, config, extra, mountLabel, handler, false) + +} + +// WithMountsCgroupWritable sorts and adds runtime and CRI mounts to the spec if cgroup_writable is enabled. +func WithMountsCgroupWritable(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string, handler *runtime.RuntimeHandler) oci.SpecOpts { + return withMounts(osi, config, extra, mountLabel, handler, true) +} + // Ensure mount point on which path is mounted, is shared. func ensureShared(path string, lookupMount func(string) (mount.Info, error)) error { mountInfo, err := lookupMount(path) diff --git a/internal/cri/opts/spec_nonlinux.go b/internal/cri/opts/spec_nonlinux.go index 83aa1dc17f..9827a4e388 100644 --- a/internal/cri/opts/spec_nonlinux.go +++ b/internal/cri/opts/spec_nonlinux.go @@ -40,3 +40,9 @@ func WithCDI(_ map[string]string, _ []*runtime.CDIDevice) oci.SpecOpts { return nil } } + +// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode. +// On non-Linux platforms, this always returns false. +func IsCgroup2UnifiedMode() bool { + return false +} diff --git a/internal/cri/server/container_create.go b/internal/cri/server/container_create.go index e7e56f163b..0381348a6a 100644 --- a/internal/cri/server/container_create.go +++ b/internal/cri/server/container_create.go @@ -710,7 +710,14 @@ func (c *criService) buildLinuxSpec( } }() - specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel, runtimeHandler)) + var ociSpecOpts oci.SpecOpts + if ociRuntime.CgroupWritable { + ociSpecOpts = customopts.WithMountsCgroupWritable(c.os, config, extraMounts, mountLabel, runtimeHandler) + } else { + ociSpecOpts = customopts.WithMounts(c.os, config, extraMounts, mountLabel, runtimeHandler) + } + + specOpts = append(specOpts, ociSpecOpts) if !c.config.DisableProcMount { // Change the default masked/readonly paths to empty slices