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 }