Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-Friendly Dashboard Integration for Kubernetes Cluster Resource Usage Summary #3030

Open
ithesadson opened this issue Dec 12, 2024 · 3 comments

Comments

@ithesadson
Copy link




Is your feature request related to a problem? Please describe.
Currently, there is a lack of a simple and concise visualization for summarizing the overall resource usage (e.g., CPU, memory, pods) in the Kubernetes cluster. While K9s provides detailed metrics and pod-specific information, it does not offer a high-level overview of the cluster’s resource health and usage in a visually intuitive way. This makes it harder to quickly assess cluster health and manage resources efficiently.

Describe the solution you'd like
A feature that provides a dashboard-like summary for key resource usage metrics:

CPU: Allocatable vs. Reserved vs. Used.
Memory: Allocatable vs. Reserved vs. Used.
Pods: Total allocatable pods vs. currently used pods.
Node List: A list of nodes that are currently in use, highlighting roles (e.g., master, worker). ( Maybe Node list not neccessary, we can remove it.)

Optionally, the user should be able to include or exclude master nodes in the calculations.

Describe alternatives you've considered

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "strings"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/metrics/pkg/client/clientset/versioned"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func main() {
    // Define command-line flags
    var setContext string
    var includeMasters bool
    flag.StringVar(&setContext, "setContext", "minikube", "Set the desired context name")
    flag.BoolVar(&includeMasters, "includeMasters", false, "Set to true to include master nodes, false to exclude them")
    flag.Parse()

    // Load kubeconfig with the specified context
    configOverrides := &clientcmd.ConfigOverrides{CurrentContext: setContext}
    loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
    kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)

    config, err := kubeConfig.ClientConfig()
    if err != nil {
        log.Fatal(err)
    }

    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }

    metricsClientset, err := versioned.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }

    nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }

    var totalAllocatableCPU, totalUsedCPU, totalAllocatableMemory, totalUsedMemory, totalPods, usedPods int64
    var nodeNames []string // Slice to store node names

    for _, node := range nodes.Items {
        isMaster := false
        if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok {
            isMaster = true
        }

        if includeMasters || !isMaster {
            nodeNames = append(nodeNames, node.Name) // Add node name to the slice
            allocatable := node.Status.Allocatable
            if cpuQuantity, ok := allocatable["cpu"]; ok {
                totalAllocatableCPU += cpuQuantity.MilliValue()
            }
            if memoryQuantity, ok := allocatable["memory"]; ok {
                totalAllocatableMemory += memoryQuantity.Value()
            }
            if podsQuantity, ok := allocatable["pods"]; ok {
                totalPods += podsQuantity.Value()
            }
        }
    }

    nodeMetrics, err := metricsClientset.MetricsV1beta1().NodeMetricses().List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }

    for _, nodeMetric := range nodeMetrics.Items {
        isMaster := false
        if _, ok := nodeMetric.Labels["node-role.kubernetes.io/master"]; ok {
            isMaster = true
        }

        if includeMasters || !isMaster {
            usage := nodeMetric.Usage
            if cpuUsage, ok := usage["cpu"]; ok {
                totalUsedCPU += cpuUsage.MilliValue()
            }
            if memoryUsage, ok := usage["memory"]; ok {
                totalUsedMemory += memoryUsage.Value()
            }
        }
    }

    pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }
    usedPods = int64(len(pods.Items))

    totalReservedCPU, totalReservedMemory := calculatePodRequests(clientset)

    fmt.Printf("Pods\n")
    printResourceBar(float64(usedPods), float64(totalPods), "Used", "pods")

    fmt.Printf("\nCPU\n")
    printResourceBar(float64(totalReservedCPU)/1000, float64(totalAllocatableCPU)/1000, "Reserved", "cores")
    printResourceBar(float64(totalUsedCPU)/1000, float64(totalAllocatableCPU)/1000, "Used", "cores")

    fmt.Printf("\nMemory\n")
    printResourceBar(float64(totalReservedMemory)/1024/1024/1024, float64(totalAllocatableMemory)/1024/1024/1024, "Reserved", "GiB")
    printResourceBar(float64(totalUsedMemory)/1024/1024/1024, float64(totalAllocatableMemory)/1024/1024/1024, "Used", "GiB")

    // Aesthetic node name display
    fmt.Printf("\n✨ Used Nodes: %d ✨\n", len(nodeNames))
    fmt.Println("⚡ Here are the nodes:")
    for _, name := range nodeNames {
        fmt.Printf(" 🚀 %s\n", name)
    }
}

func calculatePodRequests(clientset *kubernetes.Clientset) (int64, int64) {
    pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }

    var totalReservedCPU, totalReservedMemory int64

    for _, pod := range pods.Items {
        for _, container := range pod.Spec.Containers {
            resources := container.Resources
            if cpuQuantity, ok := resources.Requests["cpu"]; ok {
                totalReservedCPU += cpuQuantity.MilliValue()
            }
            if memoryQuantity, ok := resources.Requests["memory"]; ok {
                totalReservedMemory += memoryQuantity.Value()
            }
        }
    }

    return totalReservedCPU, totalReservedMemory
}

func printResourceBar(used, total float64, label string, unit string) {
    percentage := (used / total) * 100
    barLength := 30
    usedBars := int(percentage * float64(barLength) / 100)
    bar := strings.Repeat("\033[33m█\033[0m", usedBars) + strings.Repeat("\033[37m░\033[0m", barLength-usedBars)

    // Check if unit is memory and convert to TiB if necessary
    if unit == "GiB" || unit == "TiB" {
        if total > 1024 {
            used = used / 1024
            total = total / 1024
            unit = "TiB"
        } else {
            unit = "GiB"
        }
    }

    // Align numbers and percentages
    if label == "Used" && unit == "pods" {
        fmt.Printf("%-8s: %8d %s / %8d %s [%s] %6.2f%%\n", label, int(used), unit, int(total), unit, bar, percentage)
    } else {
        fmt.Printf("%-8s: %8.2f %s / %8.2f %s [%s] %6.2f%%\n", label, used, unit, total, unit, bar, percentage)
    }
}

Additional context
Rancher interface has this feature, what I want is to add this feature to k9s interface. Maybe it can appear when we type :capacity or it can appear on the top right of the main screen, I don't know. This is completely open to interpretation. (example screenshot)
image

This is the output of the code I shared in cli. I want to integrate this into k9s but I found it very complicated and I couldn't find any contributing guide. I would appreciate it if someone who is familiar with the source code can help me integrate it.
image

@placintaalexandru
Copy link
Contributor

placintaalexandru commented Dec 28, 2024

hello @ithesadson

If you want to add those dashboards for pods, a good starting point is this function. That function adds all the functions you see in the top right pod view

If you think your dashboards make more sense for other resources as well (e.g the CPU & Memory can be shown also for nodes), you can have a look at the Browser struct to see how more general commands are added. An example is here where the yaml function makes sense for all resources. You can use Browser.viewCmd as an example on how to create a a live view and render your dashboards.

image

I couldn't find any contributing guide: there isn't one. I suggest to clone the repo, insert breakpoints into some places and run the app inside an IDE to check what happens when. This is what I did to have an understanding.

@derailed
Copy link
Owner

@ithesadson Did you try the pulses view?

@ithesadson
Copy link
Author

ithesadson commented Jan 6, 2025

@ithesadson Did you try the pulses view?

Yes I know this interface and I have tried it, but I think it only shows the currently used values.
I want to see two different interfaces as "Used & Reserved".

image

Currently, the interface displays the used CPU and Memory for resources, which is very helpful for real-time monitoring. However, as a user, I often find myself needing to see the requested CPU and Memory values as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants