diff --git a/docs/config.md b/docs/config.md index 6f363a8b1..8f218363f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -117,6 +117,11 @@ version = 2 # * OCI: https://github.com/opencontainers/image-spec/blob/master/annotations.md pod_annotations = [] + # container_annotations is a list of container annotations passed through to the OCI config of the containers. + # Container annotations in CRI are usually generated by other Kubernetes node components (i.e., not users). + # Currently, only device plugins populate the annotations. + container_annotations = [] + # privileged_without_host_devices allows overloading the default behaviour of passing host # devices through to privileged containers. This is useful when using a runtime where it does # not make sense to pass host devices to the container when privileged. Defaults to false - diff --git a/pkg/config/config.go b/pkg/config/config.go index 134c4dacb..89b9a2004 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,6 +38,10 @@ type Runtime struct { // PodAnnotations is a list of pod annotations passed to both pod sandbox as well as // container OCI annotations. PodAnnotations []string `toml:"pod_annotations" json:"PodAnnotations"` + // ContainerAnnotations is a list of container annotations passed through to the OCI config of the containers. + // Container annotations in CRI are usually generated by other Kubernetes node components (i.e., not users). + // Currently, only device plugins populate the annotations. + ContainerAnnotations []string `toml:"container_annotations" json:"ContainerAnnotations"` // Root is the directory used by containerd for runtime state. // DEPRECATED: use Options instead. Remove when shim v1 is deprecated. // This only works for runtime type "io.containerd.runtime.v1.linux". diff --git a/pkg/server/container_create_unix.go b/pkg/server/container_create_unix.go index 81b23943a..718806c04 100644 --- a/pkg/server/container_create_unix.go +++ b/pkg/server/container_create_unix.go @@ -210,6 +210,11 @@ func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint3 specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue)) } + for pKey, pValue := range getPassthroughAnnotations(config.Annotations, + ociRuntime.ContainerAnnotations) { + specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue)) + } + specOpts = append(specOpts, customopts.WithOOMScoreAdj(config, c.config.RestrictOOMScoreAdj), customopts.WithPodNamespaces(securityContext, sandboxPid), diff --git a/pkg/server/container_create_unix_test.go b/pkg/server/container_create_unix_test.go index 96a623a32..08b37dd81 100644 --- a/pkg/server/container_create_unix_test.go +++ b/pkg/server/container_create_unix_test.go @@ -98,7 +98,7 @@ func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandbox }, }, Labels: map[string]string{"a": "b"}, - Annotations: map[string]string{"c": "d"}, + Annotations: map[string]string{"ca-c": "ca-d"}, Linux: &runtime.LinuxContainerConfig{ Resources: &runtime.LinuxContainerResources{ CpuPeriod: 100, @@ -367,7 +367,64 @@ func TestPodAnnotationPassthroughContainerSpec(t *testing.T) { } }) } +} +func TestContainerAnnotationPassthroughContainerSpec(t *testing.T) { + testID := "test-id" + testSandboxID := "sandbox-id" + testPid := uint32(1234) + + for desc, test := range map[string]struct { + podAnnotations []string + containerAnnotations []string + configChange func(*runtime.PodSandboxConfig) + specCheck func(*testing.T, *runtimespec.Spec) + }{ + "passthrough annotations from pod and container should be passed as an OCI annotation": { + podAnnotations: []string{"c"}, + containerAnnotations: []string{"c*"}, // wildcard should pick up ca-c->ca-d pair in container + specCheck: func(t *testing.T, spec *runtimespec.Spec) { + assert.Equal(t, "d", spec.Annotations["c"]) + assert.Equal(t, "ca-d", spec.Annotations["ca-c"]) + }, + }, + "annotations should not pass through if no passthrough annotations are configured": { + podAnnotations: []string{}, + containerAnnotations: []string{}, + specCheck: func(t *testing.T, spec *runtimespec.Spec) { + assert.Equal(t, "", spec.Annotations["c"]) + assert.Equal(t, "", spec.Annotations["ca-c"]) + }, + }, + "unmatched annotations should not pass through even if passthrough annotations are configured": { + podAnnotations: []string{"x"}, + containerAnnotations: []string{"x*"}, + specCheck: func(t *testing.T, spec *runtimespec.Spec) { + assert.Equal(t, "", spec.Annotations["c"]) + assert.Equal(t, "", spec.Annotations["ca-c"]) + }, + }, + } { + t.Run(desc, func(t *testing.T) { + c := newTestCRIService() + containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData() + if test.configChange != nil { + test.configChange(sandboxConfig) + } + ociRuntime := config.Runtime{ + PodAnnotations: test.podAnnotations, + ContainerAnnotations: test.containerAnnotations, + } + spec, err := c.containerSpec(testID, testSandboxID, testPid, "", + containerConfig, sandboxConfig, imageConfig, nil, ociRuntime) + assert.NoError(t, err) + assert.NotNil(t, spec) + specCheck(t, testID, testSandboxID, testPid, spec) + if test.specCheck != nil { + test.specCheck(t, spec) + } + }) + } } func TestContainerSpecReadonlyRootfs(t *testing.T) {