k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/util/cluster.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package util 18 19 import ( 20 "fmt" 21 "strings" 22 23 corev1 "k8s.io/api/core/v1" 24 clientset "k8s.io/client-go/kubernetes" 25 "k8s.io/klog/v2" 26 "k8s.io/perf-tests/clusterloader2/pkg/framework/client" 27 ) 28 29 const keyMasterNodeLabel = "node-role.kubernetes.io/master" 30 const keyControlPlaneNodeLabel = "node-role.kubernetes.io/control-plane" 31 32 // Based on the following docs: 33 // https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions 34 // https://kubernetes.io/docs/reference/labels-annotations-taints/ 35 var builtInTaintsKeys = []string{ 36 "node.kubernetes.io/not-ready", 37 "node.kubernetes.io/unreachable", 38 "node.kubernetes.io/pid-pressure", 39 "node.kubernetes.io/out-of-disk", 40 "node.kubernetes.io/memory-pressure", 41 "node.kubernetes.io/disk-pressure", 42 "node.kubernetes.io/network-unavailable", 43 "node.kubernetes.io/unschedulable", 44 "node.cloudprovider.kubernetes.io/uninitialized", 45 "node.cloudprovider.kubernetes.io/shutdown", 46 } 47 48 // GetSchedulableUntainedNodesNumber returns number of nodes in the cluster. 49 func GetSchedulableUntainedNodesNumber(c clientset.Interface) (int, error) { 50 nodes, err := GetSchedulableUntainedNodes(c) 51 return len(nodes), err 52 } 53 54 // GetSchedulableUntainedNodes returns nodes in the cluster. 55 func GetSchedulableUntainedNodes(c clientset.Interface) ([]corev1.Node, error) { 56 nodeList, err := client.ListNodes(c) 57 if err != nil { 58 return nil, err 59 } 60 var filtered []corev1.Node 61 for i := range nodeList { 62 if IsNodeSchedulableAndUntainted(&nodeList[i]) { 63 filtered = append(filtered, nodeList[i]) 64 } 65 } 66 return filtered, err 67 } 68 69 // LogClusterNodes prints nodes information (name, internal ip, external ip) to log. 70 func LogClusterNodes(c clientset.Interface) error { 71 nodeList, err := client.ListNodes(c) 72 if err != nil { 73 return err 74 } 75 klog.V(2).Infof("Listing cluster nodes:") 76 for i := range nodeList { 77 var internalIP, externalIP string 78 isSchedulable := IsNodeSchedulableAndUntainted(&nodeList[i]) 79 for _, address := range nodeList[i].Status.Addresses { 80 if address.Type == corev1.NodeInternalIP { 81 internalIP = address.Address 82 } 83 if address.Type == corev1.NodeExternalIP { 84 externalIP = address.Address 85 } 86 } 87 klog.V(2).Infof("Name: %v, clusterIP: %v, externalIP: %v, isSchedulable: %v", nodeList[i].ObjectMeta.Name, internalIP, externalIP, isSchedulable) 88 } 89 return nil 90 } 91 92 // IsNodeSchedulableAndUntainted returns true whether node is schedulable and untainted. 93 func IsNodeSchedulableAndUntainted(node *corev1.Node) bool { 94 return isNodeSchedulable(node) && isNodeUntainted(node) 95 } 96 97 // Node is schedulable if: 98 // 1) doesn't have "unschedulable" field set 99 // 2) it's Ready condition is set to true 100 // 3) doesn't have NetworkUnavailable condition set to true 101 func isNodeSchedulable(node *corev1.Node) bool { 102 nodeReady := isNodeConditionSetAsExpected(node, corev1.NodeReady, true) 103 networkReady := isNodeConditionUnset(node, corev1.NodeNetworkUnavailable) || 104 isNodeConditionSetAsExpected(node, corev1.NodeNetworkUnavailable, false) 105 return !node.Spec.Unschedulable && nodeReady && networkReady 106 } 107 108 // Tests whether node doesn't have any built-in taint with "NoSchedule" or "NoExecute" effect. 109 func isNodeUntainted(node *corev1.Node) bool { 110 for _, nodeTaint := range node.Spec.Taints { 111 if nodeTaint.Effect != corev1.TaintEffectNoSchedule && nodeTaint.Effect != corev1.TaintEffectNoExecute { 112 continue 113 } 114 for _, evictingTaintKey := range builtInTaintsKeys { 115 if nodeTaint.Key == evictingTaintKey { 116 return false 117 } 118 } 119 } 120 return true 121 } 122 123 func isNodeConditionSetAsExpected(node *corev1.Node, conditionType corev1.NodeConditionType, wantTrue bool) bool { 124 // Check the node readiness condition (logging all). 125 for _, cond := range node.Status.Conditions { 126 // Ensure that the condition type and the status matches as desired. 127 if cond.Type == conditionType { 128 if wantTrue == (cond.Status == corev1.ConditionTrue) { 129 return true 130 } 131 klog.V(4).Infof("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v", 132 conditionType, node.Name, cond.Status == corev1.ConditionTrue, wantTrue, cond.Reason, cond.Message) 133 return false 134 } 135 } 136 klog.V(4).Infof("Couldn't find condition %v on node %v", conditionType, node.Name) 137 return false 138 } 139 140 func isNodeConditionUnset(node *corev1.Node, conditionType corev1.NodeConditionType) bool { 141 for _, cond := range node.Status.Conditions { 142 if cond.Type == conditionType { 143 return false 144 } 145 } 146 return true 147 } 148 149 // GetMasterName returns master node name. 150 func GetMasterName(c clientset.Interface) (string, error) { 151 nodeList, err := client.ListNodes(c) 152 if err != nil { 153 return "", err 154 } 155 for i := range nodeList { 156 if LegacyIsMasterNode(&nodeList[i]) || IsControlPlaneNode(&nodeList[i]) { 157 return nodeList[i].Name, nil 158 } 159 } 160 return "", fmt.Errorf("master node not found") 161 } 162 163 // GetMasterIPs returns master node ips of the given type. 164 func GetMasterIPs(c clientset.Interface, addressType corev1.NodeAddressType) ([]string, error) { 165 nodeList, err := client.ListNodes(c) 166 if err != nil { 167 return nil, err 168 } 169 var ips []string 170 for i := range nodeList { 171 if LegacyIsMasterNode(&nodeList[i]) || IsControlPlaneNode(&nodeList[i]) { 172 for _, address := range nodeList[i].Status.Addresses { 173 if address.Type == addressType && address.Address != "" { 174 ips = append(ips, address.Address) 175 break 176 } 177 } 178 } 179 } 180 if len(ips) == 0 { 181 return nil, fmt.Errorf("didn't find any %s master IPs", addressType) 182 } 183 return ips, nil 184 } 185 186 // LegacyIsMasterNode returns true if given node is a registered master according 187 // to the logic historically used for this function. This code path is deprecated 188 // and the node disruption exclusion label should be used in the future. 189 // This code will not be allowed to update to use the node-role label, since 190 // node-roles may not be used for feature enablement. 191 // DEPRECATED: this will be removed in Kubernetes 1.19 192 func LegacyIsMasterNode(node *corev1.Node) bool { 193 for key := range node.GetLabels() { 194 if key == keyMasterNodeLabel { 195 return true 196 } 197 } 198 199 // We are trying to capture "master(-...)?$" regexp. 200 // However, using regexp.MatchString() results even in more than 35% 201 // of all space allocations in ControllerManager spent in this function. 202 // That's why we are trying to be a bit smarter. 203 nodeName := node.GetName() 204 if strings.HasSuffix(nodeName, "master") { 205 return true 206 } 207 if len(nodeName) >= 10 { 208 return strings.HasSuffix(nodeName[:len(nodeName)-3], "master-") 209 } 210 return false 211 } 212 213 func IsControlPlaneNode(node *corev1.Node) bool { 214 for key := range node.GetLabels() { 215 if key == keyControlPlaneNodeLabel { 216 return true 217 } 218 } 219 return false 220 }