Sidecar: API changes
- Add SidecarContaienrs feature gate - Add ContainerRestartPolicy type - Add RestartPolicy field to the Container - Drop RestartPolicy field if the feature is disabled - Add validation for the SidecarContainers - Allow restartable init containaers to have a startup probe
This commit is contained in:
		 Gunju Kim
					Gunju Kim
				
			
				
					committed by
					
						 Sergey Kanzhelev
						Sergey Kanzhelev
					
				
			
			
				
	
			
			
			 Sergey Kanzhelev
						Sergey Kanzhelev
					
				
			
						parent
						
							c17601fa18
						
					
				
				
					commit
					5d26bcd468
				
			| @@ -510,6 +510,14 @@ func dropDisabledFields( | ||||
| 			podSpec.EphemeralContainers[i].ResizePolicy = nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) && !restartableInitContainersInUse(oldPodSpec) { | ||||
| 		// Drop the RestartPolicy field of init containers. | ||||
| 		for i := range podSpec.InitContainers { | ||||
| 			podSpec.InitContainers[i].RestartPolicy = nil | ||||
| 		} | ||||
| 		// For other types of containers, validateContainers will handle them. | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // dropDisabledPodStatusFields removes disabled fields from the pod status | ||||
| @@ -778,6 +786,23 @@ func schedulingGatesInUse(podSpec *api.PodSpec) bool { | ||||
| 	return len(podSpec.SchedulingGates) != 0 | ||||
| } | ||||
|  | ||||
| // restartableInitContainersInUse returns true if the pod spec is non-nil and | ||||
| // it has any init container with ContainerRestartPolicyAlways. | ||||
| func restartableInitContainersInUse(podSpec *api.PodSpec) bool { | ||||
| 	if podSpec == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	var inUse bool | ||||
| 	VisitContainers(podSpec, InitContainers, func(c *api.Container, containerType ContainerType) bool { | ||||
| 		if c.RestartPolicy != nil && *c.RestartPolicy == api.ContainerRestartPolicyAlways { | ||||
| 			inUse = true | ||||
| 			return false | ||||
| 		} | ||||
| 		return true | ||||
| 	}) | ||||
| 	return inUse | ||||
| } | ||||
|  | ||||
| func hasInvalidLabelValueInAffinitySelector(spec *api.PodSpec) bool { | ||||
| 	if spec.Affinity != nil { | ||||
| 		if spec.Affinity.PodAffinity != nil { | ||||
|   | ||||
| @@ -2052,6 +2052,109 @@ func TestDropInPlacePodVerticalScaling(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDropSidecarContainers(t *testing.T) { | ||||
| 	containerRestartPolicyAlways := api.ContainerRestartPolicyAlways | ||||
|  | ||||
| 	podWithSidecarContainers := func() *api.Pod { | ||||
| 		return &api.Pod{ | ||||
| 			Spec: api.PodSpec{ | ||||
| 				InitContainers: []api.Container{ | ||||
| 					{ | ||||
| 						Name:          "c1", | ||||
| 						Image:         "image", | ||||
| 						RestartPolicy: &containerRestartPolicyAlways, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	podWithoutSidecarContainers := func() *api.Pod { | ||||
| 		return &api.Pod{ | ||||
| 			Spec: api.PodSpec{ | ||||
| 				InitContainers: []api.Container{ | ||||
| 					{ | ||||
| 						Name:  "c1", | ||||
| 						Image: "image", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	podInfo := []struct { | ||||
| 		description         string | ||||
| 		hasSidecarContainer bool | ||||
| 		pod                 func() *api.Pod | ||||
| 	}{ | ||||
| 		{ | ||||
| 			description:         "has a sidecar container", | ||||
| 			hasSidecarContainer: true, | ||||
| 			pod:                 podWithSidecarContainers, | ||||
| 		}, | ||||
| 		{ | ||||
| 			description:         "does not have a sidecar container", | ||||
| 			hasSidecarContainer: false, | ||||
| 			pod:                 podWithoutSidecarContainers, | ||||
| 		}, | ||||
| 		{ | ||||
| 			description:         "is nil", | ||||
| 			hasSidecarContainer: false, | ||||
| 			pod:                 func() *api.Pod { return nil }, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, enabled := range []bool{true, false} { | ||||
| 		for _, oldPodInfo := range podInfo { | ||||
| 			for _, newPodInfo := range podInfo { | ||||
| 				oldPodHasSidecarContainer, oldPod := oldPodInfo.hasSidecarContainer, oldPodInfo.pod() | ||||
| 				newPodHasSidecarContainer, newPod := newPodInfo.hasSidecarContainer, newPodInfo.pod() | ||||
| 				if newPod == nil { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { | ||||
| 					defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SidecarContainers, enabled)() | ||||
|  | ||||
| 					var oldPodSpec *api.PodSpec | ||||
| 					if oldPod != nil { | ||||
| 						oldPodSpec = &oldPod.Spec | ||||
| 					} | ||||
| 					dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil) | ||||
|  | ||||
| 					// old pod should never be changed | ||||
| 					if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { | ||||
| 						t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod())) | ||||
| 					} | ||||
|  | ||||
| 					switch { | ||||
| 					case enabled || oldPodHasSidecarContainer: | ||||
| 						// new pod shouldn't change if feature enabled or if old pod has | ||||
| 						// any sidecar container | ||||
| 						if !reflect.DeepEqual(newPod, newPodInfo.pod()) { | ||||
| 							t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) | ||||
| 						} | ||||
| 					case newPodHasSidecarContainer: | ||||
| 						// new pod should be changed | ||||
| 						if reflect.DeepEqual(newPod, newPodInfo.pod()) { | ||||
| 							t.Errorf("new pod was not changed") | ||||
| 						} | ||||
| 						// new pod should not have any sidecar container | ||||
| 						if !reflect.DeepEqual(newPod, podWithoutSidecarContainers()) { | ||||
| 							t.Errorf("new pod has a sidecar container: %v", cmp.Diff(newPod, podWithoutSidecarContainers())) | ||||
| 						} | ||||
| 					default: | ||||
| 						// new pod should not need to be changed | ||||
| 						if !reflect.DeepEqual(newPod, newPodInfo.pod()) { | ||||
| 							t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod())) | ||||
| 						} | ||||
| 					} | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMarkPodProposedForResize(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc        string | ||||
|   | ||||
| @@ -2279,6 +2279,24 @@ type Container struct { | ||||
| 	// +featureGate=InPlacePodVerticalScaling | ||||
| 	// +optional | ||||
| 	ResizePolicy []ContainerResizePolicy | ||||
| 	// RestartPolicy defines the restart behavior of individual containers in a pod. | ||||
| 	// This field may only be set for init containers, and the only allowed value is "Always". | ||||
| 	// For non-init containers or when this field is not specified, | ||||
| 	// the restart behavior is defined by the Pod's restart policy and the container type. | ||||
| 	// Setting the RestartPolicy as "Always" for the init container will have the following effect: | ||||
| 	// this init container will be continually restarted on | ||||
| 	// exit until all regular containers have terminated. Once all regular | ||||
| 	// containers have completed, all init containers with restartPolicy "Always" | ||||
| 	// will be shut down. This lifecycle differs from normal init containers and | ||||
| 	// is often referred to as a "sidecar" container. Although this init | ||||
| 	// container still starts in the init container sequence, it does not wait | ||||
| 	// for the container to complete before proceeding to the next init | ||||
| 	// container. Instead, the next init container starts immediately after this | ||||
| 	// init container is started, or after any startupProbe has successfully | ||||
| 	// completed. | ||||
| 	// +featureGate=SidecarContainers | ||||
| 	// +optional | ||||
| 	RestartPolicy *ContainerRestartPolicy | ||||
| 	// +optional | ||||
| 	VolumeMounts []VolumeMount | ||||
| 	// volumeDevices is the list of block devices to be used by the container. | ||||
| @@ -2597,6 +2615,14 @@ const ( | ||||
| 	RestartPolicyNever     RestartPolicy = "Never" | ||||
| ) | ||||
|  | ||||
| // ContainerRestartPolicy is the restart policy for a single container. | ||||
| // This may only be set for init containers and only allowed value is "Always". | ||||
| type ContainerRestartPolicy string | ||||
|  | ||||
| const ( | ||||
| 	ContainerRestartPolicyAlways ContainerRestartPolicy = "Always" | ||||
| ) | ||||
|  | ||||
| // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object | ||||
|  | ||||
| // PodList is a list of Pods. | ||||
| @@ -3505,6 +3531,13 @@ type EphemeralContainerCommon struct { | ||||
| 	// +featureGate=InPlacePodVerticalScaling | ||||
| 	// +optional | ||||
| 	ResizePolicy []ContainerResizePolicy | ||||
| 	// Restart policy for the container to manage the restart behavior of each | ||||
| 	// container within a pod. | ||||
| 	// This may only be set for init containers. You cannot set this field on | ||||
| 	// ephemeral containers. | ||||
| 	// +featureGate=SidecarContainers | ||||
| 	// +optional | ||||
| 	RestartPolicy *ContainerRestartPolicy | ||||
| 	// Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. | ||||
| 	// +optional | ||||
| 	VolumeMounts []VolumeMount | ||||
|   | ||||
| @@ -2835,6 +2835,23 @@ func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList { | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func validateInitContainerRestartPolicy(restartPolicy *core.ContainerRestartPolicy, fldPath *field.Path) field.ErrorList { | ||||
| 	var allErrors field.ErrorList | ||||
|  | ||||
| 	if restartPolicy == nil { | ||||
| 		return allErrors | ||||
| 	} | ||||
| 	switch *restartPolicy { | ||||
| 	case core.ContainerRestartPolicyAlways: | ||||
| 		break | ||||
| 	default: | ||||
| 		validValues := []string{string(core.ContainerRestartPolicyAlways)} | ||||
| 		allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues)) | ||||
| 	} | ||||
|  | ||||
| 	return allErrors | ||||
| } | ||||
|  | ||||
| type commonHandler struct { | ||||
| 	Exec      *core.ExecAction | ||||
| 	HTTPGet   *core.HTTPGetAction | ||||
| @@ -3165,6 +3182,13 @@ func validateInitContainers(containers []core.Container, regularContainers []cor | ||||
| 		// Apply the validation common to all container types | ||||
| 		allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, idxPath, opts)...) | ||||
|  | ||||
| 		restartAlways := false | ||||
| 		// Apply the validation specific to init containers | ||||
| 		if ctr.RestartPolicy != nil { | ||||
| 			allErrs = append(allErrs, validateInitContainerRestartPolicy(ctr.RestartPolicy, idxPath.Child("restartPolicy"))...) | ||||
| 			restartAlways = *ctr.RestartPolicy == core.ContainerRestartPolicyAlways | ||||
| 		} | ||||
|  | ||||
| 		// Names must be unique within regular and init containers. Collisions with ephemeral containers | ||||
| 		// will be detected by validateEphemeralContainers(). | ||||
| 		if allNames.Has(ctr.Name) { | ||||
| @@ -3176,6 +3200,26 @@ func validateInitContainers(containers []core.Container, regularContainers []cor | ||||
| 		// Check for port conflicts in init containers individually since init containers run one-by-one. | ||||
| 		allErrs = append(allErrs, checkHostPortConflicts([]core.Container{ctr}, fldPath)...) | ||||
|  | ||||
| 		switch { | ||||
| 		case restartAlways: | ||||
| 			// TODO: Allow restartable init containers to have a lifecycle hook. | ||||
| 			if ctr.Lifecycle != nil { | ||||
| 				allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers")) | ||||
| 			} | ||||
| 			// TODO: Allow restartable init containers to have a liveness probe. | ||||
| 			if ctr.LivenessProbe != nil { | ||||
| 				allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers")) | ||||
| 			} | ||||
| 			// TODO: Allow restartable init containers to have a readiness probe. | ||||
| 			if ctr.ReadinessProbe != nil { | ||||
| 				allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers")) | ||||
| 			} | ||||
| 			allErrs = append(allErrs, validateProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...) | ||||
| 			if ctr.StartupProbe != nil && ctr.StartupProbe.SuccessThreshold != 1 { | ||||
| 				allErrs = append(allErrs, field.Invalid(idxPath.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1")) | ||||
| 			} | ||||
|  | ||||
| 		default: | ||||
| 			// These fields are disallowed for init containers. | ||||
| 			if ctr.Lifecycle != nil { | ||||
| 				allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers")) | ||||
| @@ -3189,6 +3233,8 @@ func validateInitContainers(containers []core.Container, regularContainers []cor | ||||
| 			if ctr.StartupProbe != nil { | ||||
| 				allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers")) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(ctr.ResizePolicy) > 0 { | ||||
| 			allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers")) | ||||
| 		} | ||||
| @@ -3311,6 +3357,11 @@ func validateContainers(containers []core.Container, volumes map[string]core.Vol | ||||
| 		if ctr.StartupProbe != nil && ctr.StartupProbe.SuccessThreshold != 1 { | ||||
| 			allErrs = append(allErrs, field.Invalid(path.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1")) | ||||
| 		} | ||||
|  | ||||
| 		// These fields are disallowed for regular containers | ||||
| 		if ctr.RestartPolicy != nil { | ||||
| 			allErrs = append(allErrs, field.Forbidden(path.Child("restartPolicy"), "may not be set for non-init containers")) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Port conflicts are checked across all containers | ||||
|   | ||||
| @@ -54,6 +54,14 @@ const ( | ||||
| 	envVarNameErrMsg        = "a valid environment variable name must consist of" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	containerRestartPolicyAlways    = core.ContainerRestartPolicyAlways | ||||
| 	containerRestartPolicyOnFailure = core.ContainerRestartPolicy("OnFailure") | ||||
| 	containerRestartPolicyNever     = core.ContainerRestartPolicy("Never") | ||||
| 	containerRestartPolicyInvalid   = core.ContainerRestartPolicy("invalid") | ||||
| 	containerRestartPolicyEmpty     = core.ContainerRestartPolicy("") | ||||
| ) | ||||
|  | ||||
| type topologyPair struct { | ||||
| 	key   string | ||||
| 	value string | ||||
| @@ -7129,6 +7137,71 @@ func TestValidateEphemeralContainers(t *testing.T) { | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: Always", | ||||
| 		line(), | ||||
| 		[]core.EphemeralContainer{{ | ||||
| 			EphemeralContainerCommon: core.EphemeralContainerCommon{ | ||||
| 				Name:                     "foo", | ||||
| 				Image:                    "image", | ||||
| 				ImagePullPolicy:          "IfNotPresent", | ||||
| 				TerminationMessagePolicy: "File", | ||||
| 				RestartPolicy:            &containerRestartPolicyAlways, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: OnFailure", | ||||
| 		line(), | ||||
| 		[]core.EphemeralContainer{{ | ||||
| 			EphemeralContainerCommon: core.EphemeralContainerCommon{ | ||||
| 				Name:                     "foo", | ||||
| 				Image:                    "image", | ||||
| 				ImagePullPolicy:          "IfNotPresent", | ||||
| 				TerminationMessagePolicy: "File", | ||||
| 				RestartPolicy:            &containerRestartPolicyOnFailure, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: Never", | ||||
| 		line(), | ||||
| 		[]core.EphemeralContainer{{ | ||||
| 			EphemeralContainerCommon: core.EphemeralContainerCommon{ | ||||
| 				Name:                     "foo", | ||||
| 				Image:                    "image", | ||||
| 				ImagePullPolicy:          "IfNotPresent", | ||||
| 				TerminationMessagePolicy: "File", | ||||
| 				RestartPolicy:            &containerRestartPolicyNever, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: invalid", | ||||
| 		line(), | ||||
| 		[]core.EphemeralContainer{{ | ||||
| 			EphemeralContainerCommon: core.EphemeralContainerCommon{ | ||||
| 				Name:                     "foo", | ||||
| 				Image:                    "image", | ||||
| 				ImagePullPolicy:          "IfNotPresent", | ||||
| 				TerminationMessagePolicy: "File", | ||||
| 				RestartPolicy:            &containerRestartPolicyInvalid, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: empty", | ||||
| 		line(), | ||||
| 		[]core.EphemeralContainer{{ | ||||
| 			EphemeralContainerCommon: core.EphemeralContainerCommon{ | ||||
| 				Name:                     "foo", | ||||
| 				Image:                    "image", | ||||
| 				ImagePullPolicy:          "IfNotPresent", | ||||
| 				TerminationMessagePolicy: "File", | ||||
| 				RestartPolicy:            &containerRestartPolicyEmpty, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, | ||||
| 	}, | ||||
| 	} | ||||
|  | ||||
| @@ -7986,6 +8059,61 @@ func TestValidateContainers(t *testing.T) { | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: Always", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "foo", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyAlways, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: OnFailure", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "foo", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyOnFailure, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: Never", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "foo", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyNever, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: invalid", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "foo", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyInvalid, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, | ||||
| 	}, { | ||||
| 		"Forbidden RestartPolicy: empty", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "foo", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyEmpty, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, | ||||
| 	}, | ||||
| 	} | ||||
| 	for _, tc := range errorCases { | ||||
| @@ -8035,6 +8163,18 @@ func TestValidateInitContainers(t *testing.T) { | ||||
| 		}, | ||||
| 		ImagePullPolicy:          "IfNotPresent", | ||||
| 		TerminationMessagePolicy: "File", | ||||
| 	}, { | ||||
| 		Name:                     "container-3-restart-always-with-startup-probe", | ||||
| 		Image:                    "image", | ||||
| 		ImagePullPolicy:          "IfNotPresent", | ||||
| 		TerminationMessagePolicy: "File", | ||||
| 		RestartPolicy:            &containerRestartPolicyAlways, | ||||
| 		StartupProbe: &core.Probe{ | ||||
| 			ProbeHandler: core.ProbeHandler{ | ||||
| 				TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)}, | ||||
| 			}, | ||||
| 			SuccessThreshold: 1, | ||||
| 		}, | ||||
| 	}, | ||||
| 	} | ||||
| 	if errs := validateInitContainers(successCase, containers, volumeDevices, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { | ||||
| @@ -8191,6 +8331,67 @@ func TestValidateInitContainers(t *testing.T) { | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, | ||||
| 	}, { | ||||
| 		"Not supported RestartPolicy: OnFailure", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "init", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyOnFailure, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}}, | ||||
| 	}, { | ||||
| 		"Not supported RestartPolicy: Never", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "init", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyNever, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}}, | ||||
| 	}, { | ||||
| 		"Not supported RestartPolicy: invalid", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "init", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyInvalid, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}}, | ||||
| 	}, { | ||||
| 		"Not supported RestartPolicy: empty", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "init", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyEmpty, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}}, | ||||
| 	}, { | ||||
| 		"invalid startup probe in restartable container, successThreshold != 1", | ||||
| 		line(), | ||||
| 		[]core.Container{{ | ||||
| 			Name:                     "restartable-init", | ||||
| 			Image:                    "image", | ||||
| 			ImagePullPolicy:          "IfNotPresent", | ||||
| 			TerminationMessagePolicy: "File", | ||||
| 			RestartPolicy:            &containerRestartPolicyAlways, | ||||
| 			StartupProbe: &core.Probe{ | ||||
| 				ProbeHandler: core.ProbeHandler{ | ||||
| 					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)}, | ||||
| 				}, | ||||
| 				SuccessThreshold: 2, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}}, | ||||
| 	}, | ||||
| 	} | ||||
| 	for _, tc := range errorCases { | ||||
| @@ -19323,6 +19524,7 @@ func TestValidateOSFields(t *testing.T) { | ||||
| 		"Containers[*].Resources", | ||||
| 		"Containers[*].ResizePolicy[*].RestartPolicy", | ||||
| 		"Containers[*].ResizePolicy[*].ResourceName", | ||||
| 		"Containers[*].RestartPolicy", | ||||
| 		"Containers[*].SecurityContext.RunAsNonRoot", | ||||
| 		"Containers[*].Stdin", | ||||
| 		"Containers[*].StdinOnce", | ||||
| @@ -19349,6 +19551,7 @@ func TestValidateOSFields(t *testing.T) { | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.Resources", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.Stdin", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.StdinOnce", | ||||
| 		"EphemeralContainers[*].EphemeralContainerCommon.TTY", | ||||
| @@ -19377,6 +19580,7 @@ func TestValidateOSFields(t *testing.T) { | ||||
| 		"InitContainers[*].Resources", | ||||
| 		"InitContainers[*].ResizePolicy[*].RestartPolicy", | ||||
| 		"InitContainers[*].ResizePolicy[*].ResourceName", | ||||
| 		"InitContainers[*].RestartPolicy", | ||||
| 		"InitContainers[*].Stdin", | ||||
| 		"InitContainers[*].StdinOnce", | ||||
| 		"InitContainers[*].TTY", | ||||
|   | ||||
| @@ -714,6 +714,15 @@ const ( | ||||
| 	// Subdivide the NodePort range for dynamic and static port allocation. | ||||
| 	ServiceNodePortStaticSubrange featuregate.Feature = "ServiceNodePortStaticSubrange" | ||||
|  | ||||
| 	// owner: @gjkim42 @SergeyKanzhelev @matthyx @tzneal | ||||
| 	// kep: http://kep.k8s.io/753 | ||||
| 	// alpha: v1.28 | ||||
| 	// | ||||
| 	// Introduces sidecar containers, a new type of init container that starts | ||||
| 	// before other containers but remains running for the full duration of the | ||||
| 	// pod's lifecycle and will not block pod termination. | ||||
| 	SidecarContainers featuregate.Feature = "SidecarContainers" | ||||
|  | ||||
| 	// owner: @derekwaynecarr | ||||
| 	// alpha: v1.20 | ||||
| 	// beta: v1.22 | ||||
| @@ -1037,6 +1046,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS | ||||
|  | ||||
| 	ServiceNodePortStaticSubrange: {Default: true, PreRelease: featuregate.Beta}, | ||||
|  | ||||
| 	SidecarContainers: {Default: false, PreRelease: featuregate.Alpha}, | ||||
|  | ||||
| 	SizeMemoryBackedVolumes: {Default: true, PreRelease: featuregate.Beta}, | ||||
|  | ||||
| 	StableLoadBalancerNodeSet: {Default: true, PreRelease: featuregate.Beta}, | ||||
|   | ||||
| @@ -2446,6 +2446,24 @@ type Container struct { | ||||
| 	// +optional | ||||
| 	// +listType=atomic | ||||
| 	ResizePolicy []ContainerResizePolicy `json:"resizePolicy,omitempty" protobuf:"bytes,23,rep,name=resizePolicy"` | ||||
| 	// RestartPolicy defines the restart behavior of individual containers in a pod. | ||||
| 	// This field may only be set for init containers, and the only allowed value is "Always". | ||||
| 	// For non-init containers or when this field is not specified, | ||||
| 	// the restart behavior is defined by the Pod's restart policy and the container type. | ||||
| 	// Setting the RestartPolicy as "Always" for the init container will have the following effect: | ||||
| 	// this init container will be continually restarted on | ||||
| 	// exit until all regular containers have terminated. Once all regular | ||||
| 	// containers have completed, all init containers with restartPolicy "Always" | ||||
| 	// will be shut down. This lifecycle differs from normal init containers and | ||||
| 	// is often referred to as a "sidecar" container. Although this init | ||||
| 	// container still starts in the init container sequence, it does not wait | ||||
| 	// for the container to complete before proceeding to the next init | ||||
| 	// container. Instead, the next init container starts immediately after this | ||||
| 	// init container is started, or after any startupProbe has successfully | ||||
| 	// completed. | ||||
| 	// +featureGate=SidecarContainers | ||||
| 	// +optional | ||||
| 	RestartPolicy *ContainerRestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,24,opt,name=restartPolicy,casttype=ContainerRestartPolicy"` | ||||
| 	// Pod volumes to mount into the container's filesystem. | ||||
| 	// Cannot be updated. | ||||
| 	// +optional | ||||
| @@ -2842,6 +2860,14 @@ const ( | ||||
| 	RestartPolicyNever     RestartPolicy = "Never" | ||||
| ) | ||||
|  | ||||
| // ContainerRestartPolicy is the restart policy for a single container. | ||||
| // This may only be set for init containers and only allowed value is "Always". | ||||
| type ContainerRestartPolicy string | ||||
|  | ||||
| const ( | ||||
| 	ContainerRestartPolicyAlways ContainerRestartPolicy = "Always" | ||||
| ) | ||||
|  | ||||
| // DNSPolicy defines how a pod's DNS will be configured. | ||||
| // +enum | ||||
| type DNSPolicy string | ||||
| @@ -3976,6 +4002,13 @@ type EphemeralContainerCommon struct { | ||||
| 	// +optional | ||||
| 	// +listType=atomic | ||||
| 	ResizePolicy []ContainerResizePolicy `json:"resizePolicy,omitempty" protobuf:"bytes,23,rep,name=resizePolicy"` | ||||
| 	// Restart policy for the container to manage the restart behavior of each | ||||
| 	// container within a pod. | ||||
| 	// This may only be set for init containers. You cannot set this field on | ||||
| 	// ephemeral containers. | ||||
| 	// +featureGate=SidecarContainers | ||||
| 	// +optional | ||||
| 	RestartPolicy *ContainerRestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,24,opt,name=restartPolicy,casttype=ContainerRestartPolicy"` | ||||
| 	// Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. | ||||
| 	// Cannot be updated. | ||||
| 	// +optional | ||||
|   | ||||
		Reference in New Issue
	
	Block a user