Add API for mount propagation.
In fact, this is one annotation + its parsing & validation. Appropriate kubelet logic that uses this annotation is in following patches.
This commit is contained in:
parent
2db8af96e2
commit
c49e34fd17
@ -1445,8 +1445,34 @@ type VolumeMount struct {
|
||||
// Defaults to "" (volume's root).
|
||||
// +optional
|
||||
SubPath string
|
||||
// mountPropagation determines how mounts are propagated from the host
|
||||
// to container and the other way around.
|
||||
// When not set, MountPropagationHostToContainer is used.
|
||||
// This field is alpha in 1.8 and can be reworked or removed in a future
|
||||
// release.
|
||||
// +optional
|
||||
MountPropagation *MountPropagationMode
|
||||
}
|
||||
|
||||
// MountPropagationMode describes mount propagation.
|
||||
type MountPropagationMode string
|
||||
|
||||
const (
|
||||
// MountPropagationHostToContainer means that the volume in a container will
|
||||
// receive new mounts from the host or other containers, but filesystems
|
||||
// mounted inside the container won't be propagated to the host or other
|
||||
// containers.
|
||||
// Note that this mode is recursively applied to all mounts in the volume
|
||||
// ("rslave" in Linux terminology).
|
||||
MountPropagationHostToContainer MountPropagationMode = "HostToContainer"
|
||||
// MountPropagationBidirectional means that the volume in a container will
|
||||
// receive new mounts from the host or other containers, and its own mounts
|
||||
// will be propagated from the container to the host or other containers.
|
||||
// Note that this mode is recursively applied to all mounts in the volume
|
||||
// ("rshared" in Linux terminology).
|
||||
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
|
||||
)
|
||||
|
||||
// EnvVar represents an environment variable present in a Container.
|
||||
type EnvVar struct {
|
||||
// Required: This must be a C_IDENTIFIER.
|
||||
|
@ -5133,6 +5133,7 @@ func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *v1.VolumeMount, out *api.
|
||||
out.ReadOnly = in.ReadOnly
|
||||
out.MountPath = in.MountPath
|
||||
out.SubPath = in.SubPath
|
||||
out.MountPropagation = (*api.MountPropagationMode)(unsafe.Pointer(in.MountPropagation))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5146,6 +5147,7 @@ func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *v1.
|
||||
out.ReadOnly = in.ReadOnly
|
||||
out.MountPath = in.MountPath
|
||||
out.SubPath = in.SubPath
|
||||
out.MountPropagation = (*v1.MountPropagationMode)(unsafe.Pointer(in.MountPropagation))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1027,6 +1027,38 @@ func validatePathNoBacksteps(targetPath string, fldPath *field.Path) field.Error
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateMountPropagation verifies that MountPropagation field is valid and
|
||||
// allowed for given container.
|
||||
func validateMountPropagation(mountPropagation *api.MountPropagationMode, container *api.Container, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if mountPropagation == nil {
|
||||
return allErrs
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.MountPropagation) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "mount propagation is disabled by feature-gate"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
supportedMountPropagations := sets.NewString(string(api.MountPropagationBidirectional), string(api.MountPropagationHostToContainer))
|
||||
if !supportedMountPropagations.Has(string(*mountPropagation)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath, *mountPropagation, supportedMountPropagations.List()))
|
||||
}
|
||||
|
||||
if container == nil {
|
||||
// The container is not available yet, e.g. during validation of
|
||||
// PodPreset. Stop validation now, Pod validation will refuse final
|
||||
// Pods with Bidirectional propagation in non-privileged containers.
|
||||
return allErrs
|
||||
}
|
||||
|
||||
privileged := container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged
|
||||
if *mountPropagation == api.MountPropagationBidirectional && !privileged {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "Bidirectional mount propagation is available only to privileged containers"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// This validate will make sure targetPath:
|
||||
// 1. is not abs path
|
||||
// 2. does not contain any '..' elements
|
||||
@ -1845,7 +1877,7 @@ func validateSecretKeySelector(s *api.SecretKeySelector, fldPath *field.Path) fi
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, container *api.Container, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
mountpoints := sets.NewString()
|
||||
|
||||
@ -1869,6 +1901,10 @@ func ValidateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath
|
||||
if len(mnt.SubPath) > 0 {
|
||||
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
||||
}
|
||||
|
||||
if mnt.MountPropagation != nil {
|
||||
allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@ -2135,7 +2171,7 @@ func validateContainers(containers []api.Container, volumes sets.String, fldPath
|
||||
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
|
||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...)
|
||||
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volumes, idxPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volumes, &ctr, idxPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...)
|
||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...)
|
||||
|
@ -3405,6 +3405,10 @@ func TestValidateEnvFrom(t *testing.T) {
|
||||
|
||||
func TestValidateVolumeMounts(t *testing.T) {
|
||||
volumes := sets.NewString("abc", "123", "abc-123")
|
||||
container := api.Container{
|
||||
SecurityContext: nil,
|
||||
}
|
||||
propagation := api.MountPropagationBidirectional
|
||||
|
||||
successCase := []api.VolumeMount{
|
||||
{Name: "abc", MountPath: "/foo"},
|
||||
@ -3415,28 +3419,154 @@ func TestValidateVolumeMounts(t *testing.T) {
|
||||
{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
|
||||
{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
|
||||
}
|
||||
if errs := ValidateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
|
||||
if errs := ValidateVolumeMounts(successCase, volumes, &container, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
errorCases := map[string][]api.VolumeMount{
|
||||
"empty name": {{Name: "", MountPath: "/foo"}},
|
||||
"name not found": {{Name: "", MountPath: "/foo"}},
|
||||
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
||||
"relative mountpath": {{Name: "abc", MountPath: "bar"}},
|
||||
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
||||
"absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
|
||||
"subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
|
||||
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||
"empty name": {{Name: "", MountPath: "/foo"}},
|
||||
"name not found": {{Name: "", MountPath: "/foo"}},
|
||||
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
||||
"relative mountpath": {{Name: "abc", MountPath: "bar"}},
|
||||
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
||||
"absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
|
||||
"subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
|
||||
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||
"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 {
|
||||
if errs := ValidateVolumeMounts(v, volumes, &container, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMountPropagation(t *testing.T) {
|
||||
bTrue := true
|
||||
bFalse := false
|
||||
privilegedContainer := &api.Container{
|
||||
SecurityContext: &api.SecurityContext{
|
||||
Privileged: &bTrue,
|
||||
},
|
||||
}
|
||||
nonPrivilegedContainer := &api.Container{
|
||||
SecurityContext: &api.SecurityContext{
|
||||
Privileged: &bFalse,
|
||||
},
|
||||
}
|
||||
defaultContainer := &api.Container{}
|
||||
|
||||
propagationBidirectional := api.MountPropagationBidirectional
|
||||
propagationHostToContainer := api.MountPropagationHostToContainer
|
||||
propagationInvalid := api.MountPropagationMode("invalid")
|
||||
|
||||
tests := []struct {
|
||||
mount api.VolumeMount
|
||||
container *api.Container
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
// implicitly non-privileged container + no propagation
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo"},
|
||||
defaultContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// implicitly non-privileged container + HostToContainer
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
|
||||
defaultContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// error: implicitly non-privileged container + Bidirectional
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
|
||||
defaultContainer,
|
||||
true,
|
||||
},
|
||||
{
|
||||
// explicitly non-privileged container + no propagation
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo"},
|
||||
nonPrivilegedContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// explicitly non-privileged container + HostToContainer
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
|
||||
nonPrivilegedContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// explicitly non-privileged container + HostToContainer
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
|
||||
nonPrivilegedContainer,
|
||||
true,
|
||||
},
|
||||
{
|
||||
// privileged container + no propagation
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo"},
|
||||
privilegedContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// privileged container + HostToContainer
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
|
||||
privilegedContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// privileged container + Bidirectional
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
|
||||
privilegedContainer,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// error: privileged container + invalid mount propagation
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
|
||||
privilegedContainer,
|
||||
true,
|
||||
},
|
||||
{
|
||||
// no container + Bidirectional
|
||||
api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
// Enable MountPropagation for this test
|
||||
priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("MountPropagation")
|
||||
defer func() {
|
||||
var err error
|
||||
// restoring the old value
|
||||
if priorityEnabled {
|
||||
err = utilfeature.DefaultFeatureGate.Set("MountPropagation=true")
|
||||
} else {
|
||||
err = utilfeature.DefaultFeatureGate.Set("MountPropagation=false")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Failed to restore feature gate for MountPropagation: %v", err)
|
||||
}
|
||||
}()
|
||||
err := utilfeature.DefaultFeatureGate.Set("MountPropagation=true")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to enable feature gate for MountPropagation: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
volumes := sets.NewString("foo")
|
||||
errs := ValidateVolumeMounts([]api.VolumeMount{test.mount}, volumes, test.container, field.NewPath("field"))
|
||||
if test.expectError && len(errs) == 0 {
|
||||
t.Errorf("test %d expected error, got none", i)
|
||||
}
|
||||
if !test.expectError && len(errs) != 0 {
|
||||
t.Errorf("test %d expected success, got error: %v", i, errs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestValidateProbe(t *testing.T) {
|
||||
handler := api.Handler{Exec: &api.ExecAction{Command: []string{"echo"}}}
|
||||
// These fields must be positive.
|
||||
|
@ -1409,7 +1409,9 @@ func (in *Container) DeepCopyInto(out *Container) {
|
||||
if in.VolumeMounts != nil {
|
||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||
*out = make([]VolumeMount, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.LivenessProbe != nil {
|
||||
in, out := &in.LivenessProbe, &out.LivenessProbe
|
||||
@ -5931,6 +5933,15 @@ func (in *Volume) DeepCopy() *Volume {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeMount) DeepCopyInto(out *VolumeMount) {
|
||||
*out = *in
|
||||
if in.MountPropagation != nil {
|
||||
in, out := &in.MountPropagation, &out.MountPropagation
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(MountPropagationMode)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ func ValidatePodPresetSpec(spec *settings.PodPresetSpec, fldPath *field.Path) fi
|
||||
allErrs = append(allErrs, vErrs...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateEnvFrom(spec.EnvFrom, fldPath.Child("envFrom"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, volumes, fldPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, volumes, nil, fldPath.Child("volumeMounts"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
@ -142,7 +142,9 @@ func (in *PodPresetSpec) DeepCopyInto(out *PodPresetSpec) {
|
||||
if in.VolumeMounts != nil {
|
||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||
*out = make([]api.VolumeMount, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1547,8 +1547,34 @@ type VolumeMount struct {
|
||||
// Defaults to "" (volume's root).
|
||||
// +optional
|
||||
SubPath string `json:"subPath,omitempty" protobuf:"bytes,4,opt,name=subPath"`
|
||||
// mountPropagation determines how mounts are propagated from the host
|
||||
// to container and the other way around.
|
||||
// When not set, MountPropagationHostToContainer is used.
|
||||
// This field is alpha in 1.8 and can be reworked or removed in a future
|
||||
// release.
|
||||
// +optional
|
||||
MountPropagation *MountPropagationMode `json:"mountPropagation,omitempty"`
|
||||
}
|
||||
|
||||
// MountPropagationMode describes mount propagation.
|
||||
type MountPropagationMode string
|
||||
|
||||
const (
|
||||
// MountPropagationHostToContainer means that the volume in a container will
|
||||
// receive new mounts from the host or other containers, but filesystems
|
||||
// mounted inside the container won't be propagated to the host or other
|
||||
// containers.
|
||||
// Note that this mode is recursively applied to all mounts in the volume
|
||||
// ("rslave" in Linux terminology).
|
||||
MountPropagationHostToContainer MountPropagationMode = "HostToContainer"
|
||||
// MountPropagationBidirectional means that the volume in a container will
|
||||
// receive new mounts from the host or other containers, and its own mounts
|
||||
// will be propagated from the container to the host or other containers.
|
||||
// Note that this mode is recursively applied to all mounts in the volume
|
||||
// ("rshared" in Linux terminology).
|
||||
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
|
||||
)
|
||||
|
||||
// EnvVar represents an environment variable present in a Container.
|
||||
type EnvVar struct {
|
||||
// Name of the environment variable. Must be a C_IDENTIFIER.
|
||||
|
@ -1409,7 +1409,9 @@ func (in *Container) DeepCopyInto(out *Container) {
|
||||
if in.VolumeMounts != nil {
|
||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||
*out = make([]VolumeMount, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.LivenessProbe != nil {
|
||||
in, out := &in.LivenessProbe, &out.LivenessProbe
|
||||
@ -5933,6 +5935,15 @@ func (in *Volume) DeepCopy() *Volume {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeMount) DeepCopyInto(out *VolumeMount) {
|
||||
*out = *in
|
||||
if in.MountPropagation != nil {
|
||||
in, out := &in.MountPropagation, &out.MountPropagation
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(MountPropagationMode)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,9 @@ func (in *PodPresetSpec) DeepCopyInto(out *PodPresetSpec) {
|
||||
if in.VolumeMounts != nil {
|
||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||
*out = make([]v1.VolumeMount, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user