github.com/jenkins-x/jx/v2@v2.1.155/pkg/kube/cluster_status.go (about)

     1  package kube
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/jenkins-x/jx/v2/pkg/util"
     8  
     9  	"github.com/jenkins-x/jx-logging/pkg/log"
    10  	v1 "k8s.io/api/core/v1"
    11  	"k8s.io/apimachinery/pkg/api/errors"
    12  	"k8s.io/apimachinery/pkg/api/resource"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/fields"
    15  	"k8s.io/client-go/kubernetes"
    16  )
    17  
    18  type NodeStatus struct {
    19  	Name               string
    20  	AllocatableMemory  *resource.Quantity
    21  	AllocatableCPU     *resource.Quantity
    22  	AllocatableStorage *resource.Quantity
    23  
    24  	CpuRequests resource.Quantity
    25  	CpuLimits   resource.Quantity
    26  	MemRequests resource.Quantity
    27  	MemLimits   resource.Quantity
    28  
    29  	DiskRequests              resource.Quantity
    30  	DiskLimits                resource.Quantity
    31  	numberOfNonTerminatedPods int
    32  }
    33  
    34  type ClusterStatus struct {
    35  	Name                   string
    36  	nodeCount              int
    37  	totalUsedMemory        resource.Quantity
    38  	totalUsedCpu           resource.Quantity
    39  	totalAllocatableMemory resource.Quantity
    40  	totalAllocatableCpu    resource.Quantity
    41  }
    42  
    43  func GetClusterStatus(client kubernetes.Interface, namespace string, verbose bool) (ClusterStatus, error) {
    44  
    45  	clusterStatus := ClusterStatus{
    46  		totalAllocatableCpu:    resource.Quantity{},
    47  		totalAllocatableMemory: resource.Quantity{},
    48  		totalUsedCpu:           resource.Quantity{},
    49  		totalUsedMemory:        resource.Quantity{},
    50  	}
    51  
    52  	kuber := NewKubeConfig()
    53  	config, _, err := kuber.LoadConfig()
    54  	if err != nil {
    55  		return clusterStatus, err
    56  	}
    57  
    58  	if config != nil {
    59  		context := CurrentContext(config)
    60  		if context != nil {
    61  			clusterStatus.Name = context.Cluster
    62  		}
    63  	}
    64  
    65  	nodes, err := client.CoreV1().Nodes().List(metav1.ListOptions{})
    66  
    67  	if err != nil {
    68  		return clusterStatus, err
    69  	}
    70  	clusterStatus.nodeCount = len(nodes.Items)
    71  	if clusterStatus.nodeCount < 1 {
    72  		msg := fmt.Sprintf("Number of nodes in cluster  = %d which is not sufficient", clusterStatus.nodeCount)
    73  		log.Logger().Fatal(msg)
    74  		err = errors.NewServiceUnavailable(msg)
    75  		return clusterStatus, err
    76  	}
    77  	for _, node := range nodes.Items {
    78  		if verbose {
    79  			log.Logger().Infof("\n-------------------------")
    80  			log.Logger().Infof("Node:\n%s\n", node.Name)
    81  		}
    82  
    83  		nodeStatus, err := Status(client, namespace, node, verbose)
    84  		if err != nil {
    85  			return clusterStatus, err
    86  		}
    87  		clusterStatus.totalAllocatableMemory.Add(*nodeStatus.AllocatableMemory)
    88  		clusterStatus.totalAllocatableCpu.Add(*nodeStatus.AllocatableCPU)
    89  
    90  		clusterStatus.totalUsedMemory.Add(nodeStatus.MemRequests)
    91  		clusterStatus.totalUsedCpu.Add(nodeStatus.CpuRequests)
    92  	}
    93  
    94  	return clusterStatus, nil
    95  }
    96  
    97  func (clusterStatus *ClusterStatus) MinimumResourceLimit() int {
    98  	return 80
    99  }
   100  
   101  func (clusterStatus *ClusterStatus) AverageCpuPercent() int {
   102  	return int((clusterStatus.totalUsedCpu.Value() * 100) / clusterStatus.totalAllocatableCpu.Value())
   103  }
   104  
   105  func (clusterStatus *ClusterStatus) AverageMemPercent() int {
   106  	return int((clusterStatus.totalUsedMemory.Value() * 100) / clusterStatus.totalAllocatableMemory.Value())
   107  }
   108  
   109  func (clusterStatus *ClusterStatus) NodeCount() int {
   110  	return clusterStatus.nodeCount
   111  }
   112  
   113  func (clusterStatus *ClusterStatus) CheckResource() string {
   114  	status := []string{}
   115  	if clusterStatus.AverageMemPercent() >= clusterStatus.MinimumResourceLimit() {
   116  		status = append(status, "needs more free memory")
   117  	}
   118  	if clusterStatus.AverageCpuPercent() >= clusterStatus.MinimumResourceLimit() {
   119  		status = append(status, "needs more free compute")
   120  	}
   121  	return strings.Join(status, ", ")
   122  }
   123  
   124  func (clusterStatus *ClusterStatus) Info() string {
   125  	str := fmt.Sprintf("Cluster(%s): %d nodes, memory %d%% of %s, cpu %d%% of %s",
   126  		clusterStatus.Name,
   127  		clusterStatus.NodeCount(),
   128  		clusterStatus.AverageMemPercent(),
   129  		clusterStatus.totalAllocatableMemory.String(),
   130  		clusterStatus.AverageCpuPercent(),
   131  		clusterStatus.totalAllocatableCpu.String())
   132  	return str
   133  }
   134  
   135  func Status(client kubernetes.Interface, namespace string, node v1.Node, verbose bool) (NodeStatus, error) {
   136  	nodeStatus := NodeStatus{}
   137  	fieldSelector, err := fields.ParseSelector("spec.nodeName=" + node.Name + ",status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed))
   138  	if err != nil {
   139  		return nodeStatus, err
   140  	}
   141  
   142  	allocatable := node.Status.Capacity
   143  	if len(node.Status.Allocatable) > 0 {
   144  		allocatable = node.Status.Allocatable
   145  	}
   146  
   147  	nodeStatus.Name = node.Name
   148  
   149  	nodeStatus.AllocatableCPU = allocatable.Cpu()
   150  	nodeStatus.AllocatableMemory = allocatable.Memory()
   151  	nodeStatus.AllocatableStorage = allocatable.StorageEphemeral()
   152  
   153  	// in a policy aware setting, users may have access to a node, but not all pods
   154  	// in that case, we note that the user does not have access to the pods
   155  
   156  	nodeNonTerminatedPodsList, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{FieldSelector: fieldSelector.String()})
   157  	if err != nil {
   158  		if !errors.IsForbidden(err) {
   159  			return nodeStatus, err
   160  		}
   161  	}
   162  
   163  	nodeStatus.numberOfNonTerminatedPods = len(nodeNonTerminatedPodsList.Items)
   164  
   165  	reqs, limits := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList, verbose)
   166  
   167  	cpuReqs, cpuLimits, memoryReqs, memoryLimits, diskReqs, diskLimits := reqs[v1.ResourceCPU], limits[v1.ResourceCPU], reqs[v1.ResourceMemory], limits[v1.ResourceMemory], reqs[v1.ResourceEphemeralStorage], limits[v1.ResourceEphemeralStorage]
   168  
   169  	if verbose {
   170  		cpuPercent := (cpuReqs.Value() * 100) / allocatable.Cpu().Value()
   171  		cpuMessage := ""
   172  		if cpuReqs.Value() > allocatable.Cpu().Value() {
   173  			cpuMessage = " - Node appears to be overcommitted on CPU"
   174  		}
   175  
   176  		log.Logger().Infof("CPU usage %v%% %s", cpuPercent, util.ColorWarning(cpuMessage))
   177  
   178  		memoryPercent := (memoryReqs.Value() * 100) / allocatable.Memory().Value()
   179  		memoryMessage := ""
   180  		if memoryReqs.Value() > allocatable.Memory().Value() {
   181  			memoryMessage = " - Node appears to be overcommitted on Memory"
   182  		}
   183  
   184  		log.Logger().Infof("Memory usage %v%%%s", memoryPercent, util.ColorWarning(memoryMessage))
   185  	}
   186  
   187  	nodeStatus.CpuRequests = cpuReqs
   188  	nodeStatus.CpuLimits = cpuLimits
   189  	nodeStatus.MemRequests = memoryReqs
   190  	nodeStatus.MemLimits = memoryLimits
   191  
   192  	nodeStatus.DiskRequests = diskReqs
   193  	nodeStatus.DiskLimits = diskLimits
   194  
   195  	return nodeStatus, nil
   196  }
   197  
   198  func getPodsTotalRequestsAndLimits(podList *v1.PodList, verbose bool) (reqs map[v1.ResourceName]resource.Quantity, limits map[v1.ResourceName]resource.Quantity) {
   199  	reqs, limits = map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{}
   200  
   201  	if verbose {
   202  		log.Logger().Infof("Pods:")
   203  	}
   204  
   205  	for _, p := range podList.Items {
   206  		pod := p
   207  		podReqs, podLimits := PodRequestsAndLimits(&pod)
   208  
   209  		if verbose {
   210  			messages := []string{}
   211  
   212  			if _, ok := podReqs[v1.ResourceCPU]; !ok {
   213  				messages = append(messages, "No CPU request set")
   214  			}
   215  
   216  			if _, ok := podReqs[v1.ResourceMemory]; !ok {
   217  				messages = append(messages, "No Memory request set")
   218  			}
   219  
   220  			log.Logger().Infof("%s - %s %s", pod.Name, pod.Status.Phase, util.ColorError(strings.Join(messages, ", ")))
   221  		}
   222  
   223  		for podReqName, podReqValue := range podReqs {
   224  			if value, ok := reqs[podReqName]; !ok {
   225  				reqs[podReqName] = podReqValue.DeepCopy()
   226  			} else {
   227  				value.Add(podReqValue)
   228  				reqs[podReqName] = value
   229  			}
   230  		}
   231  		for podLimitName, podLimitValue := range podLimits {
   232  			if value, ok := limits[podLimitName]; !ok {
   233  				limits[podLimitName] = podLimitValue.DeepCopy()
   234  			} else {
   235  				value.Add(podLimitValue)
   236  				limits[podLimitName] = value
   237  			}
   238  		}
   239  	}
   240  
   241  	if verbose {
   242  		log.Logger().Infof("")
   243  	}
   244  
   245  	return
   246  }
   247  
   248  func PodRequestsAndLimits(pod *v1.Pod) (reqs map[v1.ResourceName]resource.Quantity, limits map[v1.ResourceName]resource.Quantity) {
   249  	reqs, limits = map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{}
   250  	for _, container := range pod.Spec.Containers {
   251  		for name, quantity := range container.Resources.Requests {
   252  			if value, ok := reqs[name]; !ok {
   253  				reqs[name] = quantity.DeepCopy()
   254  			} else {
   255  				value.Add(quantity)
   256  				reqs[name] = value
   257  			}
   258  		}
   259  		for name, quantity := range container.Resources.Limits {
   260  			if value, ok := limits[name]; !ok {
   261  				limits[name] = quantity.DeepCopy()
   262  			} else {
   263  				value.Add(quantity)
   264  				limits[name] = value
   265  			}
   266  		}
   267  	}
   268  	// init containers define the minimum of any resource
   269  	for _, container := range pod.Spec.InitContainers {
   270  		for name, quantity := range container.Resources.Requests {
   271  			value, ok := reqs[name]
   272  			if !ok {
   273  				reqs[name] = quantity.DeepCopy()
   274  				continue
   275  			}
   276  			if quantity.Cmp(value) > 0 {
   277  				reqs[name] = quantity.DeepCopy()
   278  			}
   279  		}
   280  		for name, quantity := range container.Resources.Limits {
   281  			value, ok := limits[name]
   282  			if !ok {
   283  				limits[name] = quantity.DeepCopy()
   284  				continue
   285  			}
   286  			if quantity.Cmp(value) > 0 {
   287  				limits[name] = quantity.DeepCopy()
   288  			}
   289  		}
   290  	}
   291  	return
   292  }
   293  
   294  func RoleBindings(client kubernetes.Interface, namespace string) (string, error) {
   295  	binding, err := client.RbacV1().RoleBindings(namespace).Get("", metav1.GetOptions{})
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  
   300  	result := ""
   301  
   302  	for _, s := range binding.Subjects {
   303  		result += fmt.Sprintf("%s\t%s\t%s\n", s.Kind, s.Name, s.Namespace)
   304  	}
   305  
   306  	return result, nil
   307  }