From b163bff2884f44a800225f36b54146e258ffe10f Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Tue, 24 Jan 2023 23:35:29 +0200 Subject: [PATCH 1/3] Add `--exclude-node-labels` flag Added a new flag `--exclude-node-labels` to exclude nodes with the provided label selector. # Behavior and examples I'm testing with the following cluster configuration: ```yaml nodes: - role: control-plane labels: color: red - role: worker labels: color: red shape: square - role: worker labels: color: red - role: worker labels: shape: square ``` ## All nodes ```shell $ ./kube-capacity NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS * 1250m (2%) 400m (0%) 440Mi (1%) 540Mi (1%) kind-control-plane 950m (7%) 100m (0%) 290Mi (3%) 390Mi (4%) kind-worker 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) kind-worker2 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) kind-worker3 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) ``` ## Only nodes with `color=red` ```shell $ ./kube-capacity --node-labels color=red NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS * 1150m (3%) 300m (0%) 390Mi (1%) 490Mi (2%) kind-control-plane 950m (7%) 100m (0%) 290Mi (3%) 390Mi (4%) kind-worker 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) kind-worker2 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) ``` ## Only nodes with `shape=square` ```shell $ ./kube-capacity --node-labels shape=square NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS * 200m (0%) 200m (0%) 100Mi (0%) 100Mi (0%) kind-worker 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) kind-worker3 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) ``` ## Nodes with `color=red`, excluding those with `shape=square` ```shell $ ./kube-capacity --node-labels color=red --exclude-node-labels shape=square NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS * 1050m (4%) 200m (0%) 340Mi (2%) 440Mi (2%) kind-control-plane 950m (7%) 100m (0%) 290Mi (3%) 390Mi (4%) kind-worker2 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) ``` ## All nodes except those that have both `color=red` and `shape=square` ```shell $ ./kube-capacity --exclude-node-labels color=red,shape=square NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS * 1150m (3%) 300m (0%) 390Mi (1%) 490Mi (2%) kind-control-plane 950m (7%) 100m (0%) 290Mi (3%) 390Mi (4%) kind-worker2 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) kind-worker3 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) ``` Signed-off-by: Yarden Shoham --- README.md | 1 + pkg/capacity/capacity.go | 37 +++++++++++++++++++++++++++++++------ pkg/cmd/root.go | 5 ++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 623680a8..08ad9c02 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ kube-capacity --pod-labels app=nginx kube-capacity --namespace default kube-capacity --namespace-labels team=api kube-capacity --node-labels kubernetes.io/role=node +kube-capacity --exclude-node-labels kubernetes.io/os=linux ``` ### JSON and YAML Output diff --git a/pkg/capacity/capacity.go b/pkg/capacity/capacity.go index 37aefe98..6b71aa92 100644 --- a/pkg/capacity/capacity.go +++ b/pkg/capacity/capacity.go @@ -29,14 +29,14 @@ import ( ) // FetchAndPrint gathers cluster resource data and outputs it -func FetchAndPrint(showContainers, showPods, showUtil, showPodCount, availableFormat bool, podLabels, nodeLabels, namespaceLabels, namespace, kubeContext, kubeConfig, output, sortBy string) { +func FetchAndPrint(showContainers, showPods, showUtil, showPodCount, availableFormat bool, podLabels, nodeLabels, excludeNodeLabels, namespaceLabels, namespace, kubeContext, kubeConfig, output, sortBy string) { clientset, err := kube.NewClientSet(kubeContext, kubeConfig) if err != nil { fmt.Printf("Error connecting to Kubernetes: %v\n", err) os.Exit(1) } - podList, nodeList := getPodsAndNodes(clientset, podLabels, nodeLabels, namespaceLabels, namespace) + podList, nodeList := getPodsAndNodes(clientset, podLabels, nodeLabels, excludeNodeLabels, namespaceLabels, namespace) var pmList *v1beta1.PodMetricsList var nmList *v1beta1.NodeMetricsList @@ -59,7 +59,7 @@ func FetchAndPrint(showContainers, showPods, showUtil, showPodCount, availableFo printList(&cm, showContainers, showPods, showUtil, showPodCount, showNamespace, output, sortBy, availableFormat) } -func getPodsAndNodes(clientset kubernetes.Interface, podLabels, nodeLabels, namespaceLabels, namespace string) (*corev1.PodList, *corev1.NodeList) { +func getPodsAndNodes(clientset kubernetes.Interface, podLabels, nodeLabels, excludeNodeLabels, namespaceLabels, namespace string) (*corev1.PodList, *corev1.NodeList) { nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{ LabelSelector: nodeLabels, }) @@ -76,13 +76,38 @@ func getPodsAndNodes(clientset kubernetes.Interface, podLabels, nodeLabels, name os.Exit(3) } - newPodItems := []corev1.Pod{} - nodes := map[string]bool{} + nodeNamesToExclude := map[string]bool{} + if excludeNodeLabels != "" { + excludeNodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{ + LabelSelector: excludeNodeLabels, + }) + if err != nil { + fmt.Printf("Error listing Nodes: %v\n", err) + os.Exit(2) + } + for _, node := range excludeNodeList.Items { + nodeNamesToExclude[node.GetName()] = true + } + } + for _, node := range nodeList.Items { - nodes[node.GetName()] = true + if !nodeNamesToExclude[node.GetName()] { + nodes[node.GetName()] = true + } + } + + newNodeItems := []corev1.Node{} + for _, node := range nodeList.Items { + if nodes[node.GetName()] { + newNodeItems = append(newNodeItems, node) + } } + nodeList.Items = newNodeItems + + newPodItems := []corev1.Pod{} + for _, pod := range podList.Items { if !nodes[pod.Spec.NodeName] { continue diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 3d39d050..be35ffb4 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -28,6 +28,7 @@ var showUtil bool var showPodCount bool var podLabels string var nodeLabels string +var excludeNodeLabels string var namespaceLabels string var namespace string var kubeContext string @@ -50,7 +51,7 @@ var rootCmd = &cobra.Command{ os.Exit(1) } - capacity.FetchAndPrint(showContainers, showPods, showUtil, showPodCount, availableFormat, podLabels, nodeLabels, + capacity.FetchAndPrint(showContainers, showPods, showUtil, showPodCount, availableFormat, podLabels, nodeLabels, excludeNodeLabels, namespaceLabels, namespace, kubeContext, kubeConfig, outputFormat, sortBy) }, } @@ -70,6 +71,8 @@ func init() { "pod-labels", "l", "", "labels to filter pods with") rootCmd.PersistentFlags().StringVarP(&nodeLabels, "node-labels", "", "", "labels to filter nodes with") + rootCmd.PersistentFlags().StringVarP(&excludeNodeLabels, + "exclude-node-labels", "", "", "labels to exclude nodes with") rootCmd.PersistentFlags().StringVarP(&namespaceLabels, "namespace-labels", "", "", "labels to filter namespaces with") rootCmd.PersistentFlags().StringVarP(&namespace, From ba4eb8265203bffd3ffd00cb5b34a57a3571d795 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Tue, 24 Jan 2023 23:37:15 +0200 Subject: [PATCH 2/3] Regenerate supported flags in README Signed-off-by: Yarden Shoham --- README.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 08ad9c02..da8d78ec 100644 --- a/README.md +++ b/README.md @@ -141,23 +141,21 @@ kube-capacity --pods --containers --util --output yaml ## Flags Supported ``` - -c, --containers includes containers in output - --context string context to use for Kubernetes config - -h, --help help for kube-capacity - -n, --namespace string only include pods from this namespace - --namespace-labels string labels to filter namespaces with - --node-labels string labels to filter nodes with - -o, --output string output format for information - (supports: [table json yaml]) - (default "table") - -a, --available includes quantity available instead of percentage used - -l, --pod-labels string labels to filter pods with - -p, --pods includes pods in output - --sort string attribute to sort results be (supports: - [cpu.util cpu.request cpu.limit mem.util mem.request mem.limit name]) - (default "name") - -u, --util includes resource utilization in output - --pod-count includes pod counts for each of the nodes and the whole cluster + -a, --available includes quantity available instead of percentage used + -c, --containers includes containers in output + --context string context to use for Kubernetes config + --exclude-node-labels string labels to exclude nodes with + -h, --help help for kube-capacity + --kubeconfig string kubeconfig file to use for Kubernetes config + -n, --namespace string only include pods from this namespace + --namespace-labels string labels to filter namespaces with + --node-labels string labels to filter nodes with + -o, --output string output format for information (supports: [table json yaml]) (default "table") + --pod-count includes pod count per node in output + -l, --pod-labels string labels to filter pods with + -p, --pods includes pods in output + --sort string attribute to sort results by (supports: [cpu.util cpu.request cpu.limit mem.util mem.request mem.limit cpu.util.percentage cpu.request.percentage cpu.limit.percentage mem.util.percentage mem.request.percentage mem.limit.percentage name]) (default "name") + -u, --util includes resource utilization in output ``` ## Prerequisites From fa98dfc868d240a999c8b6ce7d76e78b03b3ceb3 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Tue, 24 Jan 2023 23:44:29 +0200 Subject: [PATCH 3/3] Update tests Signed-off-by: Yarden Shoham --- pkg/capacity/capacity_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/capacity/capacity_test.go b/pkg/capacity/capacity_test.go index a78ba3c3..28da313c 100644 --- a/pkg/capacity/capacity_test.go +++ b/pkg/capacity/capacity_test.go @@ -40,7 +40,7 @@ func TestGetPodsAndNodes(t *testing.T) { pod("mynode", "default", "mypod6", map[string]string{"g": "test"}), ) - podList, nodeList := getPodsAndNodes(clientset, "", "", "", "") + podList, nodeList := getPodsAndNodes(clientset, "", "", "", "", "") assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "another/mypod5", @@ -52,7 +52,7 @@ func TestGetPodsAndNodes(t *testing.T) { "other/mypod3", }, listPods(podList)) - podList, nodeList = getPodsAndNodes(clientset, "", "hello=world", "", "") + podList, nodeList = getPodsAndNodes(clientset, "", "hello=world", "", "", "") assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "another/mypod5", @@ -64,7 +64,7 @@ func TestGetPodsAndNodes(t *testing.T) { "other/mypod3", }, listPods(podList)) - podList, nodeList = getPodsAndNodes(clientset, "", "moon=lol", "", "") + podList, nodeList = getPodsAndNodes(clientset, "", "moon=lol", "", "", "") assert.Equal(t, []string{"mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "default/mypod4", @@ -72,19 +72,19 @@ func TestGetPodsAndNodes(t *testing.T) { "other/mypod3", }, listPods(podList)) - podList, nodeList = getPodsAndNodes(clientset, "a=test", "", "", "") + podList, nodeList = getPodsAndNodes(clientset, "a=test", "", "", "", "") assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "default/mypod", }, listPods(podList)) - podList, nodeList = getPodsAndNodes(clientset, "a=test,b!=test", "", "app=true", "") + podList, nodeList = getPodsAndNodes(clientset, "a=test,b!=test", "", "app=true", "", "") assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "default/mypod", }, listPods(podList)) - podList, nodeList = getPodsAndNodes(clientset, "a=test,b!=test", "", "", "default") + podList, nodeList = getPodsAndNodes(clientset, "a=test,b!=test", "", "", "default", "") assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList)) assert.Equal(t, []string{ "default/mypod",