Merge pull request #9787 from AkihiroSuda/cri-rro-kep-3857
KEP-3857: Recursive Read-only (RRO) mounts
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
crierrors "k8s.io/cri-api/pkg/errors"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/containers"
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
@@ -39,7 +40,7 @@ import (
|
||||
)
|
||||
|
||||
// WithMounts sorts and adds runtime and CRI mounts to the spec
|
||||
func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string) oci.SpecOpts {
|
||||
func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string, handler *runtime.RuntimeHandler) 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
|
||||
@@ -151,8 +152,24 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru
|
||||
// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
|
||||
// is readonly. This is different from docker's behavior, but make more sense.
|
||||
if mount.GetReadonly() {
|
||||
options = append(options, "ro")
|
||||
if mount.GetRecursiveReadOnly() {
|
||||
if handler == nil || !handler.Features.RecursiveReadOnlyMounts {
|
||||
return fmt.Errorf("%w: runtime handler does not support recursive read-only mounts (hostPath=%q)",
|
||||
crierrors.ErrRROUnsupported, mount.HostPath)
|
||||
}
|
||||
if mount.Propagation != runtime.MountPropagation_PROPAGATION_PRIVATE {
|
||||
return fmt.Errorf("recursive read-only mount needs private propagation, got %q (hostPath=%q)",
|
||||
mount.Propagation.String(), mount.HostPath)
|
||||
}
|
||||
options = append(options, "rro")
|
||||
} else {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
} else {
|
||||
if mount.GetRecursiveReadOnly() {
|
||||
return fmt.Errorf("recursive read-only mount conflicts with RW mount (hostPath=%q)",
|
||||
mount.HostPath)
|
||||
}
|
||||
options = append(options, "rw")
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,14 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get sandbox runtime: %w", err)
|
||||
}
|
||||
var runtimeHandler *runtime.RuntimeHandler
|
||||
for _, f := range c.runtimeHandlers {
|
||||
f := f
|
||||
if f.Name == sandbox.Metadata.RuntimeHandler {
|
||||
runtimeHandler = f
|
||||
break
|
||||
}
|
||||
}
|
||||
log.G(ctx).Debugf("Use OCI runtime %+v for sandbox %q and container %q", ociRuntime, sandboxID, id)
|
||||
|
||||
spec, err := c.buildContainerSpec(
|
||||
@@ -182,6 +190,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
||||
&image.ImageSpec.Config,
|
||||
volumeMounts,
|
||||
ociRuntime,
|
||||
runtimeHandler,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate container %q spec: %w", id, err)
|
||||
@@ -530,6 +539,7 @@ func (c *criService) buildContainerSpec(
|
||||
imageConfig *imagespec.ImageConfig,
|
||||
extraMounts []*runtime.Mount,
|
||||
ociRuntime criconfig.Runtime,
|
||||
runtimeHandler *runtime.RuntimeHandler,
|
||||
) (_ *runtimespec.Spec, retErr error) {
|
||||
var (
|
||||
specOpts []oci.SpecOpts
|
||||
@@ -559,6 +569,7 @@ func (c *criService) buildContainerSpec(
|
||||
imageConfig,
|
||||
append(linuxMounts, extraMounts...),
|
||||
ociRuntime,
|
||||
runtimeHandler,
|
||||
)
|
||||
case isWindows:
|
||||
specOpts, err = c.buildWindowsSpec(
|
||||
@@ -573,6 +584,7 @@ func (c *criService) buildContainerSpec(
|
||||
imageConfig,
|
||||
extraMounts,
|
||||
ociRuntime,
|
||||
runtimeHandler,
|
||||
)
|
||||
case isDarwin:
|
||||
specOpts, err = c.buildDarwinSpec(
|
||||
@@ -585,6 +597,7 @@ func (c *criService) buildContainerSpec(
|
||||
imageConfig,
|
||||
extraMounts,
|
||||
ociRuntime,
|
||||
runtimeHandler,
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported spec platform: %s", platform.OS)
|
||||
@@ -609,6 +622,7 @@ func (c *criService) buildLinuxSpec(
|
||||
imageConfig *imagespec.ImageConfig,
|
||||
extraMounts []*runtime.Mount,
|
||||
ociRuntime criconfig.Runtime,
|
||||
runtimeHandler *runtime.RuntimeHandler,
|
||||
) (_ []oci.SpecOpts, retErr error) {
|
||||
specOpts := []oci.SpecOpts{
|
||||
oci.WithoutRunMount,
|
||||
@@ -683,7 +697,7 @@ func (c *criService) buildLinuxSpec(
|
||||
}
|
||||
}()
|
||||
|
||||
specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
|
||||
specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel, runtimeHandler))
|
||||
|
||||
if !c.config.DisableProcMount {
|
||||
// Change the default masked/readonly paths to empty slices
|
||||
@@ -841,6 +855,7 @@ func (c *criService) buildWindowsSpec(
|
||||
imageConfig *imagespec.ImageConfig,
|
||||
extraMounts []*runtime.Mount,
|
||||
ociRuntime criconfig.Runtime,
|
||||
runtimeHandler *runtime.RuntimeHandler,
|
||||
) (_ []oci.SpecOpts, retErr error) {
|
||||
var specOpts []oci.SpecOpts
|
||||
specOpts = append(specOpts, customopts.WithProcessCommandLineOrArgsForWindows(config, imageConfig))
|
||||
@@ -935,6 +950,7 @@ func (c *criService) buildDarwinSpec(
|
||||
imageConfig *imagespec.ImageConfig,
|
||||
extraMounts []*runtime.Mount,
|
||||
ociRuntime criconfig.Runtime,
|
||||
runtimeHandler *runtime.RuntimeHandler,
|
||||
) (_ []oci.SpecOpts, retErr error) {
|
||||
specOpts := []oci.SpecOpts{
|
||||
customopts.WithProcessArgs(config, imageConfig),
|
||||
|
||||
@@ -255,7 +255,7 @@ func TestContainerCapabilities(t *testing.T) {
|
||||
c.allCaps = allCaps
|
||||
|
||||
containerConfig.Linux.SecurityContext.Capabilities = test.capability
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
if selinux.GetEnabled() {
|
||||
@@ -290,7 +290,7 @@ func TestContainerSpecTty(t *testing.T) {
|
||||
c := newTestCRIService()
|
||||
for _, tty := range []bool{true, false} {
|
||||
containerConfig.Tty = tty
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
assert.Equal(t, tty, spec.Process.Terminal)
|
||||
@@ -317,7 +317,7 @@ func TestContainerSpecDefaultPath(t *testing.T) {
|
||||
imageConfig.Env = append(imageConfig.Env, pathenv)
|
||||
expected = pathenv
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
assert.Contains(t, spec.Process.Env, expected)
|
||||
@@ -334,7 +334,7 @@ func TestContainerSpecReadonlyRootfs(t *testing.T) {
|
||||
c := newTestCRIService()
|
||||
for _, readonly := range []bool{true, false} {
|
||||
containerConfig.Linux.SecurityContext.ReadonlyRootfs = readonly
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
assert.Equal(t, readonly, spec.Root.Readonly)
|
||||
@@ -368,7 +368,7 @@ func TestContainerSpecWithExtraMounts(t *testing.T) {
|
||||
Readonly: false,
|
||||
},
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, extraMounts, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, extraMounts, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
var mounts, sysMounts []runtimespec.Mount
|
||||
@@ -435,7 +435,7 @@ func TestContainerAndSandboxPrivileged(t *testing.T) {
|
||||
sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||
Privileged: test.sandboxPrivileged,
|
||||
}
|
||||
_, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
_, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
if test.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -476,7 +476,7 @@ func TestPrivilegedBindMount(t *testing.T) {
|
||||
containerConfig.Linux.SecurityContext.Privileged = test.privileged
|
||||
sandboxConfig.Linux.SecurityContext.Privileged = test.privileged
|
||||
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if test.expectedSysFSRO {
|
||||
@@ -597,7 +597,7 @@ func TestMountPropagation(t *testing.T) {
|
||||
var spec runtimespec.Spec
|
||||
spec.Linux = &runtimespec.Linux{}
|
||||
|
||||
err := opts.WithMounts(c.os, config, []*runtime.Mount{test.criMount}, "")(context.Background(), nil, nil, &spec)
|
||||
err := opts.WithMounts(c.os, config, []*runtime.Mount{test.criMount}, "", nil)(context.Background(), nil, nil, &spec)
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@@ -648,7 +648,7 @@ func TestPidNamespace(t *testing.T) {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
containerConfig.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{Pid: test.pidNS}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, spec.Linux.Namespaces, test.expected)
|
||||
})
|
||||
@@ -823,7 +823,7 @@ func TestUserNamespace(t *testing.T) {
|
||||
sandboxUserns = test.sandboxUserNS
|
||||
}
|
||||
sandboxConfig.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{UsernsOptions: sandboxUserns}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
|
||||
if test.err {
|
||||
require.Error(t, err)
|
||||
@@ -853,7 +853,7 @@ func TestNoDefaultRunMount(t *testing.T) {
|
||||
ociRuntime := config.Runtime{}
|
||||
c := newTestCRIService()
|
||||
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
assert.NoError(t, err)
|
||||
for _, mount := range spec.Mounts {
|
||||
assert.NotEqual(t, "/run", mount.Destination)
|
||||
@@ -1282,7 +1282,7 @@ func TestMaskedAndReadonlyPaths(t *testing.T) {
|
||||
sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||
Privileged: test.privileged,
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
if !test.privileged { // specCheck presumes an unprivileged container
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -1335,7 +1335,7 @@ func TestHostname(t *testing.T) {
|
||||
sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||
NamespaceOptions: &runtime.NamespaceOption{Network: test.networkNs},
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
assert.Contains(t, spec.Process.Env, test.expectedEnv)
|
||||
@@ -1348,7 +1348,7 @@ func TestDisableCgroup(t *testing.T) {
|
||||
ociRuntime := config.Runtime{}
|
||||
c := newTestCRIService()
|
||||
c.config.DisableCgroup = true
|
||||
spec, err := c.buildContainerSpec(currentPlatform, "test-id", "sandbox-id", 1234, "", "container-name", testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, "test-id", "sandbox-id", 1234, "", "container-name", testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("resource limit should not be set")
|
||||
@@ -1503,7 +1503,7 @@ additional-group-for-root:x:22222:root
|
||||
containerConfig.Linux.SecurityContext = test.securityContext
|
||||
imageConfig.User = test.imageConfigUser
|
||||
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
spec.Root.Path = tempRootDir // simulating /etc/{passwd, group}
|
||||
@@ -1579,7 +1579,7 @@ func TestNonRootUserAndDevices(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedDeviceUID, *spec.Linux.Devices[0].UID)
|
||||
@@ -1653,7 +1653,7 @@ func TestPrivilegedDevices(t *testing.T) {
|
||||
PrivilegedWithoutHostDevices: test.privilegedWithoutHostDevices,
|
||||
PrivilegedWithoutHostDevicesAllDevicesAllowed: test.privilegedWithoutHostDevicesAllDevicesAllowed,
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hostDevicesRaw, err := oci.HostDevices()
|
||||
@@ -1708,7 +1708,7 @@ func TestBaseOCISpec(t *testing.T) {
|
||||
testPid := uint32(1234)
|
||||
containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
|
||||
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -2040,7 +2040,7 @@ containerEdits:
|
||||
},
|
||||
} {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestGeneralContainerSpec(t *testing.T) {
|
||||
c := newTestCRIService()
|
||||
testSandboxID := "sandbox-id"
|
||||
testContainerName := "container-name"
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
require.NoError(t, err)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestPodAnnotationPassthroughContainerSpec(t *testing.T) {
|
||||
PodAnnotations: test.podAnnotations,
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName,
|
||||
containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -512,7 +512,7 @@ func TestContainerAnnotationPassthroughContainerSpec(t *testing.T) {
|
||||
ContainerAnnotations: test.containerAnnotations,
|
||||
}
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName,
|
||||
containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
|
||||
@@ -157,7 +157,7 @@ func TestContainerWindowsNetworkNamespace(t *testing.T) {
|
||||
c := newTestCRIService()
|
||||
|
||||
containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -179,7 +179,7 @@ func TestMountCleanPath(t *testing.T) {
|
||||
ContainerPath: "c:/test/container-path",
|
||||
HostPath: "c:/test/host-path",
|
||||
})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -199,7 +199,7 @@ func TestMountNamedPipe(t *testing.T) {
|
||||
ContainerPath: `\\.\pipe\foo`,
|
||||
HostPath: `\\.\pipe\foo`,
|
||||
})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
|
||||
spec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
specCheck(t, testID, testSandboxID, testPid, spec)
|
||||
@@ -251,7 +251,7 @@ func TestHostProcessRequirements(t *testing.T) {
|
||||
sandboxConfig.Windows.SecurityContext = &runtime.WindowsSandboxSecurityContext{
|
||||
HostProcess: test.sandboxHostProcess,
|
||||
}
|
||||
_, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
|
||||
_, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime, nil)
|
||||
if test.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -348,7 +348,7 @@ func TestEntrypointAndCmdForArgsEscaped(t *testing.T) {
|
||||
Args: test.args,
|
||||
Windows: &runtime.WindowsContainerConfig{},
|
||||
}
|
||||
runtimeSpec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
|
||||
runtimeSpec, err := c.buildContainerSpec(currentPlatform, testID, testSandboxID, testPid, nsPath, testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, runtimeSpec)
|
||||
|
||||
|
||||
@@ -21,18 +21,25 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/go-cni"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
"github.com/opencontainers/runtime-spec/specs-go/features"
|
||||
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/kubelet/pkg/cri/streaming"
|
||||
|
||||
introspectionapi "github.com/containerd/containerd/v2/api/services/introspection/v1"
|
||||
apitypes "github.com/containerd/containerd/v2/api/types"
|
||||
containerd "github.com/containerd/containerd/v2/client"
|
||||
_ "github.com/containerd/containerd/v2/core/runtime" // for typeurl init
|
||||
"github.com/containerd/containerd/v2/core/sandbox"
|
||||
"github.com/containerd/containerd/v2/internal/cri/config"
|
||||
criconfig "github.com/containerd/containerd/v2/internal/cri/config"
|
||||
"github.com/containerd/containerd/v2/internal/cri/nri"
|
||||
"github.com/containerd/containerd/v2/internal/cri/server/podsandbox"
|
||||
@@ -46,8 +53,13 @@ import (
|
||||
"github.com/containerd/containerd/v2/internal/registrar"
|
||||
"github.com/containerd/containerd/v2/pkg/oci"
|
||||
osinterface "github.com/containerd/containerd/v2/pkg/os"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services/introspection"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
)
|
||||
|
||||
var kernelSupportsRRO bool
|
||||
|
||||
// defaultNetworkPlugin is used for the default CNI configuration
|
||||
const defaultNetworkPlugin = "default"
|
||||
|
||||
@@ -135,6 +147,8 @@ type criService struct {
|
||||
nri *nri.API
|
||||
// sandboxService is the sandbox related service for CRI
|
||||
sandboxService sandboxService
|
||||
// runtimeHandlers contains runtime handler info
|
||||
runtimeHandlers []*runtime.RuntimeHandler
|
||||
}
|
||||
|
||||
type CRIServiceOptions struct {
|
||||
@@ -157,6 +171,7 @@ type CRIServiceOptions struct {
|
||||
|
||||
// NewCRIService returns a new instance of CRIService
|
||||
func NewCRIService(options *CRIServiceOptions) (CRIService, runtime.RuntimeServiceServer, error) {
|
||||
ctx := context.Background()
|
||||
var err error
|
||||
labels := label.NewStore()
|
||||
config := options.RuntimeService.Config()
|
||||
@@ -222,6 +237,11 @@ func NewCRIService(options *CRIServiceOptions) (CRIService, runtime.RuntimeServi
|
||||
|
||||
c.nri = options.NRI
|
||||
|
||||
c.runtimeHandlers, err = c.introspectRuntimeHandlers(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to introspect runtime handlers: %w", err)
|
||||
}
|
||||
|
||||
return c, c, nil
|
||||
}
|
||||
|
||||
@@ -340,3 +360,81 @@ func (c *criService) Close() error {
|
||||
func (c *criService) IsInitialized() bool {
|
||||
return c.initialized.Load()
|
||||
}
|
||||
|
||||
func (c *criService) introspectRuntimeHandlers(ctx context.Context) ([]*runtime.RuntimeHandler, error) {
|
||||
var res []*runtime.RuntimeHandler
|
||||
intro := c.client.IntrospectionService()
|
||||
for name, r := range c.config.Runtimes {
|
||||
h := runtime.RuntimeHandler{
|
||||
Name: name,
|
||||
}
|
||||
rawFeatures, err := introspectRuntimeFeatures(ctx, intro, r)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Debugf("failed to introspect features of runtime %q", name)
|
||||
} else {
|
||||
h.Features = &runtime.RuntimeHandlerFeatures{}
|
||||
if slices.Contains(rawFeatures.MountOptions, "rro") {
|
||||
if kernelSupportsRRO {
|
||||
log.G(ctx).Debugf("runtime %q supports recursive read-only mounts", name)
|
||||
h.Features.RecursiveReadOnlyMounts = true
|
||||
} else {
|
||||
log.G(ctx).Debugf("runtime %q supports recursive read-only mounts, but the kernel does not", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
res = append(res, &h)
|
||||
if name == c.config.DefaultRuntimeName {
|
||||
defH := h
|
||||
defH.Name = "" // denotes default
|
||||
res = append(res, &defH)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func introspectRuntimeFeatures(ctx context.Context, intro introspection.Service, r config.Runtime) (*features.Features, error) {
|
||||
if r.Type != plugins.RuntimeRuncV2 {
|
||||
return nil, fmt.Errorf("introspecting OCI runtime features needs the runtime type to be %q, got %q",
|
||||
plugins.RuntimeRuncV2, r.Type)
|
||||
// For other runtimes, protobuf.MarshalAnyToProto will cause nil panic during typeurl dereference
|
||||
}
|
||||
infoReq := &introspectionapi.PluginInfoRequest{
|
||||
Type: string(plugins.RuntimePluginV2), // "io.containerd.runtime.v2"
|
||||
ID: "task",
|
||||
}
|
||||
rr := &apitypes.RuntimeRequest{
|
||||
RuntimePath: r.Type, // "io.containerd.runc.v2"
|
||||
}
|
||||
if r.Path != "" {
|
||||
rr.RuntimePath = r.Path // "/usr/local/bin/crun"
|
||||
}
|
||||
options, err := config.GenerateRuntimeOptions(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr.Options, err = protobuf.MarshalAnyToProto(options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal %T: %w", options, err)
|
||||
}
|
||||
infoReq.Options, err = protobuf.MarshalAnyToProto(rr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal %T: %w", rr, err)
|
||||
}
|
||||
infoResp, err := intro.PluginInfo(ctx, infoReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to call PluginInfo: %w", err)
|
||||
}
|
||||
var info apitypes.RuntimeInfo
|
||||
if err := typeurl.UnmarshalTo(infoResp.Extra, &info); err != nil {
|
||||
return nil, fmt.Errorf("failed to get runtime info from plugin info: %w", err)
|
||||
}
|
||||
featuresX, err := typeurl.UnmarshalAny(info.Features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal Features (%T): %w", info.Features, err)
|
||||
}
|
||||
features, ok := featuresX.(*features.Features)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown features type %T", featuresX)
|
||||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
@@ -23,11 +23,20 @@ import (
|
||||
"tags.cncf.io/container-device-interface/pkg/cdi"
|
||||
|
||||
"github.com/containerd/containerd/v2/pkg/cap"
|
||||
"github.com/containerd/containerd/v2/pkg/kernelversion"
|
||||
"github.com/containerd/containerd/v2/pkg/userns"
|
||||
"github.com/containerd/go-cni"
|
||||
"github.com/containerd/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
kernelSupportsRRO, err = kernelversion.GreaterEqualThan(kernelversion.KernelVersion{Kernel: 5, Major: 12})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to check kernel version: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// networkAttachCount is the minimum number of networks the PodSandbox
|
||||
// attaches to
|
||||
const networkAttachCount = 2
|
||||
|
||||
@@ -58,6 +58,7 @@ func (c *criService) Status(ctx context.Context, r *runtime.StatusRequest) (*run
|
||||
runtimeCondition,
|
||||
networkCondition,
|
||||
}},
|
||||
RuntimeHandlers: c.runtimeHandlers,
|
||||
}
|
||||
if r.Verbose {
|
||||
configByt, err := json.Marshal(c.config)
|
||||
|
||||
Reference in New Issue
Block a user