Merge pull request #113274 from Huang-Wei/kep-3521-A

[KEP-3521] Part 1: New Pod API .spec.schedulingGates
This commit is contained in:
Kubernetes Prow Robot 2022-11-03 21:24:25 -07:00 committed by GitHub
commit 8c77820759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 2314 additions and 1017 deletions

View File

@ -7248,6 +7248,19 @@
],
"type": "object"
},
"io.k8s.api.core.v1.PodSchedulingGate": {
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
"properties": {
"name": {
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
@ -7452,6 +7465,19 @@
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
"type": "string"
},
"schedulingGates": {
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.PodSchedulingGate"
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"securityContext": {
"$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext",
"description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field."

View File

@ -4869,6 +4869,20 @@
],
"type": "object"
},
"io.k8s.api.core.v1.PodSchedulingGate": {
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
"properties": {
"name": {
"default": "",
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
@ -5139,6 +5153,24 @@
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
"type": "string"
},
"schedulingGates": {
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
}
],
"default": {}
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"securityContext": {
"allOf": [
{

View File

@ -3291,6 +3291,20 @@
],
"type": "object"
},
"io.k8s.api.core.v1.PodSchedulingGate": {
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
"properties": {
"name": {
"default": "",
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
@ -3561,6 +3575,24 @@
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
"type": "string"
},
"schedulingGates": {
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
}
],
"default": {}
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"securityContext": {
"allOf": [
{

View File

@ -2485,6 +2485,20 @@
],
"type": "object"
},
"io.k8s.api.core.v1.PodSchedulingGate": {
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
"properties": {
"name": {
"default": "",
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
@ -2755,6 +2769,24 @@
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
"type": "string"
},
"schedulingGates": {
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
}
],
"default": {}
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"securityContext": {
"allOf": [
{

View File

@ -537,6 +537,11 @@ func dropDisabledFields(
}
}
// If the feature is disabled and not in use, drop the schedulingGates field.
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && !schedulingGatesInUse(oldPodSpec) {
podSpec.SchedulingGates = nil
}
dropDisabledProcMountField(podSpec, oldPodSpec)
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
@ -719,6 +724,14 @@ func probeGracePeriodInUse(podSpec *api.PodSpec) bool {
return inUse
}
// schedulingGatesInUse returns true if the pod spec is non-nil and it has SchedulingGates field set.
func schedulingGatesInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
return len(podSpec.SchedulingGates) != 0
}
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
// converted annotation value
func SeccompAnnotationForField(field *api.SeccompProfile) string {

View File

@ -1935,3 +1935,85 @@ func TestDropHostUsers(t *testing.T) {
}
}
func TestDropSchedulingGates(t *testing.T) {
podWithSchedulingGates := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
SchedulingGates: []api.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
},
},
}
}
podWithoutSchedulingGates := func() *api.Pod { return &api.Pod{} }
podInfo := []struct {
description string
hasSchedulingGatesField bool
pod func() *api.Pod
}{
{
description: "has SchedulingGates field",
hasSchedulingGatesField: true,
pod: podWithSchedulingGates,
},
{
description: "does not have SchedulingGates field",
hasSchedulingGatesField: false,
pod: podWithoutSchedulingGates,
},
{
description: "is nil",
hasSchedulingGatesField: false,
pod: func() *api.Pod { return nil },
},
}
for _, enabled := range []bool{true, false} {
for _, oldPodInfo := range podInfo {
for _, newPodInfo := range podInfo {
oldPodHasSchedulingGates, oldPod := oldPodInfo.hasSchedulingGatesField, oldPodInfo.pod()
newPodHasSchedulingGates, newPod := newPodInfo.hasSchedulingGatesField, 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.PodSchedulingReadiness, enabled)()
var oldPodSpec *api.PodSpec
if oldPod != nil {
oldPodSpec = &oldPod.Spec
}
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
// Old Pod should never be changed.
if diff := cmp.Diff(oldPod, oldPodInfo.pod()); diff != "" {
t.Errorf("old pod changed: %v", diff)
}
switch {
case enabled || oldPodHasSchedulingGates:
// New Pod should not be changed if the feature is enabled, or if the old Pod had schedulingGates.
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
t.Errorf("new pod changed: %v", diff)
}
case newPodHasSchedulingGates:
// New Pod should be changed.
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod was not changed")
}
// New Pod should not have SchedulingGates field.
if diff := cmp.Diff(newPod, podWithoutSchedulingGates()); diff != "" {
t.Errorf("new pod has SchedulingGates field: %v", diff)
}
default:
// New pod should not need to be changed.
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
t.Errorf("new pod changed: %v", diff)
}
}
})
}
}
}
}

View File

@ -2428,6 +2428,9 @@ const (
// PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
PodReasonUnschedulable = "Unschedulable"
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
// skips scheduling the pod because one or more scheduling gates are still present.
PodReasonSchedulingGated = "SchedulingGated"
// ContainersReady indicates whether all containers in the pod are ready.
ContainersReady PodConditionType = "ContainersReady"
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
@ -2997,6 +3000,12 @@ type PodSpec struct {
// - spec.containers[*].securityContext.runAsGroup
// +optional
OS *PodOS
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
//
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
// +optional
SchedulingGates []PodSchedulingGate
}
// OSName is the set of OS'es that can be used in OS.
@ -3017,6 +3026,13 @@ type PodOS struct {
Name OSName
}
// PodSchedulingGate is associated to a Pod to guard its scheduling.
type PodSchedulingGate struct {
// Name of the scheduling gate.
// Each scheduling gate must have a unique name field.
Name string
}
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
// pod's hosts file.
type HostAlias struct {

View File

@ -1352,6 +1352,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.PodSchedulingGate)(nil), (*core.PodSchedulingGate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate(a.(*v1.PodSchedulingGate), b.(*core.PodSchedulingGate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.PodSchedulingGate)(nil), (*v1.PodSchedulingGate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate(a.(*core.PodSchedulingGate), b.(*v1.PodSchedulingGate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.PodSecurityContext)(nil), (*core.PodSecurityContext)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_PodSecurityContext_To_core_PodSecurityContext(a.(*v1.PodSecurityContext), b.(*core.PodSecurityContext), scope)
}); err != nil {
@ -6069,6 +6079,26 @@ func Convert_core_PodReadinessGate_To_v1_PodReadinessGate(in *core.PodReadinessG
return autoConvert_core_PodReadinessGate_To_v1_PodReadinessGate(in, out, s)
}
func autoConvert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in *v1.PodSchedulingGate, out *core.PodSchedulingGate, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate is an autogenerated conversion function.
func Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in *v1.PodSchedulingGate, out *core.PodSchedulingGate, s conversion.Scope) error {
return autoConvert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in, out, s)
}
func autoConvert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in *core.PodSchedulingGate, out *v1.PodSchedulingGate, s conversion.Scope) error {
out.Name = in.Name
return nil
}
// Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate is an autogenerated conversion function.
func Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in *core.PodSchedulingGate, out *v1.PodSchedulingGate, s conversion.Scope) error {
return autoConvert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in, out, s)
}
func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error {
out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions))
out.WindowsOptions = (*core.WindowsSecurityContextOptions)(unsafe.Pointer(in.WindowsOptions))
@ -6188,6 +6218,7 @@ func autoConvert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s
out.SetHostnameAsFQDN = (*bool)(unsafe.Pointer(in.SetHostnameAsFQDN))
out.OS = (*core.PodOS)(unsafe.Pointer(in.OS))
// INFO: in.HostUsers opted out of conversion generation
out.SchedulingGates = *(*[]core.PodSchedulingGate)(unsafe.Pointer(&in.SchedulingGates))
return nil
}
@ -6241,6 +6272,7 @@ func autoConvert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s
out.EnableServiceLinks = (*bool)(unsafe.Pointer(in.EnableServiceLinks))
out.TopologySpreadConstraints = *(*[]v1.TopologySpreadConstraint)(unsafe.Pointer(&in.TopologySpreadConstraints))
out.OS = (*v1.PodOS)(unsafe.Pointer(in.OS))
out.SchedulingGates = *(*[]v1.PodSchedulingGate)(unsafe.Pointer(&in.SchedulingGates))
return nil
}

View File

@ -3272,6 +3272,22 @@ func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *fie
return allErrs
}
func validateSchedulingGates(schedulingGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// There should be no duplicates in the list of scheduling gates.
seen := sets.String{}
for i, schedulingGate := range schedulingGates {
if schedulingGate.Name == "" {
allErrs = append(allErrs, field.Required(fldPath.Index(i), "must not be empty"))
}
if seen.Has(schedulingGate.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), schedulingGate.Name))
}
seen.Insert(schedulingGate.Name)
}
return allErrs
}
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
@ -3420,6 +3436,28 @@ func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldToleratio
return allErrs
}
func validateOnlyDeletedSchedulingGates(newGates, oldGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(newGates) == 0 {
return allErrs
}
additionalGates := make(map[string]int)
for i, newGate := range newGates {
additionalGates[newGate.Name] = i
}
for _, oldGate := range oldGates {
delete(additionalGates, oldGate.Name)
}
for gate, i := range additionalGates {
allErrs = append(allErrs, field.Forbidden(fldPath.Index(i).Child("name"), fmt.Sprintf("only deletion is allowed, but found new scheduling gate '%s'", gate)))
}
return allErrs
}
func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, hostAlias := range hostAliases {
@ -3611,6 +3649,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
@ -4285,6 +4324,11 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
if len(pod.Spec.EphemeralContainers) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
}
// A Pod cannot be assigned a Node if there are remaining scheduling gates.
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) &&
pod.Spec.NodeName != "" && len(pod.Spec.SchedulingGates) != 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
}
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
return allErrs
@ -4370,6 +4414,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
// 2. spec.initContainers[*].image
// 3. spec.activeDeadlineSeconds
// 4. spec.terminationGracePeriodSeconds
// 5. spec.schedulingGates
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
allErrs = append(allErrs, containerErrs...)
@ -4405,6 +4450,9 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
// Allow only additions to tolerations updates.
allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
// Allow only deletions to schedulingGates updates.
allErrs = append(allErrs, validateOnlyDeletedSchedulingGates(newPod.Spec.SchedulingGates, oldPod.Spec.SchedulingGates, specPath.Child("schedulingGates"))...)
// the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have
// so far and save the cost of a deep copy.
if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
@ -4433,6 +4481,8 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
}
// munge spec.schedulingGates
mungedPodSpec.SchedulingGates = oldPod.Spec.SchedulingGates // +k8s:verify-mutation:reason=clone
// tolerations are checked before the deep copy, so munge those too
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone

View File

@ -10794,6 +10794,91 @@ func TestValidatePod(t *testing.T) {
}
}
func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
applyEssentials := func(pod *core.Pod) {
pod.Spec.Containers = []core.Container{
{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
}
pod.Spec.RestartPolicy = core.RestartPolicyAlways
pod.Spec.DNSPolicy = core.DNSClusterFirst
}
fldPath := field.NewPath("spec")
tests := []struct {
name string
pod *core.Pod
featureEnabled bool
wantFieldErrors field.ErrorList
}{
{
name: "create a Pod with nodeName and schedulingGates, feature disabled",
pod: &core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
Spec: core.PodSpec{
NodeName: "node",
SchedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
},
},
},
featureEnabled: false,
wantFieldErrors: nil,
},
{
name: "create a Pod with nodeName and schedulingGates, feature enabled",
pod: &core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
Spec: core.PodSpec{
NodeName: "node",
SchedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
},
},
},
featureEnabled: true,
wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
},
{
name: "create a Pod with schedulingGates, feature disabled",
pod: &core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
},
},
},
featureEnabled: false,
wantFieldErrors: nil,
},
{
name: "create a Pod with schedulingGates, feature enabled",
pod: &core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
},
},
},
featureEnabled: true,
wantFieldErrors: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
applyEssentials(tt.pod)
errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
}
})
}
}
func TestValidatePodUpdate(t *testing.T) {
var (
activeDeadlineSecondsZero = int64(0)
@ -11698,6 +11783,54 @@ func TestValidatePodUpdate(t *testing.T) {
err: "Forbidden: pod updates may not change fields other than ",
test: "update pod spec OS to a valid value, featuregate disabled",
},
{
new: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
},
},
old: core.Pod{},
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
test: "update pod spec schedulingGates: add new scheduling gate",
},
{
new: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
},
},
old: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
},
},
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
test: "update pod spec schedulingGates: mutating an existing scheduling gate",
},
{
new: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
},
},
old: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
},
},
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
},
{
new: core.Pod{},
old: core.Pod{
Spec: core.PodSpec{
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
},
},
err: "",
test: "update pod spec schedulingGates: legal deletion",
},
}
for _, test := range tests {
test.new.ObjectMeta.ResourceVersion = "1"
@ -18481,6 +18614,7 @@ func TestValidateOSFields(t *testing.T) {
"RestartPolicy",
"RuntimeClassName",
"SchedulerName",
"SchedulingGates[*].Name",
"SecurityContext.RunAsNonRoot",
"ServiceAccountName",
"SetHostnameAsFQDN",
@ -18517,6 +18651,71 @@ func TestValidateOSFields(t *testing.T) {
}
}
func TestValidateSchedulingGates(t *testing.T) {
fieldPath := field.NewPath("field")
tests := []struct {
name string
schedulingGates []core.PodSchedulingGate
wantFieldErrors field.ErrorList
}{
{
name: "nil gates",
schedulingGates: nil,
wantFieldErrors: field.ErrorList{},
},
{
name: "empty string in gates",
schedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
{Name: ""},
},
wantFieldErrors: []*field.Error{field.Required(fieldPath.Index(1), "must not be empty")},
},
{
name: "legal gates",
schedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
},
wantFieldErrors: field.ErrorList{},
},
{
name: "duplicated gates (single duplication)",
schedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
{Name: "bar"},
},
wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
},
{
name: "duplicated gates (multiple duplications)",
schedulingGates: []core.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
{Name: "foo"},
{Name: "baz"},
{Name: "foo"},
{Name: "bar"},
},
wantFieldErrors: field.ErrorList{
field.Duplicate(fieldPath.Index(2), "foo"),
field.Duplicate(fieldPath.Index(4), "foo"),
field.Duplicate(fieldPath.Index(5), "bar"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
}
})
}
}
// collectResourcePaths traverses the object, computing all the struct paths.
func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {

View File

@ -3728,6 +3728,22 @@ func (in *PodReadinessGate) DeepCopy() *PodReadinessGate {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSchedulingGate) DeepCopyInto(out *PodSchedulingGate) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGate.
func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate {
if in == nil {
return nil
}
out := new(PodSchedulingGate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
*out = *in
@ -3961,6 +3977,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) {
*out = new(PodOS)
**out = **in
}
if in.SchedulingGates != nil {
in, out := &in.SchedulingGates, &out.SchedulingGates
*out = make([]PodSchedulingGate, len(*in))
copy(*out, *in)
}
return
}

View File

@ -643,6 +643,13 @@ const (
// sandbox creation and network configuration completes successfully
PodHasNetworkCondition featuregate.Feature = "PodHasNetworkCondition"
// owner: @Huang-Wei
// kep: https://kep.k8s.io/3521
// alpha: v1.26
//
// Enable users to specify when a Pod is ready for scheduling.
PodSchedulingReadiness featuregate.Feature = "PodSchedulingReadiness"
// owner: @liggitt, @tallclair, sig-auth
// alpha: v1.22
// beta: v1.23
@ -996,6 +1003,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
PodSchedulingReadiness: {Default: false, PreRelease: featuregate.Alpha},
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25

View File

@ -448,6 +448,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/api/core/v1.PodPortForwardOptions": schema_k8sio_api_core_v1_PodPortForwardOptions(ref),
"k8s.io/api/core/v1.PodProxyOptions": schema_k8sio_api_core_v1_PodProxyOptions(ref),
"k8s.io/api/core/v1.PodReadinessGate": schema_k8sio_api_core_v1_PodReadinessGate(ref),
"k8s.io/api/core/v1.PodSchedulingGate": schema_k8sio_api_core_v1_PodSchedulingGate(ref),
"k8s.io/api/core/v1.PodSecurityContext": schema_k8sio_api_core_v1_PodSecurityContext(ref),
"k8s.io/api/core/v1.PodSignature": schema_k8sio_api_core_v1_PodSignature(ref),
"k8s.io/api/core/v1.PodSpec": schema_k8sio_api_core_v1_PodSpec(ref),
@ -22346,6 +22347,28 @@ func schema_k8sio_api_core_v1_PodReadinessGate(ref common.ReferenceCallback) com
}
}
func schema_k8sio_api_core_v1_PodSchedulingGate(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "PodSchedulingGate is associated to a Pod to guard its scheduling.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name"},
},
},
}
}
func schema_k8sio_api_core_v1_PodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -22858,12 +22881,36 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA
Format: "",
},
},
"schedulingGates": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"name",
},
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge",
},
},
SchemaProps: spec.SchemaProps{
Description: "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/api/core/v1.PodSchedulingGate"),
},
},
},
},
},
},
Required: []string{"containers"},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
"k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
}
}

View File

@ -767,6 +767,13 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow
reason = pod.Status.Reason
}
// If the Pod carries {type:PodScheduled, reason:WaitingForGates}, set reason to 'SchedulingGated'.
for _, condition := range pod.Status.Conditions {
if condition.Type == api.PodScheduled && condition.Reason == api.PodReasonSchedulingGated {
reason = api.PodReasonSchedulingGated
}
}
row := metav1.TableRow{
Object: runtime.RawExtension{Object: pod},
}

View File

@ -1502,6 +1502,24 @@ func TestPrintPod(t *testing.T) {
},
[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
},
{
// Test PodScheduled condition with reason WaitingForGates
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "test15"},
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
Status: api.PodStatus{
Phase: "podPhase",
Conditions: []api.PodCondition{
{
Type: api.PodScheduled,
Status: api.ConditionFalse,
Reason: api.PodReasonSchedulingGated,
},
},
},
},
[]metav1.TableRow{{Cells: []interface{}{"test15", "0/2", api.PodReasonSchedulingGated, "0", "<unknown>"}}},
},
}
for i, test := range tests {

View File

@ -33,10 +33,12 @@ import (
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
@ -221,6 +223,10 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types
if pod.Spec.NodeName != "" {
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
}
// Reject binding to a scheduling un-ready Pod.
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && len(pod.Spec.SchedulingGates) != 0 {
return nil, fmt.Errorf("pod %v has non-empty .spec.schedulingGates", pod.Name)
}
pod.Spec.NodeName = machine
if pod.Annotations == nil {
pod.Annotations = make(map[string]string)

View File

@ -18,6 +18,7 @@ package storage
import (
"context"
goerrors "errors"
"fmt"
"net/url"
"strings"
@ -41,7 +42,10 @@ import (
apiserverstorage "k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/securitycontext"
)
@ -745,6 +749,77 @@ func TestEtcdCreateWithConflict(t *testing.T) {
}
}
func TestEtcdCreateWithSchedulingGates(t *testing.T) {
tests := []struct {
name string
featureEnabled bool
schedulingGates []api.PodSchedulingGate
wantErr error
}{
{
name: "pod with non-nil schedulingGates, feature disabled",
featureEnabled: false,
schedulingGates: []api.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
},
wantErr: nil,
},
{
name: "pod with non-nil schedulingGates, feature enabled",
featureEnabled: true,
schedulingGates: []api.PodSchedulingGate{
{Name: "foo"},
{Name: "bar"},
},
wantErr: goerrors.New(`Operation cannot be fulfilled on pods/binding "foo": pod foo has non-empty .spec.schedulingGates`),
},
{
name: "pod with nil schedulingGates, feature disabled",
featureEnabled: false,
schedulingGates: nil,
wantErr: nil,
},
{
name: "pod with nil schedulingGates, feature enabled",
featureEnabled: true,
schedulingGates: nil,
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
storage, bindingStorage, _, server := newStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
ctx := genericapirequest.NewDefaultContext()
pod := validNewPod()
pod.Spec.SchedulingGates = tt.schedulingGates
if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err := bindingStorage.Create(ctx, "foo", &api.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
}, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if tt.wantErr == nil {
if err != nil {
t.Errorf("Want nil err, but got %v", err)
}
} else {
if err == nil {
t.Errorf("Want %v, but got nil err", tt.wantErr)
} else if tt.wantErr.Error() != err.Error() {
t.Errorf("Want %v, but got %v", tt.wantErr, err)
}
}
})
}
}
func validNewBinding() *api.Binding {
return &api.Binding{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},

View File

@ -38,12 +38,14 @@ import (
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/api/legacyscheme"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
@ -87,6 +89,7 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
podutil.DropDisabledPodFields(pod, nil)
applySeccompVersionSkew(pod)
applyWaitingForSchedulingGatesCondition(pod)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
@ -642,6 +645,29 @@ func validateContainer(container string, pod *api.Pod) (string, error) {
return container, nil
}
// applyWaitingForSchedulingGatesCondition adds a {type:PodScheduled, reason:WaitingForGates} condition
// to a new-created Pod if necessary.
func applyWaitingForSchedulingGatesCondition(pod *api.Pod) {
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) ||
len(pod.Spec.SchedulingGates) == 0 {
return
}
// If found a condition with type PodScheduled, return.
for _, condition := range pod.Status.Conditions {
if condition.Type == api.PodScheduled {
return
}
}
pod.Status.Conditions = append(pod.Status.Conditions, api.PodCondition{
Type: api.PodScheduled,
Status: api.ConditionFalse,
Reason: api.PodReasonSchedulingGated,
Message: "Scheduling is blocked due to non-empty scheduling gates",
})
}
// applySeccompVersionSkew implements the version skew behavior described in:
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/135-seccomp#version-skew-strategy
// Note that we dropped copying the field to annotation synchronization in

View File

@ -35,9 +35,12 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/cache"
featuregatetesting "k8s.io/component-base/featuregate/testing"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
utilpointer "k8s.io/utils/pointer"
@ -261,6 +264,72 @@ func TestGetPodQOS(t *testing.T) {
}
}
func TestWaitingForGatesCondition(t *testing.T) {
tests := []struct {
name string
pod *api.Pod
featureEnabled bool
want api.PodCondition
}{
{
name: "pod without .spec.schedulingGates, feature disabled",
pod: &api.Pod{},
featureEnabled: false,
want: api.PodCondition{},
},
{
name: "pod without .spec.schedulingGates, feature enabled",
pod: &api.Pod{},
featureEnabled: true,
want: api.PodCondition{},
},
{
name: "pod with .spec.schedulingGates, feature disabled",
pod: &api.Pod{
Spec: api.PodSpec{
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
},
},
featureEnabled: false,
want: api.PodCondition{},
},
{
name: "pod with .spec.schedulingGates, feature enabled",
pod: &api.Pod{
Spec: api.PodSpec{
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
},
},
featureEnabled: true,
want: api.PodCondition{
Type: api.PodScheduled,
Status: api.ConditionFalse,
Reason: api.PodReasonSchedulingGated,
Message: "Scheduling is blocked due to non-empty scheduling gates",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod)
var got api.PodCondition
for _, condition := range tt.pod.Status.Conditions {
if condition.Type == api.PodScheduled {
got = condition
break
}
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
}
})
}
}
func TestCheckGracefulDelete(t *testing.T) {
defaultGracePeriod := int64(30)
tcs := []struct {

File diff suppressed because it is too large Load Diff

View File

@ -3353,6 +3353,13 @@ message PodReadinessGate {
optional string conditionType = 1;
}
// PodSchedulingGate is associated to a Pod to guard its scheduling.
message PodSchedulingGate {
// Name of the scheduling gate.
// Each scheduling gate must have a unique name field.
optional string name = 1;
}
// PodSecurityContext holds pod-level security attributes and common container settings.
// Some fields are also present in container.securityContext. Field values of
// container.securityContext take precedence over field values of PodSecurityContext.
@ -3747,6 +3754,17 @@ message PodSpec {
// +k8s:conversion-gen=false
// +optional
optional bool hostUsers = 37;
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
//
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
// +optional
// +patchMergeKey=name
// +patchStrategy=merge
// +listType=map
// +listMapKey=name
repeated PodSchedulingGate schedulingGates = 38;
}
// PodStatus represents information about the status of a pod. Status may trail the actual

View File

@ -2666,6 +2666,10 @@ const (
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
PodReasonUnschedulable = "Unschedulable"
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
// skips scheduling the pod because one or more scheduling gates are still present.
PodReasonSchedulingGated = "SchedulingGated"
// PodReasonSchedulerError reason in PodScheduled PodCondition means that some internal error happens
// during scheduling, for example due to nodeAffinity parsing errors.
PodReasonSchedulerError = "SchedulerError"
@ -3327,6 +3331,16 @@ type PodSpec struct {
// +k8s:conversion-gen=false
// +optional
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
//
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
// +optional
// +patchMergeKey=name
// +patchStrategy=merge
// +listType=map
// +listMapKey=name
SchedulingGates []PodSchedulingGate `json:"schedulingGates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,38,opt,name=schedulingGates"`
}
// OSName is the set of OS'es that can be used in OS.
@ -3347,6 +3361,13 @@ type PodOS struct {
Name OSName `json:"name" protobuf:"bytes,1,opt,name=name"`
}
// PodSchedulingGate is associated to a Pod to guard its scheduling.
type PodSchedulingGate struct {
// Name of the scheduling gate.
// Each scheduling gate must have a unique name field.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
}
// +enum
type UnsatisfiableConstraintAction string

View File

@ -1606,6 +1606,15 @@ func (PodReadinessGate) SwaggerDoc() map[string]string {
return map_PodReadinessGate
}
var map_PodSchedulingGate = map[string]string{
"": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
"name": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
}
func (PodSchedulingGate) SwaggerDoc() map[string]string {
return map_PodSchedulingGate
}
var map_PodSecurityContext = map[string]string{
"": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"seLinuxOptions": "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.",
@ -1672,6 +1681,7 @@ var map_PodSpec = map[string]string{
"setHostnameAsFQDN": "If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false.",
"os": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
"hostUsers": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.",
"schedulingGates": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
}
func (PodSpec) SwaggerDoc() map[string]string {

View File

@ -3726,6 +3726,22 @@ func (in *PodReadinessGate) DeepCopy() *PodReadinessGate {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSchedulingGate) DeepCopyInto(out *PodSchedulingGate) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGate.
func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate {
if in == nil {
return nil
}
out := new(PodSchedulingGate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
*out = *in
@ -3959,6 +3975,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) {
*out = new(bool)
**out = **in
}
if in.SchedulingGates != nil {
in, out := &in.SchedulingGates, &out.SchedulingGates
*out = make([]PodSchedulingGate, len(*in))
copy(*out, *in)
}
return
}

View File

@ -1626,7 +1626,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"updateStrategy": {

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"strategy": {

View File

@ -810,6 +810,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1628,7 +1628,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
}
},

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"volumeClaimTemplates": [

View File

@ -808,6 +808,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"strategy": {

View File

@ -812,6 +812,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"volumeClaimTemplates": [

View File

@ -808,6 +808,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1626,7 +1626,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"updateStrategy": {

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"strategy": {

View File

@ -810,6 +810,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1628,7 +1628,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
}
},

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"volumeClaimTemplates": [

View File

@ -808,6 +808,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1700,7 +1700,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"ttlSecondsAfterFinished": 8,

View File

@ -854,6 +854,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1651,7 +1651,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"ttlSecondsAfterFinished": 8,

View File

@ -818,6 +818,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1700,7 +1700,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"ttlSecondsAfterFinished": 8,

View File

@ -854,6 +854,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1694,7 +1694,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"ttlSecondsAfterFinished": 8,

View File

@ -851,6 +851,8 @@ template:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1568,7 +1568,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
},
"status": {
"phase": "phaseValue",

View File

@ -758,6 +758,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1611,7 +1611,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
}
}

View File

@ -791,6 +791,8 @@ template:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1617,7 +1617,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
}
},

View File

@ -796,6 +796,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1626,7 +1626,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"updateStrategy": {

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1627,7 +1627,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
},
"strategy": {

View File

@ -812,6 +812,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -1628,7 +1628,12 @@
"os": {
"name": "nameValue"
},
"hostUsers": true
"hostUsers": true,
"schedulingGates": [
{
"name": "nameValue"
}
]
}
}
},

View File

@ -802,6 +802,8 @@ spec:
restartPolicy: restartPolicyValue
runtimeClassName: runtimeClassNameValue
schedulerName: schedulerNameValue
schedulingGates:
- name: nameValue
securityContext:
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue

View File

@ -0,0 +1,39 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
// PodSchedulingGateApplyConfiguration represents an declarative configuration of the PodSchedulingGate type for use
// with apply.
type PodSchedulingGateApplyConfiguration struct {
Name *string `json:"name,omitempty"`
}
// PodSchedulingGateApplyConfiguration constructs an declarative configuration of the PodSchedulingGate type for use with
// apply.
func PodSchedulingGate() *PodSchedulingGateApplyConfiguration {
return &PodSchedulingGateApplyConfiguration{}
}
// WithName sets the Name field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Name field is set to the value of the last call.
func (b *PodSchedulingGateApplyConfiguration) WithName(value string) *PodSchedulingGateApplyConfiguration {
b.Name = &value
return b
}

View File

@ -62,6 +62,7 @@ type PodSpecApplyConfiguration struct {
SetHostnameAsFQDN *bool `json:"setHostnameAsFQDN,omitempty"`
OS *PodOSApplyConfiguration `json:"os,omitempty"`
HostUsers *bool `json:"hostUsers,omitempty"`
SchedulingGates []PodSchedulingGateApplyConfiguration `json:"schedulingGates,omitempty"`
}
// PodSpecApplyConfiguration constructs an declarative configuration of the PodSpec type for use with
@ -416,3 +417,16 @@ func (b *PodSpecApplyConfiguration) WithHostUsers(value bool) *PodSpecApplyConfi
b.HostUsers = &value
return b
}
// WithSchedulingGates adds the given value to the SchedulingGates field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the SchedulingGates field.
func (b *PodSpecApplyConfiguration) WithSchedulingGates(values ...*PodSchedulingGateApplyConfiguration) *PodSpecApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithSchedulingGates")
}
b.SchedulingGates = append(b.SchedulingGates, *values[i])
}
return b
}

View File

@ -5725,6 +5725,13 @@ var schemaYAML = typed.YAMLObject(`types:
type:
scalar: string
default: ""
- name: io.k8s.api.core.v1.PodSchedulingGate
map:
fields:
- name: name
type:
scalar: string
default: ""
- name: io.k8s.api.core.v1.PodSecurityContext
map:
fields:
@ -5881,6 +5888,14 @@ var schemaYAML = typed.YAMLObject(`types:
- name: schedulerName
type:
scalar: string
- name: schedulingGates
type:
list:
elementType:
namedType: io.k8s.api.core.v1.PodSchedulingGate
elementRelationship: associative
keys:
- name
- name: securityContext
type:
namedType: io.k8s.api.core.v1.PodSecurityContext

View File

@ -735,6 +735,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &applyconfigurationscorev1.PodOSApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("PodReadinessGate"):
return &applyconfigurationscorev1.PodReadinessGateApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("PodSchedulingGate"):
return &applyconfigurationscorev1.PodSchedulingGateApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("PodSecurityContext"):
return &applyconfigurationscorev1.PodSecurityContextApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("PodSpec"):

View File

@ -184,6 +184,7 @@ INFO: Unexpected error: wait for pod pending-pod running:
SetHostnameAsFQDN: nil,
OS: nil,
HostUsers: nil,
SchedulingGates: nil,
},
Status: {
Phase: "",