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  }