Do hostNet Pod-ports -> hostPorts in Pod defaults

Rather than doing it in PodSpec defaulting, which triggers in
Deployments and DaemonSets, do it only when a Pod is actually in play.
This commit is contained in:
Tim Hockin 2023-05-01 13:57:26 -07:00
parent 4c45313c3f
commit ec3379a717
No known key found for this signature in database
7 changed files with 371 additions and 35 deletions

View File

@ -213,6 +213,30 @@ func TestValidateStatefulSet(t *testing.T) {
}, },
} }
validHostNetPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
HostNetwork: true,
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{
Name: "abc",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}},
}},
},
},
}
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{ invalidPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
@ -254,6 +278,9 @@ func TestValidateStatefulSet(t *testing.T) {
}, { }, {
name: "alphanumeric name", name: "alphanumeric name",
set: mkStatefulSet(&validPodTemplate, tweakName("abc-123")), set: mkStatefulSet(&validPodTemplate, tweakName("abc-123")),
}, {
name: "hostNetwork true",
set: mkStatefulSet(&validHostNetPodTemplate),
}, { }, {
name: "parallel pod management", name: "parallel pod management",
set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)), set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)),
@ -1988,6 +2015,30 @@ func TestValidateDaemonSet(t *testing.T) {
}, },
}, },
} }
validHostNetPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
HostNetwork: true,
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{
Name: "abc",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: api.TerminationMessageReadFile,
Ports: []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}},
}},
},
},
}
invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{ invalidPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
@ -2018,6 +2069,15 @@ func TestValidateDaemonSet(t *testing.T) {
Type: apps.OnDeleteDaemonSetStrategyType, Type: apps.OnDeleteDaemonSetStrategyType,
}, },
}, },
}, {
ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
Spec: apps.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validHostNetPodTemplate.Template,
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Type: apps.OnDeleteDaemonSetStrategyType,
},
},
}, },
} }
for _, successCase := range successCases { for _, successCase := range successCases {
@ -2187,8 +2247,8 @@ func TestValidateDaemonSet(t *testing.T) {
} }
} }
func validDeployment() *apps.Deployment { func validDeployment(tweaks ...func(d *apps.Deployment)) *apps.Deployment {
return &apps.Deployment{ d := &apps.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "abc", Name: "abc",
Namespace: metav1.NamespaceDefault, Namespace: metav1.NamespaceDefault,
@ -2230,11 +2290,26 @@ func validDeployment() *apps.Deployment {
}, },
}, },
} }
for _, tweak := range tweaks {
tweak(d)
}
return d
} }
func TestValidateDeployment(t *testing.T) { func TestValidateDeployment(t *testing.T) {
successCases := []*apps.Deployment{ successCases := []*apps.Deployment{
validDeployment(), validDeployment(),
validDeployment(func(d *apps.Deployment) {
d.Spec.Template.Spec.SecurityContext = &api.PodSecurityContext{
HostNetwork: true,
}
d.Spec.Template.Spec.Containers[0].Ports = []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}}
}),
} }
for _, successCase := range successCases { for _, successCase := range successCases {
if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
@ -3174,6 +3249,30 @@ func TestValidateReplicaSet(t *testing.T) {
}, },
}, },
} }
validHostNetPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
HostNetwork: true,
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{
Name: "abc",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: api.TerminationMessageReadFile,
Ports: []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}},
}},
},
},
}
readWriteVolumePodTemplate := api.PodTemplate{ readWriteVolumePodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -3211,6 +3310,12 @@ func TestValidateReplicaSet(t *testing.T) {
Selector: &metav1.LabelSelector{MatchLabels: validLabels}, Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, {
ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
Spec: apps.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: validHostNetPodTemplate.Template,
},
}, { }, {
ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
Spec: apps.ReplicaSetSpec{ Spec: apps.ReplicaSetSpec{

View File

@ -99,6 +99,17 @@ func TestValidateJob(t *testing.T) {
validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector) validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector) validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever
validHostNetPodTemplateSpec := func() api.PodTemplateSpec {
spec := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
spec.Spec.SecurityContext = &api.PodSecurityContext{
HostNetwork: true,
}
spec.Spec.Containers[0].Ports = []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}}
return spec
}()
successCases := map[string]struct { successCases := map[string]struct {
opts JobValidationOptions opts JobValidationOptions
@ -179,6 +190,20 @@ func TestValidateJob(t *testing.T) {
}, },
}, },
}, },
"valid hostnet": {
opts: JobValidationOptions{RequirePrefixedLabels: true},
job: batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validHostNetPodTemplateSpec,
},
},
},
"valid NonIndexed completion mode": { "valid NonIndexed completion mode": {
opts: JobValidationOptions{RequirePrefixedLabels: true}, opts: JobValidationOptions{RequirePrefixedLabels: true},
job: batch.Job{ job: batch.Job{
@ -1784,6 +1809,17 @@ func TestValidateCronJob(t *testing.T) {
validManualSelector := getValidManualSelector() validManualSelector := getValidManualSelector()
validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector()) validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
validPodTemplateSpec.Labels = map[string]string{} validPodTemplateSpec.Labels = map[string]string{}
validHostNetPodTemplateSpec := func() api.PodTemplateSpec {
spec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
spec.Spec.SecurityContext = &api.PodSecurityContext{
HostNetwork: true,
}
spec.Spec.Containers[0].Ports = []api.ContainerPort{{
ContainerPort: 12345,
Protocol: api.ProtocolTCP,
}}
return spec
}()
successCases := map[string]batch.CronJob{ successCases := map[string]batch.CronJob{
"basic scheduled job": { "basic scheduled job": {
@ -1802,6 +1838,22 @@ func TestValidateCronJob(t *testing.T) {
}, },
}, },
}, },
"hostnet job": {
ObjectMeta: metav1.ObjectMeta{
Name: "mycronjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.CronJobSpec{
Schedule: "* * * * ?",
ConcurrencyPolicy: batch.AllowConcurrent,
JobTemplate: batch.JobTemplateSpec{
Spec: batch.JobSpec{
Template: validHostNetPodTemplateSpec,
},
},
},
},
"non-standard scheduled": { "non-standard scheduled": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "mycronjob", Name: "mycronjob",

View File

@ -199,6 +199,11 @@ func SetDefaults_Pod(obj *v1.Pod) {
enableServiceLinks := v1.DefaultEnableServiceLinks enableServiceLinks := v1.DefaultEnableServiceLinks
obj.Spec.EnableServiceLinks = &enableServiceLinks obj.Spec.EnableServiceLinks = &enableServiceLinks
} }
if obj.Spec.HostNetwork {
defaultHostNetworkPorts(&obj.Spec.Containers)
defaultHostNetworkPorts(&obj.Spec.InitContainers)
}
} }
func SetDefaults_PodSpec(obj *v1.PodSpec) { func SetDefaults_PodSpec(obj *v1.PodSpec) {
// New fields added here will break upgrade tests: // New fields added here will break upgrade tests:
@ -211,9 +216,11 @@ func SetDefaults_PodSpec(obj *v1.PodSpec) {
if obj.RestartPolicy == "" { if obj.RestartPolicy == "" {
obj.RestartPolicy = v1.RestartPolicyAlways obj.RestartPolicy = v1.RestartPolicyAlways
} }
if obj.HostNetwork { if utilfeature.DefaultFeatureGate.Enabled(features.DefaultHostNetworkHostPortsInPodTemplates) {
defaultHostNetworkPorts(&obj.Containers) if obj.HostNetwork {
defaultHostNetworkPorts(&obj.InitContainers) defaultHostNetworkPorts(&obj.Containers)
defaultHostNetworkPorts(&obj.InitContainers)
}
} }
if obj.SecurityContext == nil { if obj.SecurityContext == nil {
obj.SecurityContext = &v1.PodSecurityContext{} obj.SecurityContext = &v1.PodSecurityContext{}

View File

@ -47,14 +47,12 @@ func TestWorkloadDefaults(t *testing.T) {
t.Run("disabled_features", func(t *testing.T) { testWorkloadDefaults(t, false) }) t.Run("disabled_features", func(t *testing.T) { testWorkloadDefaults(t, false) })
} }
func testWorkloadDefaults(t *testing.T, featuresEnabled bool) { func testWorkloadDefaults(t *testing.T, featuresEnabled bool) {
features := utilfeature.DefaultFeatureGate.DeepCopy().GetAll() allFeatures := utilfeature.DefaultFeatureGate.DeepCopy().GetAll()
for feature, featureSpec := range features { for feature, featureSpec := range allFeatures {
if !featureSpec.LockToDefault { if !featureSpec.LockToDefault {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, featuresEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, featuresEnabled)()
} }
} }
rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}}
template := rc.Spec.Template
// New defaults under PodTemplateSpec are only acceptable if they would not be applied when reading data from a previous release. // New defaults under PodTemplateSpec are only acceptable if they would not be applied when reading data from a previous release.
// Forbidden: adding a new field `MyField *bool` and defaulting it to a non-nil value // Forbidden: adding a new field `MyField *bool` and defaulting it to a non-nil value
// Forbidden: defaulting an existing field `MyField *bool` when it was previously not defaulted // Forbidden: defaulting an existing field `MyField *bool` when it was previously not defaulted
@ -177,11 +175,59 @@ func testWorkloadDefaults(t *testing.T, featuresEnabled bool) {
".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`, ".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`,
".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`, ".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`,
} }
defaults := detectDefaults(t, rc, reflect.ValueOf(template)) t.Run("empty PodTemplateSpec", func(t *testing.T) {
if !reflect.DeepEqual(expectedDefaults, defaults) { rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}}
t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.") template := rc.Spec.Template
t.Logf(cmp.Diff(expectedDefaults, defaults)) defaults := detectDefaults(t, rc, reflect.ValueOf(template))
} if !reflect.DeepEqual(expectedDefaults, defaults) {
t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.")
t.Logf(cmp.Diff(expectedDefaults, defaults))
}
})
t.Run("hostnet PodTemplateSpec with ports", func(t *testing.T) {
rc := &v1.ReplicationController{
Spec: v1.ReplicationControllerSpec{
Template: &v1.PodTemplateSpec{
Spec: v1.PodSpec{
HostNetwork: true,
Containers: []v1.Container{{
Ports: []v1.ContainerPort{{
ContainerPort: 12345,
Protocol: v1.ProtocolTCP,
}},
}},
},
},
},
}
template := rc.Spec.Template
defaults := detectDefaults(t, rc, reflect.ValueOf(template))
expected := func() map[string]string {
// Set values that are known inputs
m := map[string]string{
".Spec.HostNetwork": "true",
".Spec.Containers[0].Ports[0].ContainerPort": "12345",
}
if utilfeature.DefaultFeatureGate.Enabled(features.DefaultHostNetworkHostPortsInPodTemplates) {
m[".Spec.Containers"] = `[{"name":"","ports":[{"hostPort":12345,"containerPort":12345,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"IfNotPresent"}]`
m[".Spec.Containers[0].Ports"] = `[{"hostPort":12345,"containerPort":12345,"protocol":"TCP"}]`
m[".Spec.Containers[0].Ports[0].HostPort"] = "12345"
} else {
m[".Spec.Containers"] = `[{"name":"","ports":[{"containerPort":12345,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"IfNotPresent"}]`
m[".Spec.Containers[0].Ports"] = `[{"containerPort":12345,"protocol":"TCP"}]`
}
for k, v := range expectedDefaults {
if _, found := m[k]; !found {
m[k] = v
}
}
return m
}()
if !reflect.DeepEqual(expected, defaults) {
t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.")
t.Logf(cmp.Diff(expected, defaults))
}
})
} }
// TestPodDefaults detects changes to defaults within PodSpec. // TestPodDefaults detects changes to defaults within PodSpec.
@ -330,6 +376,78 @@ func testPodDefaults(t *testing.T, featuresEnabled bool) {
} }
} }
func TestPodHostNetworkDefaults(t *testing.T) {
cases := []struct {
name string
gate bool
hostNet bool
expectPodDefault bool
expectPodSpecDefault bool
}{{
name: "gate disabled, hostNetwork=false",
gate: false,
hostNet: false,
expectPodDefault: false,
expectPodSpecDefault: false,
}, {
name: "gate disabled, hostNetwork=true",
gate: false,
hostNet: true,
expectPodDefault: true,
expectPodSpecDefault: false,
}, {
name: "gate enabled, hostNetwork=false",
gate: true,
hostNet: false,
expectPodDefault: false,
expectPodSpecDefault: false,
}, {
name: "gate enabled, hostNetwork=true",
gate: true,
hostNet: true,
expectPodDefault: true,
expectPodSpecDefault: true,
}}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DefaultHostNetworkHostPortsInPodTemplates, tc.gate)()
const portNum = 12345
spec := v1.PodSpec{
HostNetwork: tc.hostNet,
Containers: []v1.Container{{
Ports: []v1.ContainerPort{{
ContainerPort: portNum,
Protocol: v1.ProtocolTCP,
// Note: HostPort is not set
}},
}},
}
// Test Pod defaulting.
p := v1.Pod{Spec: *spec.DeepCopy()}
corev1.SetDefaults_Pod(&p)
if got := p.Spec.Containers[0].Ports[0].HostPort; tc.expectPodDefault && got == 0 {
t.Errorf("expected Pod HostPort to be defaulted, got %v", got)
}
if got := p.Spec.Containers[0].Ports[0].HostPort; !tc.expectPodDefault && got != 0 {
t.Errorf("expected Pod HostPort to be 0, got %v", got)
}
// Test PodSpec defaulting.
s := spec.DeepCopy()
corev1.SetDefaults_PodSpec(s)
if got := s.Containers[0].Ports[0].HostPort; tc.expectPodSpecDefault && got == 0 {
t.Errorf("expected PodSpec HostPort to be defaulted, got %v", got)
}
if got := s.Containers[0].Ports[0].HostPort; !tc.expectPodSpecDefault && got != 0 {
t.Errorf("expected PodSpec HostPort to be 0, got %v", got)
}
})
}
}
type testPath struct { type testPath struct {
path string path string
value reflect.Value value reflect.Value
@ -359,9 +477,12 @@ func detectDefaults(t *testing.T, obj runtime.Object, v reflect.Value) map[strin
case visit.value.Kind() == reflect.Slice: case visit.value.Kind() == reflect.Slice:
if !visit.value.IsNil() { if !visit.value.IsNil() {
// if we already have a value, we got defaulted // if we already have a value, we either got defaulted or there
// was a fixed input - flag it and see if we can descend
// anyway.
marshaled, _ := json.Marshal(defaultedV.Interface()) marshaled, _ := json.Marshal(defaultedV.Interface())
defaults[visit.path] = string(marshaled) defaults[visit.path] = string(marshaled)
toVisit = append(toVisit, testPath{path: visit.path + "[0]", value: visit.value.Index(0)})
} else if visit.value.Type().Elem().Kind() == reflect.Struct { } else if visit.value.Type().Elem().Kind() == reflect.Struct {
if strings.HasPrefix(visit.path, ".ObjectMeta.ManagedFields[") { if strings.HasPrefix(visit.path, ".ObjectMeta.ManagedFields[") {
break break

View File

@ -3699,14 +3699,17 @@ type PodValidationOptions struct {
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set, // validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
// and is called by ValidatePodCreate and ValidatePodUpdate. // and is called by ValidatePodCreate and ValidatePodUpdate.
func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.ErrorList { func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
fldPath := field.NewPath("metadata") metaPath := field.NewPath("metadata")
allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) specPath := field.NewPath("spec")
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"), opts)...)
allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, &pod.ObjectMeta, field.NewPath("spec"), opts)...) allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, metaPath)
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, metaPath.Child("annotations"), opts)...)
allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, &pod.ObjectMeta, specPath, opts)...)
// we do additional validation only pertinent for pods and not pod templates // we do additional validation only pertinent for pods and not pod templates
// this was done to preserve backwards compatibility // this was done to preserve backwards compatibility
specPath := field.NewPath("spec")
allErrs = append(allErrs, validatePodSecurityContext(pod.Spec.SecurityContext, &pod.Spec, specPath, specPath.Child("securityContext"), opts)...)
if pod.Spec.ServiceAccountName == "" { if pod.Spec.ServiceAccountName == "" {
for vi, volume := range pod.Spec.Volumes { for vi, volume := range pod.Spec.Volumes {
@ -3793,7 +3796,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...) allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...) allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...) allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...) allErrs = append(allErrs, validatePodSpecSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...)
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...) allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...) allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...)
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...) allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
@ -4396,12 +4399,25 @@ func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList
return allErrs return allErrs
} }
// ValidatePodSecurityContext test that the specified PodSecurityContext has valid data. // validatePodSecurityContext verifies the SecurityContext of a Pod, but only
func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { // when it is a Pod and not an embedded PodSpec.
func validatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, scPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if securityContext != nil { if securityContext != nil {
allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...) allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...)
}
return allErrs
}
// validatePodSpecSecurityContext verifies the SecurityContext of a PodSpec,
// whether that is defined in a Pod or in an embedded PodSpec (e.g. a
// Deployment's pod template).
func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
if securityContext != nil {
if securityContext.FSGroup != nil { if securityContext.FSGroup != nil {
for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) { for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg)) allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg))

View File

@ -8895,7 +8895,6 @@ func TestValidatePodSpec(t *testing.T) {
"bad supplementalGroups large than math.MaxInt32": { "bad supplementalGroups large than math.MaxInt32": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false,
SupplementalGroups: []int64{maxGroupID, 1234}, SupplementalGroups: []int64{maxGroupID, 1234},
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
@ -8904,7 +8903,6 @@ func TestValidatePodSpec(t *testing.T) {
"bad supplementalGroups less than 0": { "bad supplementalGroups less than 0": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false,
SupplementalGroups: []int64{minGroupID, 1234}, SupplementalGroups: []int64{minGroupID, 1234},
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
@ -8913,8 +8911,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad runAsUser large than math.MaxInt32": { "bad runAsUser large than math.MaxInt32": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false, RunAsUser: &maxUserID,
RunAsUser: &maxUserID,
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
@ -8922,8 +8919,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad runAsUser less than 0": { "bad runAsUser less than 0": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false, RunAsUser: &minUserID,
RunAsUser: &minUserID,
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
@ -8931,8 +8927,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad fsGroup large than math.MaxInt32": { "bad fsGroup large than math.MaxInt32": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false, FSGroup: &maxGroupID,
FSGroup: &maxGroupID,
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
@ -8940,8 +8935,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad fsGroup less than 0": { "bad fsGroup less than 0": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostNetwork: false, FSGroup: &minGroupID,
FSGroup: &minGroupID,
}, },
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
@ -15381,6 +15375,30 @@ func TestValidateReplicationController(t *testing.T) {
}, },
}, },
} }
hostnetPodTemplate := core.PodTemplate{
Template: core.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
},
Spec: core.PodSpec{
SecurityContext: &core.PodSecurityContext{
HostNetwork: true,
},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Containers: []core.Container{{
Name: "abc",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
Ports: []core.ContainerPort{{
ContainerPort: 12345,
Protocol: core.ProtocolTCP,
}},
}},
},
},
}
invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := core.PodTemplate{ invalidPodTemplate := core.PodTemplate{
Template: core.PodTemplateSpec{ Template: core.PodTemplateSpec{
@ -15412,8 +15430,14 @@ func TestValidateReplicationController(t *testing.T) {
Selector: validSelector, Selector: validSelector,
Template: &readWriteVolumePodTemplate.Template, Template: &readWriteVolumePodTemplate.Template,
}, },
}, }, {
} ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
Spec: core.ReplicationControllerSpec{
Replicas: 1,
Selector: validSelector,
Template: &hostnetPodTemplate.Template,
},
}}
for _, successCase := range successCases { for _, successCase := range successCases {
if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 { if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)

View File

@ -212,6 +212,15 @@ const (
// Enables support for time zones in CronJobs. // Enables support for time zones in CronJobs.
CronJobTimeZone featuregate.Feature = "CronJobTimeZone" CronJobTimeZone featuregate.Feature = "CronJobTimeZone"
// owner: @thockin
// deprecated: v1.28
//
// Changes when the default value of PodSpec.containers[].ports[].hostPort
// is assigned. The default is to only set a default value in Pods.
// Enabling this means a default will be assigned even to embeddedPodSpecs
// (e.g. in a Deployment), which is the historical default.
DefaultHostNetworkHostPortsInPodTemplates featuregate.Feature = "DefaultHostNetworkHostPortsInPodTemplates"
// owner: @andrewsykim // owner: @andrewsykim
// alpha: v1.22 // alpha: v1.22
// //
@ -893,6 +902,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
CronJobTimeZone: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.29 CronJobTimeZone: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.29
DefaultHostNetworkHostPortsInPodTemplates: {Default: false, PreRelease: featuregate.Deprecated},
DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha}, DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha},
DisableKubeletCloudCredentialProviders: {Default: false, PreRelease: featuregate.Alpha}, DisableKubeletCloudCredentialProviders: {Default: false, PreRelease: featuregate.Alpha},