-
Notifications
You must be signed in to change notification settings - Fork 557
Expand file tree
/
Copy pathdrift.go
More file actions
128 lines (109 loc) · 4.47 KB
/
Copy pathdrift.go
File metadata and controls
128 lines (109 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
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.
*/
package disruption
import (
"context"
"errors"
"slices"
"sort"
"strings"
"github.com/samber/lo"
"k8s.io/utils/clock"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/karpenter/pkg/utils/pretty"
v1 "sigs.k8s.io/karpenter/pkg/apis/v1"
disruptionevents "sigs.k8s.io/karpenter/pkg/controllers/disruption/events"
"sigs.k8s.io/karpenter/pkg/controllers/provisioning"
"sigs.k8s.io/karpenter/pkg/controllers/state"
"sigs.k8s.io/karpenter/pkg/events"
"sigs.k8s.io/karpenter/pkg/metrics"
)
// Drift is a subreconciler that deletes drifted candidates.
type Drift struct {
kubeClient client.Client
cluster *state.Cluster
provisioner *provisioning.Provisioner
recorder events.Recorder
clock clock.Clock
}
func NewDrift(kubeClient client.Client, cluster *state.Cluster, provisioner *provisioning.Provisioner, recorder events.Recorder, clk clock.Clock) *Drift {
return &Drift{
kubeClient: kubeClient,
cluster: cluster,
provisioner: provisioner,
recorder: recorder,
clock: clk,
}
}
// ShouldDisrupt is a predicate used to filter candidates
func (d *Drift) ShouldDisrupt(ctx context.Context, c *Candidate) bool {
return !c.OwnedByStaticNodePool() && c.NodeClaim.StatusConditions().Get(string(d.Reason())).IsTrue()
}
// ComputeCommand generates a disruption command given candidates
func (d *Drift) ComputeCommands(ctx context.Context, disruptionBudgetMapping map[string]int, candidates ...*Candidate) ([]Command, error) {
sort.Slice(candidates, func(i int, j int) bool {
return candidates[i].NodeClaim.StatusConditions().Get(string(d.Reason())).LastTransitionTime.Time.Before(
candidates[j].NodeClaim.StatusConditions().Get(string(d.Reason())).LastTransitionTime.Time)
})
emptyCandidates, nonEmptyCandidates := lo.FilterReject(candidates, func(c *Candidate, _ int) bool {
return len(c.reschedulablePods) == 0
})
// Prioritize empty candidates since we want them to get priority over non-empty candidates if the budget is constrained.
// Disrupting empty candidates first also helps reduce the overall churn because if a non-empty candidate is disrupted first,
// the pods from that node can reschedule on the empty nodes and will need to move again when those nodes get disrupted.
for _, candidate := range slices.Concat(emptyCandidates, nonEmptyCandidates) {
// If the disruption budget doesn't allow this candidate to be disrupted,
// continue to the next candidate. We don't need to decrement any budget
// counter since drift commands can only have one candidate.
if disruptionBudgetMapping[candidate.NodePool.Name] == 0 {
continue
}
// Check if we need to create any NodeClaims.
stop := metrics.Measure(CandidateEvaluationDurationSeconds, map[string]string{
metrics.ReasonLabel: strings.ToLower(string(d.Reason())),
ConsolidationTypeLabel: d.ConsolidationType(),
StageLabel: StageEvaluate,
})
results, err := SimulateScheduling(ctx, d.kubeClient, d.cluster, d.provisioner, d.clock, d.recorder, nil, candidate)
stop()
if err != nil {
// if a candidate is now deleting, just retry
if errors.Is(err, errCandidateDeleting) {
continue
}
return []Command{}, err
}
// Emit an event that we couldn't reschedule the pods on the node.
if !results.AllNonPendingPodsScheduled() {
d.recorder.Publish(disruptionevents.Blocked(candidate.Node, candidate.NodeClaim, pretty.Sentence(results.NonPendingPodSchedulingErrors()))...)
continue
}
cmd := Command{
Candidates: []*Candidate{candidate},
Replacements: replacementsFromNodeClaims(results.NewNodeClaims...),
Results: results,
PoolDisruptionCosts: computePoolDisruptionCosts([]*Candidate{candidate}),
}
return []Command{cmd}, nil
}
return []Command{}, nil
}
func (d *Drift) Reason() v1.DisruptionReason {
return v1.DisruptionReasonDrifted
}
func (d *Drift) Class() string {
return EventualDisruptionClass
}
func (d *Drift) ConsolidationType() string {
return ""
}