Merge pull request #117696 from thockin/hostnet_hostport_default_pod_not_podspec

Do hostNet Pod-ports -> hostPorts in Pod defaults
This commit is contained in:
Kubernetes Prow Robot 2023-05-09 22:53:00 -07:00 committed by GitHub
commit 21afcd453a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 410 additions and 42 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

@ -3481,15 +3481,35 @@ func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolic
return allErrs return allErrs
} }
func validateHostNetwork(hostNetwork bool, containers []core.Container, fldPath *field.Path) field.ErrorList { // validatePodHostNetworkDeps checks fields which depend on whether HostNetwork is
// true or not. It should be called on all PodSpecs, but opts can change what
// is enforce. E.g. opts.ResourceIsPod should only be set when called in the
// context of a Pod, and not on PodSpecs which are embedded in other resources
// (e.g. Deployments).
func validatePodHostNetworkDeps(spec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
// For <reasons> we keep `.HostNetwork` in .SecurityContext on the internal
// version of Pod.
hostNetwork := false
if spec.SecurityContext != nil {
hostNetwork = spec.SecurityContext.HostNetwork
}
allErrors := field.ErrorList{} allErrors := field.ErrorList{}
if hostNetwork { if hostNetwork {
for i, container := range containers { fldPath := fldPath.Child("containers")
for i, container := range spec.Containers {
portsPath := fldPath.Index(i).Child("ports") portsPath := fldPath.Index(i).Child("ports")
for i, port := range container.Ports { for i, port := range container.Ports {
idxPath := portsPath.Index(i) idxPath := portsPath.Index(i)
if port.HostPort != port.ContainerPort { // At this point, we know that HostNetwork is true. If this
allErrors = append(allErrors, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, "must match `hostPort` when `hostNetwork` is true")) // PodSpec is in a Pod (opts.ResourceIsPod), then HostPort must
// be the same value as ContainerPort. If this PodSpec is in
// some other resource (e.g. Deployment) we allow 0 (i.e.
// unspecified) because it will be defaulted when the Pod is
// ultimately created, but we do not allow any other values.
if hp, cp := port.HostPort, port.ContainerPort; (opts.ResourceIsPod || hp != 0) && hp != cp {
allErrors = append(allErrors, field.Invalid(idxPath.Child("hostPort"), port.HostPort, "must match `containerPort` when `hostNetwork` is true"))
} }
} }
} }
@ -3694,19 +3714,23 @@ type PodValidationOptions struct {
AllowInvalidTopologySpreadConstraintLabelSelector bool AllowInvalidTopologySpreadConstraintLabelSelector bool
// Allow node selector additions for gated pods. // Allow node selector additions for gated pods.
AllowMutableNodeSelectorAndNodeAffinity bool AllowMutableNodeSelectorAndNodeAffinity bool
// The top-level resource being validated is a Pod, not just a PodSpec
// embedded in some other resource.
ResourceIsPod bool
} }
// 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")
if pod.Spec.ServiceAccountName == "" { if pod.Spec.ServiceAccountName == "" {
for vi, volume := range pod.Spec.Volumes { for vi, volume := range pod.Spec.Volumes {
@ -3790,10 +3814,11 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
allErrs = append(allErrs, validateContainers(spec.Containers, vols, podClaimNames, fldPath.Child("containers"), opts)...) allErrs = append(allErrs, validateContainers(spec.Containers, vols, podClaimNames, fldPath.Child("containers"), opts)...)
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, podClaimNames, fldPath.Child("initContainers"), opts)...) allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, podClaimNames, fldPath.Child("initContainers"), opts)...)
allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, podClaimNames, fldPath.Child("ephemeralContainers"), opts)...) allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, podClaimNames, fldPath.Child("ephemeralContainers"), opts)...)
allErrs = append(allErrs, validatePodHostNetworkDeps(spec, fldPath, opts)...)
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 +4421,13 @@ func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList
return allErrs return allErrs
} }
// ValidatePodSecurityContext test that the specified PodSecurityContext has valid data. // validatePodSpecSecurityContext verifies the SecurityContext of a PodSpec,
func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { // 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{} allErrs := field.ErrorList{}
if securityContext != nil { if securityContext != nil {
allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...)
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

@ -8816,7 +8816,10 @@ func TestValidatePodSpec(t *testing.T) {
} }
for k, v := range successCases { for k, v := range successCases {
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { opts := PodValidationOptions{
ResourceIsPod: true,
}
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
}) })
@ -8868,6 +8871,18 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
}, },
"with hostNetwork hostPort unspecified": {
Containers: []core.Container{
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
{HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}},
},
},
SecurityContext: &core.PodSecurityContext{
HostNetwork: true,
},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
"with hostNetwork hostPort not equal to containerPort": { "with hostNetwork hostPort not equal to containerPort": {
Containers: []core.Container{ Containers: []core.Container{
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{ {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
@ -8895,7 +8910,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 +8918,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 +8926,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 +8934,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 +8942,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 +8950,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,
@ -9042,7 +9051,10 @@ func TestValidatePodSpec(t *testing.T) {
}, },
} }
for k, v := range failureCases { for k, v := range failureCases {
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 { opts := PodValidationOptions{
ResourceIsPod: true,
}
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 {
t.Errorf("expected failure for %q", k) t.Errorf("expected failure for %q", k)
} }
} }
@ -15381,6 +15393,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 +15448,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},

View File

@ -112,6 +112,7 @@ func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, nil, &pod.ObjectMeta, nil) opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, nil, &pod.ObjectMeta, nil)
opts.ResourceIsPod = true
return corevalidation.ValidatePodCreate(pod, opts) return corevalidation.ValidatePodCreate(pod, opts)
} }
@ -141,6 +142,7 @@ func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object)
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
oldPod := old.(*api.Pod) oldPod := old.(*api.Pod)
opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta) opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta)
opts.ResourceIsPod = true
return corevalidation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts) return corevalidation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts)
} }
@ -225,6 +227,7 @@ func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
oldPod := old.(*api.Pod) oldPod := old.(*api.Pod)
opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta) opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta)
opts.ResourceIsPod = true
return corevalidation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod), opts) return corevalidation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod), opts)
} }
@ -264,6 +267,7 @@ func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, o
newPod := obj.(*api.Pod) newPod := obj.(*api.Pod)
oldPod := old.(*api.Pod) oldPod := old.(*api.Pod)
opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&newPod.Spec, &oldPod.Spec, &newPod.ObjectMeta, &oldPod.ObjectMeta) opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&newPod.Spec, &oldPod.Spec, &newPod.ObjectMeta, &oldPod.ObjectMeta)
opts.ResourceIsPod = true
return corevalidation.ValidatePodEphemeralContainersUpdate(newPod, oldPod, opts) return corevalidation.ValidatePodEphemeralContainersUpdate(newPod, oldPod, opts)
} }