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,10 +216,12 @@ func SetDefaults_PodSpec(obj *v1.PodSpec) {
if obj.RestartPolicy == "" { if obj.RestartPolicy == "" {
obj.RestartPolicy = v1.RestartPolicyAlways obj.RestartPolicy = v1.RestartPolicyAlways
} }
if utilfeature.DefaultFeatureGate.Enabled(features.DefaultHostNetworkHostPortsInPodTemplates) {
if obj.HostNetwork { if obj.HostNetwork {
defaultHostNetworkPorts(&obj.Containers) defaultHostNetworkPorts(&obj.Containers)
defaultHostNetworkPorts(&obj.InitContainers) 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`,
} }
t.Run("empty PodTemplateSpec", func(t *testing.T) {
rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}}
template := rc.Spec.Template
defaults := detectDefaults(t, rc, reflect.ValueOf(template)) defaults := detectDefaults(t, rc, reflect.ValueOf(template))
if !reflect.DeepEqual(expectedDefaults, defaults) { if !reflect.DeepEqual(expectedDefaults, defaults) {
t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.") t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.")
t.Logf(cmp.Diff(expectedDefaults, defaults)) 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,7 +8911,6 @@ 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,
@ -8922,7 +8919,6 @@ 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,
@ -8931,7 +8927,6 @@ 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,
@ -8940,7 +8935,6 @@ 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,
@ -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},