Skip to content

Commit d0983b5

Browse files
authored
Merge pull request kubernetes#84731 from verb/ec-pid
Add namespace targeting mode to CRI and kubelet
2 parents 79b674d + d05bcf6 commit d0983b5

13 files changed

+668
-347
lines changed

pkg/kubelet/dockershim/security_context.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,26 +146,25 @@ func modifyHostConfig(sc *runtimeapi.LinuxContainerSecurityContext, hostConfig *
146146
// modifySandboxNamespaceOptions apply namespace options for sandbox
147147
func modifySandboxNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, hostConfig *dockercontainer.HostConfig, network *knetwork.PluginManager) {
148148
// The sandbox's PID namespace is the one that's shared, so CONTAINER and POD are equivalent for it
149-
modifyCommonNamespaceOptions(nsOpts, hostConfig)
149+
if nsOpts.GetPid() == runtimeapi.NamespaceMode_NODE {
150+
hostConfig.PidMode = namespaceModeHost
151+
}
150152
modifyHostOptionsForSandbox(nsOpts, network, hostConfig)
151153
}
152154

153155
// modifyContainerNamespaceOptions apply namespace options for container
154156
func modifyContainerNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, podSandboxID string, hostConfig *dockercontainer.HostConfig) {
155-
if nsOpts.GetPid() == runtimeapi.NamespaceMode_POD {
157+
switch nsOpts.GetPid() {
158+
case runtimeapi.NamespaceMode_NODE:
159+
hostConfig.PidMode = namespaceModeHost
160+
case runtimeapi.NamespaceMode_POD:
156161
hostConfig.PidMode = dockercontainer.PidMode(fmt.Sprintf("container:%v", podSandboxID))
162+
case runtimeapi.NamespaceMode_TARGET:
163+
hostConfig.PidMode = dockercontainer.PidMode(fmt.Sprintf("container:%v", nsOpts.GetTargetId()))
157164
}
158-
modifyCommonNamespaceOptions(nsOpts, hostConfig)
159165
modifyHostOptionsForContainer(nsOpts, podSandboxID, hostConfig)
160166
}
161167

162-
// modifyCommonNamespaceOptions apply common namespace options for sandbox and container
163-
func modifyCommonNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, hostConfig *dockercontainer.HostConfig) {
164-
if nsOpts.GetPid() == runtimeapi.NamespaceMode_NODE {
165-
hostConfig.PidMode = namespaceModeHost
166-
}
167-
}
168-
169168
// modifyHostOptionsForSandbox applies NetworkMode/UTSMode to sandbox's dockercontainer.HostConfig.
170169
func modifyHostOptionsForSandbox(nsOpts *runtimeapi.NamespaceOption, network *knetwork.PluginManager, hc *dockercontainer.HostConfig) {
171170
if nsOpts.GetIpc() == runtimeapi.NamespaceMode_NODE {

pkg/kubelet/dockershim/security_context_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,27 @@ func TestModifySandboxNamespaceOptions(t *testing.T) {
345345
NetworkMode: "default",
346346
},
347347
},
348+
{
349+
name: "Pod PID NamespaceOption (for sandbox is same as container ns option)",
350+
nsOpt: &runtimeapi.NamespaceOption{
351+
Pid: runtimeapi.NamespaceMode_POD,
352+
},
353+
expected: &dockercontainer.HostConfig{
354+
PidMode: "",
355+
NetworkMode: "default",
356+
},
357+
},
358+
{
359+
name: "Target PID NamespaceOption (invalid for sandbox)",
360+
nsOpt: &runtimeapi.NamespaceOption{
361+
Pid: runtimeapi.NamespaceMode_TARGET,
362+
TargetId: "same-container",
363+
},
364+
expected: &dockercontainer.HostConfig{
365+
PidMode: "",
366+
NetworkMode: "default",
367+
},
368+
},
348369
}
349370
for _, tc := range cases {
350371
dockerCfg := &dockercontainer.HostConfig{}
@@ -395,6 +416,29 @@ func TestModifyContainerNamespaceOptions(t *testing.T) {
395416
PidMode: namespaceModeHost,
396417
},
397418
},
419+
{
420+
name: "Pod PID NamespaceOption",
421+
nsOpt: &runtimeapi.NamespaceOption{
422+
Pid: runtimeapi.NamespaceMode_POD,
423+
},
424+
expected: &dockercontainer.HostConfig{
425+
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
426+
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
427+
PidMode: dockercontainer.PidMode(sandboxNSMode),
428+
},
429+
},
430+
{
431+
name: "Target PID NamespaceOption",
432+
nsOpt: &runtimeapi.NamespaceOption{
433+
Pid: runtimeapi.NamespaceMode_TARGET,
434+
TargetId: "some-container",
435+
},
436+
expected: &dockercontainer.HostConfig{
437+
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
438+
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
439+
PidMode: dockercontainer.PidMode("container:some-container"),
440+
},
441+
},
398442
}
399443
for _, tc := range cases {
400444
dockerCfg := &dockercontainer.HostConfig{}

pkg/kubelet/kuberuntime/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ go_test(
131131
"//staging/src/k8s.io/cri-api/pkg/apis/testing:go_default_library",
132132
"//vendor/github.com/golang/mock/gomock:go_default_library",
133133
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
134+
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
134135
"//vendor/github.com/stretchr/testify/assert:go_default_library",
135136
"//vendor/github.com/stretchr/testify/require:go_default_library",
136137
"//vendor/k8s.io/utils/pointer:go_default_library",

pkg/kubelet/kuberuntime/kuberuntime_container.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ import (
4141
kubetypes "k8s.io/apimachinery/pkg/types"
4242
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
4343
"k8s.io/apimachinery/pkg/util/sets"
44+
utilfeature "k8s.io/apiserver/pkg/util/feature"
4445
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
46+
"k8s.io/kubernetes/pkg/features"
4547
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
4648
"k8s.io/kubernetes/pkg/kubelet/events"
4749
"k8s.io/kubernetes/pkg/kubelet/types"
@@ -86,13 +88,52 @@ func (m *kubeGenericRuntimeManager) recordContainerEvent(pod *v1.Pod, container
8688
m.recorder.Event(ref, eventType, reason, eventMessage)
8789
}
8890

91+
// startSpec wraps the spec required to start a container, either a regular/init container
92+
// or an ephemeral container. Ephemeral containers contain all the fields of regular/init
93+
// containers, plus some additional fields. In both cases startSpec.container will be set.
94+
type startSpec struct {
95+
container *v1.Container
96+
ephemeralContainer *v1.EphemeralContainer
97+
}
98+
99+
func containerStartSpec(c *v1.Container) *startSpec {
100+
return &startSpec{container: c}
101+
}
102+
103+
func ephemeralContainerStartSpec(ec *v1.EphemeralContainer) *startSpec {
104+
return &startSpec{
105+
container: (*v1.Container)(&ec.EphemeralContainerCommon),
106+
ephemeralContainer: ec,
107+
}
108+
}
109+
110+
// getTargetID returns the kubecontainer.ContainerID for ephemeral container namespace
111+
// targeting. The target is stored as EphemeralContainer.TargetContainerName, which must be
112+
// resolved to a ContainerID using podStatus. The target container must already exist, which
113+
// usually isn't a problem since ephemeral containers aren't allowed at pod creation time.
114+
// This always returns nil when the EphemeralContainers feature is disabled.
115+
func (s *startSpec) getTargetID(podStatus *kubecontainer.PodStatus) (*kubecontainer.ContainerID, error) {
116+
if s.ephemeralContainer == nil || s.ephemeralContainer.TargetContainerName == "" || !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
117+
return nil, nil
118+
}
119+
120+
targetStatus := podStatus.FindContainerStatusByName(s.ephemeralContainer.TargetContainerName)
121+
if targetStatus == nil {
122+
return nil, fmt.Errorf("unable to find target container %v", s.ephemeralContainer.TargetContainerName)
123+
}
124+
125+
return &targetStatus.ID, nil
126+
}
127+
89128
// startContainer starts a container and returns a message indicates why it is failed on error.
90129
// It starts the container through the following steps:
91130
// * pull the image
92131
// * create the container
93132
// * start the container
94133
// * run the post start lifecycle hooks (if applicable)
95-
func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, container *v1.Container, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
134+
func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, spec *startSpec, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
135+
container := spec.container
136+
96137
// Step 1: pull the image.
97138
imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets, podSandboxConfig)
98139
if err != nil {
@@ -115,7 +156,14 @@ func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandb
115156
restartCount = containerStatus.RestartCount + 1
116157
}
117158

118-
containerConfig, cleanupAction, err := m.generateContainerConfig(container, pod, restartCount, podIP, imageRef, podIPs)
159+
target, err := spec.getTargetID(podStatus)
160+
if err != nil {
161+
s, _ := grpcstatus.FromError(err)
162+
m.recordContainerEvent(pod, container, "", v1.EventTypeWarning, events.FailedToCreateContainer, "Error: %v", s.Message())
163+
return s.Message(), ErrCreateContainerConfig
164+
}
165+
166+
containerConfig, cleanupAction, err := m.generateContainerConfig(container, pod, restartCount, podIP, imageRef, podIPs, target)
119167
if cleanupAction != nil {
120168
defer cleanupAction()
121169
}
@@ -195,7 +243,7 @@ func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandb
195243
}
196244

197245
// generateContainerConfig generates container config for kubelet runtime v1.
198-
func (m *kubeGenericRuntimeManager) generateContainerConfig(container *v1.Container, pod *v1.Pod, restartCount int, podIP, imageRef string, podIPs []string) (*runtimeapi.ContainerConfig, func(), error) {
246+
func (m *kubeGenericRuntimeManager) generateContainerConfig(container *v1.Container, pod *v1.Pod, restartCount int, podIP, imageRef string, podIPs []string, nsTarget *kubecontainer.ContainerID) (*runtimeapi.ContainerConfig, func(), error) {
199247
opts, cleanupAction, err := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP, podIPs)
200248
if err != nil {
201249
return nil, nil, err
@@ -239,7 +287,7 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *v1.Contai
239287
}
240288

241289
// set platform specific configurations.
242-
if err := m.applyPlatformSpecificContainerConfig(config, container, pod, uid, username); err != nil {
290+
if err := m.applyPlatformSpecificContainerConfig(config, container, pod, uid, username, nsTarget); err != nil {
243291
return nil, cleanupAction, err
244292
}
245293

pkg/kubelet/kuberuntime/kuberuntime_container_linux.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,28 @@ import (
2828
"k8s.io/klog"
2929
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
3030
kubefeatures "k8s.io/kubernetes/pkg/features"
31+
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
3132
"k8s.io/kubernetes/pkg/kubelet/qos"
3233
)
3334

3435
// applyPlatformSpecificContainerConfig applies platform specific configurations to runtimeapi.ContainerConfig.
35-
func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config *runtimeapi.ContainerConfig, container *v1.Container, pod *v1.Pod, uid *int64, username string) error {
36-
config.Linux = m.generateLinuxContainerConfig(container, pod, uid, username)
36+
func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config *runtimeapi.ContainerConfig, container *v1.Container, pod *v1.Pod, uid *int64, username string, nsTarget *kubecontainer.ContainerID) error {
37+
config.Linux = m.generateLinuxContainerConfig(container, pod, uid, username, nsTarget)
3738
return nil
3839
}
3940

4041
// generateLinuxContainerConfig generates linux container config for kubelet runtime v1.
41-
func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.Container, pod *v1.Pod, uid *int64, username string) *runtimeapi.LinuxContainerConfig {
42+
func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.Container, pod *v1.Pod, uid *int64, username string, nsTarget *kubecontainer.ContainerID) *runtimeapi.LinuxContainerConfig {
4243
lc := &runtimeapi.LinuxContainerConfig{
4344
Resources: &runtimeapi.LinuxContainerResources{},
4445
SecurityContext: m.determineEffectiveSecurityContext(pod, container, uid, username),
4546
}
4647

48+
if nsTarget != nil && lc.SecurityContext.NamespaceOptions.Pid == runtimeapi.NamespaceMode_CONTAINER {
49+
lc.SecurityContext.NamespaceOptions.Pid = runtimeapi.NamespaceMode_TARGET
50+
lc.SecurityContext.NamespaceOptions.TargetId = nsTarget.ID
51+
}
52+
4753
// set linux container resources
4854
var cpuShares int64
4955
cpuRequest := container.Resources.Requests.Cpu()

pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ import (
2222
"reflect"
2323
"testing"
2424

25+
"github.com/google/go-cmp/cmp"
2526
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
2627
"github.com/stretchr/testify/assert"
2728
v1 "k8s.io/api/core/v1"
29+
"k8s.io/apimachinery/pkg/api/resource"
2830
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
utilfeature "k8s.io/apiserver/pkg/util/feature"
32+
featuregatetesting "k8s.io/component-base/featuregate/testing"
2933
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
30-
31-
"k8s.io/apimachinery/pkg/api/resource"
34+
"k8s.io/kubernetes/pkg/features"
35+
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
3236
)
3337

3438
func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerIndex int) *runtimeapi.ContainerConfig {
@@ -57,7 +61,7 @@ func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerInde
5761
Stdin: container.Stdin,
5862
StdinOnce: container.StdinOnce,
5963
Tty: container.TTY,
60-
Linux: m.generateLinuxContainerConfig(container, pod, new(int64), ""),
64+
Linux: m.generateLinuxContainerConfig(container, pod, new(int64), "", nil),
6165
Envs: envs,
6266
}
6367
return expectedConfig
@@ -93,7 +97,7 @@ func TestGenerateContainerConfig(t *testing.T) {
9397
}
9498

9599
expectedConfig := makeExpectedConfig(m, pod, 0)
96-
containerConfig, _, err := m.generateContainerConfig(&pod.Spec.Containers[0], pod, 0, "", pod.Spec.Containers[0].Image, []string{})
100+
containerConfig, _, err := m.generateContainerConfig(&pod.Spec.Containers[0], pod, 0, "", pod.Spec.Containers[0].Image, []string{}, nil)
97101
assert.NoError(t, err)
98102
assert.Equal(t, expectedConfig, containerConfig, "generate container config for kubelet runtime v1.")
99103
assert.Equal(t, runAsUser, containerConfig.GetLinux().GetSecurityContext().GetRunAsUser().GetValue(), "RunAsUser should be set")
@@ -124,7 +128,7 @@ func TestGenerateContainerConfig(t *testing.T) {
124128
},
125129
}
126130

127-
_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{})
131+
_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
128132
assert.Error(t, err)
129133

130134
imageID, _ := imageService.PullImage(&runtimeapi.ImageSpec{Image: "busybox"}, nil, nil)
@@ -136,7 +140,7 @@ func TestGenerateContainerConfig(t *testing.T) {
136140
podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsUser = nil
137141
podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsNonRoot = &runAsNonRootTrue
138142

139-
_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{})
143+
_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
140144
assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username")
141145
}
142146

@@ -296,3 +300,70 @@ func TestGetHugepageLimitsFromResources(t *testing.T) {
296300
}
297301
}
298302
}
303+
304+
func TestGenerateLinuxContainerConfigNamespaces(t *testing.T) {
305+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
306+
_, _, m, err := createTestRuntimeManager()
307+
if err != nil {
308+
t.Fatalf("error creating test RuntimeManager: %v", err)
309+
}
310+
311+
for _, tc := range []struct {
312+
name string
313+
pod *v1.Pod
314+
target *kubecontainer.ContainerID
315+
want *runtimeapi.NamespaceOption
316+
}{
317+
{
318+
"Default namespaces",
319+
&v1.Pod{
320+
Spec: v1.PodSpec{
321+
Containers: []v1.Container{
322+
{Name: "test"},
323+
},
324+
},
325+
},
326+
nil,
327+
&runtimeapi.NamespaceOption{
328+
Pid: runtimeapi.NamespaceMode_CONTAINER,
329+
},
330+
},
331+
{
332+
"PID Namespace POD",
333+
&v1.Pod{
334+
Spec: v1.PodSpec{
335+
Containers: []v1.Container{
336+
{Name: "test"},
337+
},
338+
ShareProcessNamespace: &[]bool{true}[0],
339+
},
340+
},
341+
nil,
342+
&runtimeapi.NamespaceOption{
343+
Pid: runtimeapi.NamespaceMode_POD,
344+
},
345+
},
346+
{
347+
"PID Namespace TARGET",
348+
&v1.Pod{
349+
Spec: v1.PodSpec{
350+
Containers: []v1.Container{
351+
{Name: "test"},
352+
},
353+
},
354+
},
355+
&kubecontainer.ContainerID{Type: "docker", ID: "really-long-id-string"},
356+
&runtimeapi.NamespaceOption{
357+
Pid: runtimeapi.NamespaceMode_TARGET,
358+
TargetId: "really-long-id-string",
359+
},
360+
},
361+
} {
362+
t.Run(tc.name, func(t *testing.T) {
363+
got := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target)
364+
if diff := cmp.Diff(tc.want, got.SecurityContext.NamespaceOptions); diff != "" {
365+
t.Errorf("%v: diff (-want +got):\n%v", t.Name(), diff)
366+
}
367+
})
368+
}
369+
}

0 commit comments

Comments
 (0)