Skip to content

Commit ba43630

Browse files
authored
Merge pull request kubernetes#90475 from alculquicondor/topology-scoring
Topology spreading scoring with automatically weighted topologies
2 parents c71a25e + 1aaa5fc commit ba43630

File tree

2 files changed

+143
-135
lines changed

2 files changed

+143
-135
lines changed

pkg/scheduler/framework/plugins/podtopologyspread/scoring.go

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type preScoreState struct {
3939
IgnoredNodes sets.String
4040
// TopologyPairToPodCounts is keyed with topologyPair, and valued with the number of matching pods.
4141
TopologyPairToPodCounts map[topologyPair]*int64
42+
// TopologyNormalizingWeight is the weight we give to the counts per topology.
43+
// This allows the pod counts of smaller topologies to not be watered down by
44+
// bigger ones.
45+
TopologyNormalizingWeight []float64
4246
}
4347

4448
// Clone implements the mandatory Clone interface. We don't really copy the data since
@@ -51,6 +55,7 @@ func (s *preScoreState) Clone() framework.StateData {
5155
// don't have required topologyKey(s), and initialize:
5256
// 1) s.TopologyPairToPodCounts: keyed with both eligible topology pair and node names.
5357
// 2) s.IgnoredNodes: the set of nodes that shouldn't be scored.
58+
// 3) s.TopologyNormalizingWeight: The weight to be given to each constraint based on the number of values in a topology.
5459
func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, filteredNodes []*v1.Node) error {
5560
var err error
5661
if len(pod.Spec.TopologySpreadConstraints) > 0 {
@@ -67,24 +72,35 @@ func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, fi
6772
if len(s.Constraints) == 0 {
6873
return nil
6974
}
75+
topoSize := make([]int, len(s.Constraints))
7076
for _, node := range filteredNodes {
7177
if !nodeLabelsMatchSpreadConstraints(node.Labels, s.Constraints) {
7278
// Nodes which don't have all required topologyKeys present are ignored
7379
// when scoring later.
7480
s.IgnoredNodes.Insert(node.Name)
7581
continue
7682
}
77-
for _, constraint := range s.Constraints {
83+
for i, constraint := range s.Constraints {
7884
// per-node counts are calculated during Score.
7985
if constraint.TopologyKey == v1.LabelHostname {
8086
continue
8187
}
8288
pair := topologyPair{key: constraint.TopologyKey, value: node.Labels[constraint.TopologyKey]}
8389
if s.TopologyPairToPodCounts[pair] == nil {
8490
s.TopologyPairToPodCounts[pair] = new(int64)
91+
topoSize[i]++
8592
}
8693
}
8794
}
95+
96+
s.TopologyNormalizingWeight = make([]float64, len(s.Constraints))
97+
for i, c := range s.Constraints {
98+
sz := topoSize[i]
99+
if c.TopologyKey == v1.LabelHostname {
100+
sz = len(filteredNodes) - len(s.IgnoredNodes)
101+
}
102+
s.TopologyNormalizingWeight[i] = topologyNormalizingWeight(sz)
103+
}
88104
return nil
89105
}
90106

@@ -174,20 +190,20 @@ func (pl *PodTopologySpread) Score(ctx context.Context, cycleState *framework.Cy
174190

175191
// For each present <pair>, current node gets a credit of <matchSum>.
176192
// And we sum up <matchSum> and return it as this node's score.
177-
var score int64
178-
for _, c := range s.Constraints {
193+
var score float64
194+
for i, c := range s.Constraints {
179195
if tpVal, ok := node.Labels[c.TopologyKey]; ok {
196+
var cnt int64
180197
if c.TopologyKey == v1.LabelHostname {
181-
count := countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace)
182-
score += int64(count)
198+
cnt = int64(countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace))
183199
} else {
184200
pair := topologyPair{key: c.TopologyKey, value: tpVal}
185-
matchSum := *s.TopologyPairToPodCounts[pair]
186-
score += matchSum
201+
cnt = *s.TopologyPairToPodCounts[pair]
187202
}
203+
score += float64(cnt) * s.TopologyNormalizingWeight[i]
188204
}
189205
}
190-
return score, nil
206+
return int64(score), nil
191207
}
192208

193209
// NormalizeScore invoked after scoring all nodes.
@@ -200,41 +216,41 @@ func (pl *PodTopologySpread) NormalizeScore(ctx context.Context, cycleState *fra
200216
return nil
201217
}
202218

203-
// Calculate the summed <total> score and <minScore>.
219+
// Calculate <minScore> and <maxScore>
204220
var minScore int64 = math.MaxInt64
205-
var total int64
221+
var maxScore int64
206222
for _, score := range scores {
207223
// it's mandatory to check if <score.Name> is present in m.IgnoredNodes
208224
if s.IgnoredNodes.Has(score.Name) {
209225
continue
210226
}
211-
total += score.Score
212227
if score.Score < minScore {
213228
minScore = score.Score
214229
}
230+
if score.Score > maxScore {
231+
maxScore = score.Score
232+
}
215233
}
216234

217-
maxMinDiff := total - minScore
218235
for i := range scores {
219236
nodeInfo, err := pl.sharedLister.NodeInfos().Get(scores[i].Name)
220237
if err != nil {
221238
return framework.NewStatus(framework.Error, err.Error())
222239
}
223240
node := nodeInfo.Node()
224241

225-
if maxMinDiff == 0 {
226-
scores[i].Score = framework.MaxNodeScore
242+
if s.IgnoredNodes.Has(node.Name) {
243+
scores[i].Score = 0
227244
continue
228245
}
229246

230-
if s.IgnoredNodes.Has(node.Name) {
231-
scores[i].Score = 0
247+
if maxScore == 0 {
248+
scores[i].Score = framework.MaxNodeScore
232249
continue
233250
}
234251

235-
flippedScore := total - scores[i].Score
236-
fScore := float64(framework.MaxNodeScore) * (float64(flippedScore) / float64(maxMinDiff))
237-
scores[i].Score = int64(fScore)
252+
s := scores[i].Score
253+
scores[i].Score = framework.MaxNodeScore * (maxScore + minScore - s) / maxScore
238254
}
239255
return nil
240256
}
@@ -256,3 +272,16 @@ func getPreScoreState(cycleState *framework.CycleState) (*preScoreState, error)
256272
}
257273
return s, nil
258274
}
275+
276+
// topologyNormalizingWeight calculates the weight for the topology, based on
277+
// the number of values that exist for a topology.
278+
// Since <size> is at least 1 (all nodes that passed the Filters are in the
279+
// same topology), and k8s supports 5k nodes, the result is in the interval
280+
// <1.09, 8.52>.
281+
//
282+
// Note: <size> could also be zero when no nodes have the required topologies,
283+
// however we don't care about topology weight in this case as we return a 0
284+
// score for all nodes.
285+
func topologyNormalizingWeight(size int) float64 {
286+
return math.Log(float64(size + 2))
287+
}

0 commit comments

Comments
 (0)