k8s随笔--descheduler源码简读

启动:

 折叠源码
  
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
// 通过cobra生成启动命令
    cmd := &cobra.Command{
        Use:   "descheduler",
        Short: "descheduler",
        Long:  `The descheduler evicts pods which may be bound to less desired nodes`,
        Run: func(cmd *cobra.Command, args []string) {
            // s.Logs.Config.Format = s.Logging.Format
 
            // LoopbackClientConfig is a config for a privileged loopback connection
            var LoopbackClientConfig *restclient.Config
            var SecureServing *apiserver.SecureServingInfo
            if err := s.SecureServing.ApplyTo(&SecureServing, &LoopbackClientConfig); err != nil {
                klog.ErrorS(err, "failed to apply secure server configuration")
                return
            }
 
            factory, _ := registry.LogRegistry.Get(s.Logging.Format)
            if factory == nil {
                klog.ClearLogger()
            else {
                log, logrFlush := factory.Create(config.FormatOptions{})
                defer logrFlush()
                klog.SetLogger(log)
            }
 
            ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
            defer done()
            pathRecorderMux := mux.NewPathRecorderMux("descheduler")
            // 可配置是否开启监控
            if !s.DisableMetrics {
                pathRecorderMux.Handle("/metrics", legacyregistry.HandlerWithReset())
            }
 
            healthz.InstallHandler(pathRecorderMux, healthz.NamedCheck("Descheduler", healthz.PingHealthz.Check))
 
            if _, err := SecureServing.Serve(pathRecorderMux, 0, ctx.Done()); err != nil {
                klog.Fatalf("failed to start secure server: %v", err)
                return
            }
 
            err := Run(ctx, s)
            if err != nil {
                klog.ErrorS(err, "descheduler server")
            }
        },
    }

初始化及声明运行周期等部分:

 折叠源码
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer, deschedulerPolicy *api.DeschedulerPolicy, evictionPolicyGroupVersion string, stopChannel chan struct{}) error {
    // 获取进行二次调度需要使用的一些相关信息
    sharedInformerFactory := informers.NewSharedInformerFactory(rs.Client, 0)
    nodeInformer := sharedInformerFactory.Core().V1().Nodes()
    podInformer := sharedInformerFactory.Core().V1().Pods()
    namespaceInformer := sharedInformerFactory.Core().V1().Namespaces()
    priorityClassInformer := sharedInformerFactory.Scheduling().V1().PriorityClasses()
 
    // create the informers
    namespaceInformer.Informer()
    priorityClassInformer.Informer()
 
    // 注意这里,这里其实build了一个func,这个func接收nodeName和podFilter方法,返回该node下通过filter的所有pod
    getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
    if err != nil {
        return fmt.Errorf("build get pods assigned to node function error: %v", err)
    }
 
    sharedInformerFactory.Start(stopChannel)
    sharedInformerFactory.WaitForCacheSync(stopChannel)
 
    // 可能使用到的(descheduler预定义的)所有重调度策略,value是实现各自策略的方法
    // 每个策略具体执行的实现都是这个类型的 
    // type strategyFunction func(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor, getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc)
    strategyFuncs := map[api.StrategyName]strategyFunction{
        // 移除同一node下的重复pod
        "RemoveDuplicates":                            strategies.RemoveDuplicatePods,
        // node低资源使用率
        "LowNodeUtilization":                          nodeutilization.LowNodeUtilization,
        // node高资源使用率
        "HighNodeUtilization":                         nodeutilization.HighNodeUtilization,
        "RemovePodsViolatingInterPodAntiAffinity":     strategies.RemovePodsViolatingInterPodAntiAffinity,
        "RemovePodsViolatingNodeAffinity":             strategies.RemovePodsViolatingNodeAffinity,
        "RemovePodsViolatingNodeTaints":               strategies.RemovePodsViolatingNodeTaints,
        "RemovePodsHavingTooManyRestarts":             strategies.RemovePodsHavingTooManyRestarts,
        "PodLifeTime":                                 strategies.PodLifeTime,
        "RemovePodsViolatingTopologySpreadConstraint": strategies.RemovePodsViolatingTopologySpreadConstraint,
        "RemoveFailedPods":                            strategies.RemoveFailedPods,
    }
 
    // 下面准备一些相关的配置
    var nodeSelector string
    if deschedulerPolicy.NodeSelector != nil {
        nodeSelector = *deschedulerPolicy.NodeSelector
    }
 
    var evictLocalStoragePods bool
    if deschedulerPolicy.EvictLocalStoragePods != nil {
        evictLocalStoragePods = *deschedulerPolicy.EvictLocalStoragePods
    }
 
    evictBarePods := false
    if deschedulerPolicy.EvictFailedBarePods != nil {
        evictBarePods = *deschedulerPolicy.EvictFailedBarePods
        if evictBarePods {
            klog.V(1).InfoS("Warning: EvictFailedBarePods is set to True. This could cause eviction of pods without ownerReferences.")
        }
    }
 
    evictSystemCriticalPods := false
    if deschedulerPolicy.EvictSystemCriticalPods != nil {
        evictSystemCriticalPods = *deschedulerPolicy.EvictSystemCriticalPods
        if evictSystemCriticalPods {
            klog.V(1).InfoS("Warning: EvictSystemCriticalPods is set to True. This could cause eviction of Kubernetes system pods.")
        }
    }
 
    ignorePvcPods := false
    if deschedulerPolicy.IgnorePVCPods != nil {
        ignorePvcPods = *deschedulerPolicy.IgnorePVCPods
    }
 
    // 通过NonSlidingUntil来周期性的执行descheduler的监控及驱逐策略,执行周期为 rs.DeschedulingInterval
    wait.NonSlidingUntil(func() {
        // 查找node集合
        nodes, err := nodeutil.ReadyNodes(ctx, rs.Client, nodeInformer, nodeSelector)
        if err != nil {
            klog.V(1).InfoS("Unable to get ready nodes""err", err)
            close(stopChannel)
            return
        }
 
        //node数量<=1,应当终止
        if len(nodes) <= 1 {
            klog.V(1).InfoS("The cluster size is 0 or 1 meaning eviction causes service disruption or degradation. So aborting..")
            close(stopChannel)
            return
        }
 
        var podEvictorClient clientset.Interface
        // When the dry mode is enable, collect all the relevant objects (mostly pods) under a fake client.
        // So when evicting pods while running multiple strategies in a row have the cummulative effect
        // as is when evicting pods for real.
        // DryRun模式下,会构造虚拟的podEvictorClient,不会真正的进行驱逐
        if rs.DryRun {
            klog.V(3).Infof("Building a cached client from the cluster for the dry run")
            // Create a new cache so we start from scratch without any leftovers
            fakeClient, err := cachedClient(rs.Client, podInformer, nodeInformer, namespaceInformer, priorityClassInformer)
            if err != nil {
                klog.Error(err)
                return
            }
 
            fakeSharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
            getPodsAssignedToNode, err = podutil.BuildGetPodsAssignedToNodeFunc(fakeSharedInformerFactory.Core().V1().Pods())
            if err != nil {
                klog.Errorf("build get pods assigned to node function error: %v", err)
                return
            }
 
            fakeCtx, cncl := context.WithCancel(context.TODO())
            defer cncl()
            fakeSharedInformerFactory.Start(fakeCtx.Done())
            fakeSharedInformerFactory.WaitForCacheSync(fakeCtx.Done())
 
            podEvictorClient = fakeClient
        else {
            podEvictorClient = rs.Client
        }
 
        klog.V(3).Infof("Building a pod evictor")
        // 构造驱逐器
        podEvictor := evictions.NewPodEvictor(
            // client, dryRun模式下是fakeClient
            podEvictorClient,
            // 集群支持的驱逐器所需要使用的子资源的版本
            evictionPolicyGroupVersion,
            // 是否为dryRun
            rs.DryRun,
            // 每个node最多驱逐的pod限制,可配置
            deschedulerPolicy.MaxNoOfPodsToEvictPerNode,
            // 每个node最多驱逐的pod限制,可配置
            deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace,
            // 所有node的集合
            nodes,
            // 是否驱逐LocalStoragePod,可配置
            evictLocalStoragePods,
            // 是否驱逐SystemCritical,可配置
            evictSystemCriticalPods,
            // 是否忽略PvcPods,可配置
            ignorePvcPods,
            evictBarePods,
            !rs.DisableMetrics,
        )
 
        // 遍历用户配置中指定的驱逐策略,如果descheduler支持当前策略,就通过 f(ctx, rs.Client, strategy, nodes, podEvictor, getPodsAssignedToNode) 执行.
        for name, strategy := range deschedulerPolicy.Strategies {
            if f, ok := strategyFuncs[name]; ok {
                if strategy.Enabled {
                    f(ctx, rs.Client, strategy, nodes, podEvictor, getPodsAssignedToNode)
                }
            else {
                klog.ErrorS(fmt.Errorf("unknown strategy name"), "skipping strategy""strategy", name)
            }
        }
 
        klog.V(1).InfoS("Number of evicted pods""totalEvicted", podEvictor.TotalEvicted())
 
        // If there was no interval specified, send a signal to the stopChannel to end the wait.Until loop after 1 iteration
        if rs.DeschedulingInterval.Seconds() == 0 {
            close(stopChannel)
        }
    }, rs.DeschedulingInterval, stopChannel)
 
    return nil
}

移除同一个Node下重复Pod策略实现:

 折叠源码
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// RemoveDuplicatePods removes the duplicate pods on node. This strategy evicts all duplicate pods on node.
// A pod is said to be a duplicate of other if both of them are from same creator, kind and are within the same
// namespace, and have at least one container with the same image.
// As of now, this strategy won't evict daemonsets, mirror pods, critical pods and pods with local storages.
func RemoveDuplicatePods(
    ctx context.Context,
    client clientset.Interface,
    strategy api.DeschedulerStrategy,
    nodes []*v1.Node,
    podEvictor *evictions.PodEvictor,
    getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc,
) {
    // 一些关于策略配置方面的基础校验
    if err := validateRemoveDuplicatePodsParams(strategy.Params); err != nil {
        klog.ErrorS(err, "Invalid RemoveDuplicatePods parameters")
        return
    }
    thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params)
    if err != nil {
        klog.ErrorS(err, "Failed to get threshold priority from strategy's params")
        return
    }
 
    var includedNamespaces, excludedNamespaces sets.String
    if strategy.Params != nil && strategy.Params.Namespaces != nil {
        includedNamespaces = sets.NewString(strategy.Params.Namespaces.Include...)
        excludedNamespaces = sets.NewString(strategy.Params.Namespaces.Exclude...)
    }
 
    nodeFit := false
    if strategy.Params != nil {
        nodeFit = strategy.Params.NodeFit
    }
 
    evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority), evictions.WithNodeFit(nodeFit))
 
    duplicatePods := make(map[podOwner]map[string][]*v1.Pod)
    ownerKeyOccurence := make(map[podOwner]int32)
    nodeCount := 0
    nodeMap := make(map[string]*v1.Node)
 
    // 构造podFilter
    podFilter, err := podutil.NewOptions().
        WithFilter(evictable.IsEvictable).
        WithNamespaces(includedNamespaces).
        WithoutNamespaces(excludedNamespaces).
        BuildFilterFunc()
    if err != nil {
        klog.ErrorS(err, "Error initializing pod filter function")
        return
    }
 
    // 遍历所有node
    for _, node := range nodes {
        klog.V(1).InfoS("Processing node""node", klog.KObj(node))
        // 注意这里的 getPodsAssignedToNode,就是初始化部分提到的 getPodsAssignedToNode, 接收节点名与podFitler方法,返回这个node下所有通过filter的pod
        pods, err := podutil.ListPodsOnANode(node.Name, getPodsAssignedToNode, podFilter)
        if err != nil {
            klog.ErrorS(err, "Error listing evictable pods on node""node", klog.KObj(node))
            continue
        }
        nodeMap[node.Name] = node
        nodeCount++
        // Each pod has a list of owners and a list of containers, and each container has 1 image spec.
        // For each pod, we go through all the OwnerRef/Image mappings and represent them as a "key" string.
        // All of those mappings together makes a list of "key" strings that essentially represent that pod's uniqueness.
        // This list of keys representing a single pod is then sorted alphabetically.
        // If any other pod has a list that matches that pod's list, those pods are undeniably duplicates for the following reasons:
        //   - The 2 pods have the exact same ownerrefs
        //   - The 2 pods have the exact same container images
        //
        // duplicateKeysMap maps the first Namespace/Kind/Name/Image in a pod's list to a 2D-slice of all the other lists where that is the first key
        // (Since we sort each pod's list, we only need to key the map on the first entry in each list. Any pod that doesn't have
        // the same first entry is clearly not a duplicate. This makes lookup quick and minimizes storage needed).
        // If any of the existing lists for that first key matches the current pod's list, the current pod is a duplicate.
        // If not, then we add this pod's list to the list of lists for that key.
 
        // 以下是筛选重复pod的关键逻辑,descheduler对于这部分做了特别的设计,尝试解释一下,也可以看看上面的英文
        // 首先是duplicateKeysMap,key是由当前pod的 Namespace/Kind/Name/Image 拼接而成 value则是一个二维string切片 [][]string
        duplicateKeysMap := map[string][][]string{}
        for _, pod := range pods {
            ownerRefList := podutil.OwnerRef(pod)
            if hasExcludedOwnerRefKind(ownerRefList, strategy) || len(ownerRefList) == 0 {
                continue
            }
            // 这里定义的podContainerKeys预先指定了容量,可以避免slice的扩容. M个ownerRet与N个containers M * N
            podContainerKeys := make([]string, 0, len(ownerRefList)*len(pod.Spec.Containers))
            imageList := []string{}
            for _, container := range pod.Spec.Containers {
                imageList = append(imageList, container.Image)
            }
            // 对pod下的image排序,保证image出现的次序
            sort.Strings(imageList)
            imagesHash := strings.Join(imageList, "#")
            for _, ownerRef := range ownerRefList {
                ownerKey := podOwner{
                    namespace:  pod.ObjectMeta.Namespace,
                    kind:       ownerRef.Kind,
                    name:       ownerRef.Name,
                    imagesHash: imagesHash,
                }
                ownerKeyOccurence[ownerKey] = ownerKeyOccurence[ownerKey] + 1
                for _, image := range imageList {
                    // Namespace/Kind/Name should be unique for the cluster.
                    // We also consider the image, as 2 pods could have the same owner but serve different purposes
                    // So any non-unique Namespace/Kind/Name/Image pattern is a duplicate pod.
                    // 这里使用了更严格的策略, 增加了image
                    s := strings.Join([]string{pod.ObjectMeta.Namespace, ownerRef.Kind, ownerRef.Name, image}, "/")
                    podContainerKeys = append(podContainerKeys, s)
                }
            }
            // 对podContainerKey再次进行排序
            sort.Strings(podContainerKeys)
 
            // If there have been any other pods with the same first "key", look through all the lists to see if any match
            // 基于上面的排序操作,如果真的存在相同的key,任何podContainerKeys 0号 索引不同的pod被认为是不重复的
            if existing, ok := duplicateKeysMap[podContainerKeys[0]]; ok {
                matched := false
                for _, keys := range existing {
                    if reflect.DeepEqual(keys, podContainerKeys) {
                        matched = true
                        klog.V(3).InfoS("Duplicate found""pod", klog.KObj(pod))
                        for _, ownerRef := range ownerRefList {
                            ownerKey := podOwner{
                                namespace:  pod.ObjectMeta.Namespace,
                                kind:       ownerRef.Kind,
                                name:       ownerRef.Name,
                                imagesHash: imagesHash,
                            }
                            // 添加到指定的duplicatePods[ownerKey]中去,key是ownerKey, value是要驱逐的pod的切片
                            if _, ok := duplicatePods[ownerKey]; !ok {
                                duplicatePods[ownerKey] = make(map[string][]*v1.Pod)
                            }
                            duplicatePods[ownerKey][node.Name] = append(duplicatePods[ownerKey][node.Name], pod)
                        }
                        break
                    }
                }
                if !matched {
                    // Found no matches, add this list of keys to the list of lists that have the same first key
                    duplicateKeysMap[podContainerKeys[0]] = append(duplicateKeysMap[podContainerKeys[0]], podContainerKeys)
                }
            else {
                // This is the first pod we've seen that has this first "key" entry
                // 当Map中不存在某个Pod生成的重复性校验的key时(例如,这组pods中的第一个pod),就把它添加进去
                duplicateKeysMap[podContainerKeys[0]] = [][]string{podContainerKeys}
            }
        }
    }
 
    // 1. how many pods can be evicted to respect uniform placement of pods among viable nodes?
    for ownerKey, podNodes := range duplicatePods {
        // 查看将要被驱逐的节点还可以再哪些节点上运行
        targetNodes := getTargetNodes(podNodes, nodes)
 
        klog.V(2).InfoS("Adjusting feasible nodes""owner", ownerKey, "from", nodeCount, "to", len(targetNodes))
        // 如果要被驱逐的pod可选用的node数量不足,就不进行本次的驱逐操作
        if len(targetNodes) < 2 {
            klog.V(1).InfoS("Less than two feasible nodes for duplicates to land, skipping eviction""owner", ownerKey)
            continue
        }
 
        upperAvg := int(math.Ceil(float64(ownerKeyOccurence[ownerKey]) / float64(len(targetNodes))))
        for nodeName, pods := range podNodes {
            klog.V(2).InfoS("Average occurrence per node""node", klog.KObj(nodeMap[nodeName]), "ownerKey", ownerKey, "avg", upperAvg)
            // list of duplicated pods does not contain the original referential pod
            if len(pods)+1 > upperAvg {
                // It's assumed all duplicated pods are in the same priority class
                // TODO(jchaloup): check if the pod has a different node to lend to
                for _, pod := range pods[upperAvg-1:] {
                    if _, err := podEvictor.EvictPod(ctx, pod, nodeMap[nodeName], "RemoveDuplicatePods"); err != nil {
                        klog.ErrorS(err, "Error evicting pod""pod", klog.KObj(pod))
                        break
                    }
                }
            }
        }
    }
}

 

 
posted @ 2022-02-22 17:53  DogTwo  阅读(209)  评论(0编辑  收藏  举报