Kubernetes API for Shared Process Namespace

This commit is contained in:
Lee Verberne 2018-01-23 18:40:39 +01:00
parent 2b530438f1
commit 2343600ccc
6 changed files with 75 additions and 18 deletions

View File

@ -244,6 +244,10 @@ func DropDisabledAlphaFields(podSpec *api.PodSpec) {
} }
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.PodShareProcessNamespace) && podSpec.SecurityContext != nil {
podSpec.SecurityContext.ShareProcessNamespace = nil
}
for i := range podSpec.Containers { for i := range podSpec.Containers {
DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts) DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts)
} }

View File

@ -2644,6 +2644,15 @@ type PodSecurityContext struct {
// +k8s:conversion-gen=false // +k8s:conversion-gen=false
// +optional // +optional
HostIPC bool HostIPC bool
// Share a single process namespace between all of the containers in a pod.
// When this is set containers will be able to view and signal processes from other containers
// in the same pod, and the first process in each container will not be assigned PID 1.
// HostPID and ShareProcessNamespace cannot both be set.
// Optional: Default to false.
// This field is alpha-level and is honored only by servers that enable the PodShareProcessNamespace feature.
// +k8s:conversion-gen=false
// +optional
ShareProcessNamespace *bool
// The SELinux context to be applied to all containers. // The SELinux context to be applied to all containers.
// If unspecified, the container runtime will allocate a random SELinux context for each // If unspecified, the container runtime will allocate a random SELinux context for each
// container. May also be set in SecurityContext. If set in // container. May also be set in SecurityContext. If set in

View File

@ -384,6 +384,7 @@ func Convert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s con
out.HostPID = in.SecurityContext.HostPID out.HostPID = in.SecurityContext.HostPID
out.HostNetwork = in.SecurityContext.HostNetwork out.HostNetwork = in.SecurityContext.HostNetwork
out.HostIPC = in.SecurityContext.HostIPC out.HostIPC = in.SecurityContext.HostIPC
out.ShareProcessNamespace = in.SecurityContext.ShareProcessNamespace
} }
return nil return nil
@ -408,6 +409,7 @@ func Convert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s con
out.SecurityContext.HostNetwork = in.HostNetwork out.SecurityContext.HostNetwork = in.HostNetwork
out.SecurityContext.HostPID = in.HostPID out.SecurityContext.HostPID = in.HostPID
out.SecurityContext.HostIPC = in.HostIPC out.SecurityContext.HostIPC = in.HostIPC
out.SecurityContext.ShareProcessNamespace = in.ShareProcessNamespace
return nil return nil
} }

View File

@ -3264,6 +3264,13 @@ func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *
allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg)) allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg))
} }
} }
if securityContext.ShareProcessNamespace != nil {
if !utilfeature.DefaultFeatureGate.Enabled(features.PodShareProcessNamespace) {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("shareProcessNamespace"), "Process Namespace Sharing is disabled by PodShareProcessNamespace feature-gate"))
} else if securityContext.HostPID && *securityContext.ShareProcessNamespace {
allErrs = append(allErrs, field.Invalid(fldPath.Child("shareProcessNamespace"), *securityContext.ShareProcessNamespace, "ShareProcessNamespace and HostPID cannot both be enabled"))
}
}
} }
return allErrs return allErrs

View File

@ -31,11 +31,13 @@ import (
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/api/testapi" _ "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/apparmor"
) )
@ -5748,24 +5750,9 @@ func TestValidatePodSpec(t *testing.T) {
minGroupID := int64(0) minGroupID := int64(0)
maxGroupID := int64(2147483647) maxGroupID := int64(2147483647)
priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("PodPriority") defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
defer func() { defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodShareProcessNamespace, true)()
var err error
// restoring the old value
if priorityEnabled {
err = utilfeature.DefaultFeatureGate.Set("PodPriority=true")
} else {
err = utilfeature.DefaultFeatureGate.Set("PodPriority=false")
}
if err != nil {
t.Errorf("Failed to restore feature gate for PodPriority: %v", err)
}
}()
err := utilfeature.DefaultFeatureGate.Set("PodPriority=true")
if err != nil {
t.Errorf("Failed to enable feature gate for PodPriority: %v", err)
return
}
successCases := []core.PodSpec{ successCases := []core.PodSpec{
{ // Populate basic fields, leave defaults for most. { // Populate basic fields, leave defaults for most.
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
@ -5890,6 +5877,15 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
PriorityClassName: "valid-name", PriorityClassName: "valid-name",
}, },
{ // Populate ShareProcessNamespace
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
SecurityContext: &core.PodSecurityContext{
ShareProcessNamespace: &[]bool{true}[0],
},
},
} }
for i := range successCases { for i := range successCases {
if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 { if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 {
@ -6061,12 +6057,42 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
PriorityClassName: "InvalidName", PriorityClassName: "InvalidName",
}, },
"ShareProcessNamespace and HostPID both set": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
SecurityContext: &core.PodSecurityContext{
HostPID: true,
ShareProcessNamespace: &[]bool{true}[0],
},
},
} }
for k, v := range failureCases { for k, v := range failureCases {
if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %q", k) t.Errorf("expected failure for %q", k)
} }
} }
// original value will be restored by previous defer
utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodShareProcessNamespace, false)
featuregatedCases := map[string]core.PodSpec{
"set ShareProcessNamespace": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
SecurityContext: &core.PodSecurityContext{
ShareProcessNamespace: &[]bool{true}[0],
},
},
}
for k, v := range featuregatedCases {
if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure due to gated feature: %q", k)
}
}
} }
func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec { func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {

View File

@ -2861,6 +2861,15 @@ type PodSpec struct {
// +k8s:conversion-gen=false // +k8s:conversion-gen=false
// +optional // +optional
HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"` HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"`
// Share a single process namespace between all of the containers in a pod.
// When this is set containers will be able to view and signal processes from other containers
// in the same pod, and the first process in each container will not be assigned PID 1.
// HostPID and ShareProcessNamespace cannot both be set.
// Optional: Default to false.
// This field is alpha-level and is honored only by servers that enable the PodShareProcessNamespace feature.
// +k8s:conversion-gen=false
// +optional
ShareProcessNamespace *bool `json:"shareProcessNamespace,omitempty" protobuf:"varint,27,opt,name=shareProcessNamespace"`
// SecurityContext holds pod-level security attributes and common container settings. // SecurityContext holds pod-level security attributes and common container settings.
// Optional: Defaults to empty. See type description for default values of each field. // Optional: Defaults to empty. See type description for default values of each field.
// +optional // +optional