Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 132ebb0

Browse files
author
k8s-merge-robot
committed
Merge pull request kubernetes#24459 from fgrzadkowski/unschedulable_pod
Automatic merge from submit-queue Add pod condition PodScheduled to detect situation when scheduler tried to schedule a Pod, but failed Set `PodSchedule` condition to `ConditionFalse` in `scheduleOne()` if scheduling failed and to `ConditionTrue` in `/bind` subresource. Ref kubernetes#24404 @mml (as it seems to be related to "why pending" effort) <!-- Reviewable:start --> --- This change is [<img src="http://reviewable.k8s.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](http://reviewable.k8s.io/reviews/kubernetes/kubernetes/24459) <!-- Reviewable:end -->
2 parents 2682208 + a80b179 commit 132ebb0

File tree

9 files changed

+106
-6
lines changed

9 files changed

+106
-6
lines changed

pkg/api/resource_helpers.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package api
1818

1919
import (
2020
"k8s.io/kubernetes/pkg/api/resource"
21+
"k8s.io/kubernetes/pkg/api/unversioned"
2122
)
2223

2324
// Returns string version of ResourceName.
@@ -80,12 +81,44 @@ func IsPodReadyConditionTrue(status PodStatus) bool {
8081
// Extracts the pod ready condition from the given status and returns that.
8182
// Returns nil if the condition is not present.
8283
func GetPodReadyCondition(status PodStatus) *PodCondition {
84+
_, condition := GetPodCondition(&status, PodReady)
85+
return condition
86+
}
87+
88+
func GetPodCondition(status *PodStatus, conditionType PodConditionType) (int, *PodCondition) {
8389
for i, c := range status.Conditions {
84-
if c.Type == PodReady {
85-
return &status.Conditions[i]
90+
if c.Type == conditionType {
91+
return i, &status.Conditions[i]
92+
}
93+
}
94+
return -1, nil
95+
}
96+
97+
// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
98+
// status has changed.
99+
// Returns true if pod condition has changed or has been added.
100+
func UpdatePodCondition(status *PodStatus, condition *PodCondition) bool {
101+
condition.LastTransitionTime = unversioned.Now()
102+
// Try to find this pod condition.
103+
conditionIndex, oldCondition := GetPodCondition(status, condition.Type)
104+
105+
if oldCondition == nil {
106+
// We are adding new pod condition.
107+
status.Conditions = append(status.Conditions, *condition)
108+
return true
109+
} else {
110+
// We are updating an existing condition, so we need to check if it has changed.
111+
if condition.Status == oldCondition.Status {
112+
condition.LastTransitionTime = oldCondition.LastTransitionTime
86113
}
114+
status.Conditions[conditionIndex] = *condition
115+
// Return true if one of the fields have changed.
116+
return condition.Status != oldCondition.Status ||
117+
condition.Reason != oldCondition.Reason ||
118+
condition.Message != oldCondition.Message ||
119+
!condition.LastProbeTime.Equal(oldCondition.LastProbeTime) ||
120+
!condition.LastTransitionTime.Equal(oldCondition.LastTransitionTime)
87121
}
88-
return nil
89122
}
90123

91124
// IsNodeReady returns true if a node is ready; false otherwise.

pkg/api/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,8 @@ type PodConditionType string
10711071

10721072
// These are valid conditions of pod.
10731073
const (
1074+
// PodScheduled represents status of the scheduling process for this pod.
1075+
PodScheduled PodConditionType = "PodScheduled"
10741076
// PodReady means the pod is able to service requests and should be added to the
10751077
// load balancing pools of all matching services.
10761078
PodReady PodConditionType = "Ready"

pkg/api/v1/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,8 @@ type PodConditionType string
13011301

13021302
// These are valid conditions of pod.
13031303
const (
1304+
// PodScheduled represents status of the scheduling process for this pod.
1305+
PodScheduled PodConditionType = "PodScheduled"
13041306
// PodReady means the pod is able to service requests and should be added to the
13051307
// load balancing pools of all matching services.
13061308
PodReady PodConditionType = "Ready"

pkg/kubelet/kubelet.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3351,6 +3351,16 @@ func (kl *Kubelet) generateAPIPodStatus(pod *api.Pod, podStatus *kubecontainer.P
33513351
s.Phase = GetPhase(spec, s.ContainerStatuses)
33523352
kl.probeManager.UpdatePodStatus(pod.UID, s)
33533353
s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(spec, s.ContainerStatuses, s.Phase))
3354+
// s (the PodStatus we are creating) will not have a PodScheduled condition yet, because converStatusToAPIStatus()
3355+
// does not create one. If the existing PodStatus has a PodScheduled condition, then copy it into s and make sure
3356+
// it is set to true. If the existing PodStatus does not have a PodScheduled condition, then create one that is set to true.
3357+
if _, oldPodScheduled := api.GetPodCondition(&pod.Status, api.PodScheduled); oldPodScheduled != nil {
3358+
s.Conditions = append(s.Conditions, *oldPodScheduled)
3359+
}
3360+
api.UpdatePodCondition(&pod.Status, &api.PodCondition{
3361+
Type: api.PodScheduled,
3362+
Status: api.ConditionTrue,
3363+
})
33543364

33553365
if !kl.standaloneMode {
33563366
hostIP, err := kl.GetHostIP()

pkg/registry/pod/etcd/etcd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx api.Context, podID, oldMachin
166166
for k, v := range annotations {
167167
pod.Annotations[k] = v
168168
}
169+
api.UpdatePodCondition(&pod.Status, &api.PodCondition{
170+
Type: api.PodScheduled,
171+
Status: api.ConditionTrue,
172+
})
169173
finalPod = pod
170174
return pod, nil
171175
}))

plugin/pkg/scheduler/factory/factory.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,10 @@ func (f *ConfigFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String,
379379
return &scheduler.Config{
380380
SchedulerCache: f.schedulerCache,
381381
// The scheduler only needs to consider schedulable nodes.
382-
NodeLister: f.NodeLister.NodeCondition(getNodeConditionPredicate()),
383-
Algorithm: algo,
384-
Binder: &binder{f.Client},
382+
NodeLister: f.NodeLister.NodeCondition(getNodeConditionPredicate()),
383+
Algorithm: algo,
384+
Binder: &binder{f.Client},
385+
PodConditionUpdater: &podConditionUpdater{f.Client},
385386
NextPod: func() *api.Pod {
386387
return f.getNextPod()
387388
},
@@ -541,6 +542,19 @@ func (b *binder) Bind(binding *api.Binding) error {
541542
// return b.Pods(binding.Namespace).Bind(binding)
542543
}
543544

545+
type podConditionUpdater struct {
546+
*client.Client
547+
}
548+
549+
func (p *podConditionUpdater) Update(pod *api.Pod, condition *api.PodCondition) error {
550+
glog.V(2).Infof("Updating pod condition for %s/%s to (%s==%s)", pod.Namespace, pod.Name, condition.Type, condition.Status)
551+
if api.UpdatePodCondition(&pod.Status, condition) {
552+
_, err := p.Pods(pod.Namespace).UpdateStatus(pod)
553+
return err
554+
}
555+
return nil
556+
}
557+
544558
type clock interface {
545559
Now() time.Time
546560
}

plugin/pkg/scheduler/scheduler.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ type Binder interface {
3737
Bind(binding *api.Binding) error
3838
}
3939

40+
type PodConditionUpdater interface {
41+
Update(pod *api.Pod, podCondition *api.PodCondition) error
42+
}
43+
4044
// Scheduler watches for new unscheduled pods. It attempts to find
4145
// nodes that they fit on and writes bindings back to the api server.
4246
type Scheduler struct {
@@ -50,6 +54,10 @@ type Config struct {
5054
NodeLister algorithm.NodeLister
5155
Algorithm algorithm.ScheduleAlgorithm
5256
Binder Binder
57+
// PodConditionUpdater is used only in case of scheduling errors. If we succeed
58+
// with scheduling, PodScheduled condition will be updated in apiserver in /bind
59+
// handler so that binding and setting PodCondition it is atomic.
60+
PodConditionUpdater PodConditionUpdater
5361

5462
// NextPod should be a function that blocks until the next pod
5563
// is available. We don't use a channel for this, because scheduling
@@ -92,6 +100,12 @@ func (s *Scheduler) scheduleOne() {
92100
glog.V(1).Infof("Failed to schedule: %+v", pod)
93101
s.config.Error(pod, err)
94102
s.config.Recorder.Eventf(pod, api.EventTypeWarning, "FailedScheduling", "%v", err)
103+
s.config.PodConditionUpdater.Update(pod, &api.PodCondition{
104+
Type: api.PodScheduled,
105+
Status: api.ConditionFalse,
106+
Reason: "Unschedulable",
107+
Message: err.Error(),
108+
})
95109
return
96110
}
97111
metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start))
@@ -120,11 +134,19 @@ func (s *Scheduler) scheduleOne() {
120134
}
121135

122136
bindingStart := time.Now()
137+
// If binding succeded then PodScheduled condition will be updated in apiserver so that
138+
// it's atomic with setting host.
123139
err := s.config.Binder.Bind(b)
124140
if err != nil {
125141
glog.V(1).Infof("Failed to bind pod: %+v", err)
126142
s.config.Error(pod, err)
127143
s.config.Recorder.Eventf(pod, api.EventTypeNormal, "FailedScheduling", "Binding rejected: %v", err)
144+
s.config.PodConditionUpdater.Update(pod, &api.PodCondition{
145+
Type: api.PodScheduled,
146+
Status: api.ConditionFalse,
147+
Reason: "BindingRejected",
148+
Message: err.Error(),
149+
})
128150
return
129151
}
130152
metrics.BindingLatency.Observe(metrics.SinceInMicroseconds(bindingStart))

plugin/pkg/scheduler/scheduler_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ type fakeBinder struct {
4343

4444
func (fb fakeBinder) Bind(binding *api.Binding) error { return fb.b(binding) }
4545

46+
type fakePodConditionUpdater struct{}
47+
48+
func (fc fakePodConditionUpdater) Update(pod *api.Pod, podCondition *api.PodCondition) error {
49+
return nil
50+
}
51+
4652
func podWithID(id, desiredHost string) *api.Pod {
4753
return &api.Pod{
4854
ObjectMeta: api.ObjectMeta{Name: id, SelfLink: testapi.Default.SelfLink("pods", id)},
@@ -128,6 +134,7 @@ func TestScheduler(t *testing.T) {
128134
gotBinding = b
129135
return item.injectBindError
130136
}},
137+
PodConditionUpdater: fakePodConditionUpdater{},
131138
Error: func(p *api.Pod, err error) {
132139
gotPod = p
133140
gotError = err

test/e2e/scheduler_predicates.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ func getPodsScheduled(pods *api.PodList) (scheduledPods, notScheduledPods []api.
4545
for _, pod := range pods.Items {
4646
if !masterNodes.Has(pod.Spec.NodeName) {
4747
if pod.Spec.NodeName != "" {
48+
_, scheduledCondition := api.GetPodCondition(&pod.Status, api.PodScheduled)
49+
Expect(scheduledCondition != nil).To(Equal(true))
50+
Expect(scheduledCondition.Status).To(Equal(api.ConditionTrue))
4851
scheduledPods = append(scheduledPods, pod)
4952
} else {
53+
_, scheduledCondition := api.GetPodCondition(&pod.Status, api.PodScheduled)
54+
Expect(scheduledCondition != nil).To(Equal(true))
55+
Expect(scheduledCondition.Status).To(Equal(api.ConditionFalse))
5056
notScheduledPods = append(notScheduledPods, pod)
5157
}
5258
}

0 commit comments

Comments
 (0)