From 90401174c238d7d5703c952b1339684a6fc515da Mon Sep 17 00:00:00 2001 From: Alex Krzos Date: Sun, 14 Feb 2021 16:16:36 -0500 Subject: [PATCH] Add ephemeral storage capacity metrics to namespace command --- .github/workflows/integration-kind.yml | 1 + README.md | 83 +++++++++++++++++--------- cmd/capacity/cluster.go | 2 +- cmd/capacity/namespace.go | 13 +++- cmd/capacity/node.go | 2 +- cmd/capacity/noderole.go | 2 +- internal/output/output.go | 58 ++++++++++++------ 7 files changed, 112 insertions(+), 49 deletions(-) diff --git a/.github/workflows/integration-kind.yml b/.github/workflows/integration-kind.yml index 41db2b7..4422d20 100644 --- a/.github/workflows/integration-kind.yml +++ b/.github/workflows/integration-kind.yml @@ -38,3 +38,4 @@ jobs: bin/kubectl-capacity no bin/kubectl-capacity no -e bin/kubectl-capacity ns + bin/kubectl-capacity ns -e diff --git a/README.md b/README.md index 8582c74..24149bf 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,13 @@ Total Ready Unready Unsch Capacity Allocatable Total Non-Term Avail Capacity 3 3 0 0 330 330 13 13 317 12.0 12.0 1.1 0.3 10.9 5.8 5.8 0.3 0.5 5.5 ``` +Flags: + +- `-e, --ephemeral-storage` flag includes ephemeral storage capacity data in table output view. + ### Node-Role -Capacity data aggregated and grouped by node-role can be displayed with the `node-role` sub-command. This is helpful to see the available space to deploy an application on your kubernetes cluster by looking at the worker/compute node-role available metrics (pods, cpu, memory). +Capacity data aggregated and grouped by node-role can be displayed with the `node-role` sub-command. This is helpful to see the available space to deploy an application on your kubernetes cluster by looking at the worker/compute node-role available metrics (pods, cpu, memory, storage). ```console $ kubectl capacity node-role @@ -114,6 +118,7 @@ master 1 1 0 0 110 110 6 6 104 4.0 Flags: +- `-e, --ephemeral-storage` flag includes ephemeral storage capacity data in table output view. - `-u, --unassigned` flag includes a row of data on non-terminated pods that have not been assigned a node. Total counts could be confusing if looking at cluster level capacity data compared to node-role data if there are unassigned pods. ### Node @@ -131,6 +136,7 @@ NAME STATUS ROLES PODS CPU Flags: +- `-e, --ephemeral-storage` flag includes ephemeral storage capacity data in table output view. - `-r, --sort-by-role` flag sorts table output by node-role rather than node name. - `-t, --display-total` flag includes a row of data displaying totals for each column. - `-u, --unassigned` flag includes a row of data on non-terminated pods that have not been assigned a node. Total counts could be confusing if looking at cluster level capacity data compared to node data if there are unassigned pods. @@ -150,12 +156,13 @@ local-path-storage 1 1 0 0.0 0.0 0.0 0.0 Flags: - `-A, --all-namespaces` flag includes namespaces with 0 pods. +- `-e, --ephemeral-storage` flag includes ephemeral storage capacity data in table output view. - `-n, --namespace string` flag selects a specific namespace. - `-t, --display-total` flag includes a row of data displaying totals for each column. ### Output formats -kubeSize supports table, yaml, and json output formats. Table data is the default format and is designed to be read by humans. With table output, CPU metrics default to cores and Memory metrics into [GiB (gibibyte)](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units). +kubeSize supports table, yaml, and json output formats. Table data is the default format and is designed to be read by humans. With table output, CPU metrics default to cores, Memory metrics into [GiB (gibibyte)](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) and Storage metrics into [GB (gigabyte)](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) Flags: @@ -168,39 +175,49 @@ Examples: $ kubectl capacity c NODES PODS CPU (cores) MEMORY (GiB) Total Ready Unready Unsch Capacity Allocatable Total Non-Term Avail Capacity Allocatable Requests Limits Avail Capacity Allocatable Requests Limits Avail -1 1 0 0 110 110 9 9 101 4.0 4.0 0.8 0.1 3.1 1.9 1.9 0.2 0.4 1.8 +1 1 0 0 110 110 11 11 99 4.0 4.0 11.4 0.1 -7.5 1.9 1.9 0.4 0.4 1.6 $ kubectl capacity c -d -NODES PODS CPU MEMORY -Total Ready Unready Unsch Capacity Allocatable Total Non-Term Avail Capacity Allocatable Requests Limits Avail Capacity Allocatable Requests Limits Avail -1 1 0 0 110 110 9 9 101 4 4 850m 100m 3150m 2036452Ki 2036452Ki 190Mi 390Mi 1841892Ki +NODES PODS CPU MEMORY +Total Ready Unready Unsch Capacity Allocatable Total Non-Term Avail Capacity Allocatable Requests Limits Avail Capacity Allocatable Requests Limits Avail +1 1 0 0 110 110 11 11 99 4 4 11450m 100m -7450m 2036452Ki 2036452Ki 400Mi 390Mi 1626852Ki $ kubectl capacity c -o yaml TotalAllocatableCPU: "4" TotalAllocatableCPUCores: 4 +TotalAllocatableEphemeralStorage: 61255492Ki +TotalAllocatableEphemeralStorageGB: 62.725623807999995 TotalAllocatableMemory: 2036452Ki TotalAllocatableMemoryGiB: 1.9421119689941406 TotalAllocatablePods: "110" -TotalAvailableCPU: 3150m -TotalAvailableCPUCores: 3.15 -TotalAvailableMemory: 1841892Ki -TotalAvailableMemoryGiB: 1.7565650939941406 -TotalAvailablePods: 101 +TotalAvailableCPU: -7450m +TotalAvailableCPUCores: -7.45 +TotalAvailableEphemeralStorage: "59620766208" +TotalAvailableEphemeralStorageGB: 59.62076620799999 +TotalAvailableMemory: 1626852Ki +TotalAvailableMemoryGiB: 1.5514869689941406 +TotalAvailablePods: 99 TotalCapacityCPU: "4" TotalCapacityCPUCores: 4 +TotalCapacityEphemeralStorage: 61255492Ki +TotalCapacityEphemeralStorageGB: 62.725623807999995 TotalCapacityMemory: 2036452Ki TotalCapacityMemoryGiB: 1.9421119689941406 TotalCapacityPods: "110" TotalLimitsCPU: 100m TotalLimitsCPUCores: 0.1 +TotalLimitsEphemeralStorage: 3G +TotalLimitsEphemeralStorageGB: 3 TotalLimitsMemory: 390Mi TotalLimitsMemoryGiB: 0.380859375 TotalNodeCount: 1 -TotalNonTermPodCount: 9 -TotalPodCount: 9 +TotalNonTermPodCount: 11 +TotalPodCount: 11 TotalReadyNodeCount: 1 -TotalRequestsCPU: 850m -TotalRequestsCPUCores: 0.85 -TotalRequestsMemory: 190Mi -TotalRequestsMemoryGiB: 0.185546875 +TotalRequestsCPU: 11450m +TotalRequestsCPUCores: 11.45 +TotalRequestsEphemeralStorage: "3104857600" +TotalRequestsEphemeralStorageGB: 3.1048576000000003 +TotalRequestsMemory: 400Mi +TotalRequestsMemoryGiB: 0.390625 TotalUnreadyNodeCount: 0 TotalUnschedulableNodeCount: 0 $ kubectl capacity c -o json @@ -209,31 +226,41 @@ $ kubectl capacity c -o json "TotalReadyNodeCount": 1, "TotalUnreadyNodeCount": 0, "TotalUnschedulableNodeCount": 0, - "TotalPodCount": 9, - "TotalNonTermPodCount": 9, + "TotalPodCount": 11, + "TotalNonTermPodCount": 11, "TotalCapacityPods": "110", "TotalCapacityCPU": "4", "TotalCapacityCPUCores": 4, "TotalCapacityMemory": "2036452Ki", "TotalCapacityMemoryGiB": 1.9421119689941406, + "TotalCapacityEphemeralStorage": "61255492Ki", + "TotalCapacityEphemeralStorageGB": 62.725623807999995, "TotalAllocatablePods": "110", "TotalAllocatableCPU": "4", "TotalAllocatableCPUCores": 4, "TotalAllocatableMemory": "2036452Ki", "TotalAllocatableMemoryGiB": 1.9421119689941406, - "TotalAvailablePods": 101, - "TotalRequestsCPU": "850m", - "TotalRequestsCPUCores": 0.85, + "TotalAllocatableEphemeralStorage": "61255492Ki", + "TotalAllocatableEphemeralStorageGB": 62.725623807999995, + "TotalAvailablePods": 99, + "TotalRequestsCPU": "11450m", + "TotalRequestsCPUCores": 11.45, "TotalLimitsCPU": "100m", "TotalLimitsCPUCores": 0.1, - "TotalAvailableCPU": "3150m", - "TotalAvailableCPUCores": 3.15, - "TotalRequestsMemory": "190Mi", - "TotalRequestsMemoryGiB": 0.185546875, + "TotalAvailableCPU": "-7450m", + "TotalAvailableCPUCores": -7.45, + "TotalRequestsMemory": "400Mi", + "TotalRequestsMemoryGiB": 0.390625, "TotalLimitsMemory": "390Mi", "TotalLimitsMemoryGiB": 0.380859375, - "TotalAvailableMemory": "1841892Ki", - "TotalAvailableMemoryGiB": 1.7565650939941406 + "TotalAvailableMemory": "1626852Ki", + "TotalAvailableMemoryGiB": 1.5514869689941406, + "TotalRequestsEphemeralStorage": "3104857600", + "TotalRequestsEphemeralStorageGB": 3.1048576000000003, + "TotalLimitsEphemeralStorage": "3G", + "TotalLimitsEphemeralStorageGB": 3, + "TotalAvailableEphemeralStorage": "59620766208", + "TotalAvailableEphemeralStorageGB": 59.62076620799999 } ``` diff --git a/cmd/capacity/cluster.go b/cmd/capacity/cluster.go index 9c6afea..d971555 100644 --- a/cmd/capacity/cluster.go +++ b/cmd/capacity/cluster.go @@ -146,5 +146,5 @@ var clusterCmd = &cobra.Command{ func init() { rootCmd.AddCommand(clusterCmd) - clusterCmd.Flags().BoolP("ephemeral-storage", "e", false, "Display ephemeral storage") + clusterCmd.Flags().BoolP("ephemeral-storage", "e", false, "Include ephemeral storage capacity data in table output") } diff --git a/cmd/capacity/namespace.go b/cmd/capacity/namespace.go index d69c2ad..e6b6506 100644 --- a/cmd/capacity/namespace.go +++ b/cmd/capacity/namespace.go @@ -99,6 +99,8 @@ var namespaceCmd = &cobra.Command{ namespaceCapacityData[pod.Namespace].TotalLimitsCPU.Add(*container.Resources.Limits.Cpu()) namespaceCapacityData[pod.Namespace].TotalRequestsMemory.Add(*container.Resources.Requests.Memory()) namespaceCapacityData[pod.Namespace].TotalLimitsMemory.Add(*container.Resources.Limits.Memory()) + namespaceCapacityData[pod.Namespace].TotalRequestsEphemeralStorage.Add(*container.Resources.Requests.StorageEphemeral()) + namespaceCapacityData[pod.Namespace].TotalLimitsEphemeralStorage.Add(*container.Resources.Limits.StorageEphemeral()) } } } @@ -111,6 +113,8 @@ var namespaceCmd = &cobra.Command{ namespaceCapacityData[namespace].TotalLimitsCPUCores = capacity.ReadableCPU(namespaceCapacityData[namespace].TotalLimitsCPU) namespaceCapacityData[namespace].TotalRequestsMemoryGiB = capacity.ReadableMem(namespaceCapacityData[namespace].TotalRequestsMemory) namespaceCapacityData[namespace].TotalLimitsMemoryGiB = capacity.ReadableMem(namespaceCapacityData[namespace].TotalLimitsMemory) + namespaceCapacityData[namespace].TotalRequestsEphemeralStorageGB = capacity.ReadableStorage(namespaceCapacityData[namespace].TotalRequestsEphemeralStorage) + namespaceCapacityData[namespace].TotalLimitsEphemeralStorageGB = capacity.ReadableStorage(namespaceCapacityData[namespace].TotalLimitsEphemeralStorage) namespaceCapacityData["*total*"].TotalPodCount += namespaceCapacityData[namespace].TotalPodCount namespaceCapacityData["*total*"].TotalNonTermPodCount += namespaceCapacityData[namespace].TotalNonTermPodCount namespaceCapacityData["*total*"].TotalUnassignedNodePodCount += namespaceCapacityData[namespace].TotalUnassignedNodePodCount @@ -122,12 +126,18 @@ var namespaceCmd = &cobra.Command{ namespaceCapacityData["*total*"].TotalRequestsMemoryGiB += namespaceCapacityData[namespace].TotalRequestsMemoryGiB namespaceCapacityData["*total*"].TotalLimitsMemory.Add(namespaceCapacityData[namespace].TotalLimitsMemory) namespaceCapacityData["*total*"].TotalLimitsMemoryGiB += namespaceCapacityData[namespace].TotalLimitsMemoryGiB + namespaceCapacityData["*total*"].TotalRequestsEphemeralStorage.Add(namespaceCapacityData[namespace].TotalRequestsEphemeralStorage) + namespaceCapacityData["*total*"].TotalRequestsEphemeralStorageGB += namespaceCapacityData[namespace].TotalRequestsEphemeralStorageGB + namespaceCapacityData["*total*"].TotalLimitsEphemeralStorage.Add(namespaceCapacityData[namespace].TotalLimitsEphemeralStorage) + namespaceCapacityData["*total*"].TotalLimitsEphemeralStorageGB += namespaceCapacityData[namespace].TotalLimitsEphemeralStorageGB } sort.Strings(namespaceNames) displayDefault, _ := cmd.Flags().GetBool("default-format") + displayEphemeralStorage, _ := cmd.Flags().GetBool("ephemeral-storage") + displayNoHeaders, _ := cmd.Flags().GetBool("no-headers") displayFormat, _ := cmd.Flags().GetString("output") @@ -140,7 +150,7 @@ var namespaceCmd = &cobra.Command{ namespaceNames = append(namespaceNames, "*total*") } - output.DisplayNamespaceData(namespaceCapacityData, namespaceNames, displayDefault, !displayNoHeaders, displayFormat, displayAllNamespaces) + output.DisplayNamespaceData(namespaceCapacityData, namespaceNames, displayDefault, !displayNoHeaders, displayEphemeralStorage, displayFormat, displayAllNamespaces) return nil }, @@ -149,5 +159,6 @@ var namespaceCmd = &cobra.Command{ func init() { rootCmd.AddCommand(namespaceCmd) namespaceCmd.Flags().BoolP("all-namespaces", "A", false, "Include 0 pod namespaces in table output") + namespaceCmd.Flags().BoolP("ephemeral-storage", "e", false, "Include ephemeral storage capacity data in table output") namespaceCmd.Flags().BoolP("display-total", "t", false, "Display sum of all namespace capacity data in table output") } diff --git a/cmd/capacity/node.go b/cmd/capacity/node.go index 2ec4037..4bb870d 100644 --- a/cmd/capacity/node.go +++ b/cmd/capacity/node.go @@ -222,7 +222,7 @@ var nodeCmd = &cobra.Command{ func init() { rootCmd.AddCommand(nodeCmd) - nodeCmd.Flags().BoolP("ephemeral-storage", "e", false, "Display ephemeral storage") + nodeCmd.Flags().BoolP("ephemeral-storage", "e", false, "Include ephemeral storage capacity data in table output") nodeCmd.Flags().BoolP("sort-by-role", "r", false, "Sort output by node-role") nodeCmd.Flags().BoolP("display-total", "t", false, "Display sum of all node capacity data in table output") nodeCmd.Flags().BoolP("unassigned", "u", false, "Include unassigned pod row, pods which do not have a node") diff --git a/cmd/capacity/noderole.go b/cmd/capacity/noderole.go index 9c51afb..4077d42 100644 --- a/cmd/capacity/noderole.go +++ b/cmd/capacity/noderole.go @@ -179,6 +179,6 @@ var nodeRoleCmd = &cobra.Command{ func init() { rootCmd.AddCommand(nodeRoleCmd) - nodeRoleCmd.Flags().BoolP("ephemeral-storage", "e", false, "Display ephemeral storage") + nodeRoleCmd.Flags().BoolP("ephemeral-storage", "e", false, "Include ephemeral storage capacity data in table output") nodeRoleCmd.Flags().BoolP("unassigned", "u", false, "Include unassigned pod row, pods which do not have a node") } diff --git a/internal/output/output.go b/internal/output/output.go index 9424f41..8cdd044 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -120,17 +120,21 @@ type NodeCapacityData struct { } type NamespaceCapacityData struct { - TotalPodCount int - TotalNonTermPodCount int - TotalUnassignedNodePodCount int - TotalRequestsCPU resource.Quantity - TotalRequestsCPUCores float64 - TotalLimitsCPU resource.Quantity - TotalLimitsCPUCores float64 - TotalRequestsMemory resource.Quantity - TotalRequestsMemoryGiB float64 - TotalLimitsMemory resource.Quantity - TotalLimitsMemoryGiB float64 + TotalPodCount int + TotalNonTermPodCount int + TotalUnassignedNodePodCount int + TotalRequestsCPU resource.Quantity + TotalRequestsCPUCores float64 + TotalLimitsCPU resource.Quantity + TotalLimitsCPUCores float64 + TotalRequestsMemory resource.Quantity + TotalRequestsMemoryGiB float64 + TotalLimitsMemory resource.Quantity + TotalLimitsMemoryGiB float64 + TotalRequestsEphemeralStorage resource.Quantity + TotalRequestsEphemeralStorageGB float64 + TotalLimitsEphemeralStorage resource.Quantity + TotalLimitsEphemeralStorageGB float64 } func DisplayClusterData(clusterCapacityData ClusterCapacityData, displayDefault bool, displayHeaders bool, displayEphemeralStorage bool, displayFormat string) { @@ -394,7 +398,7 @@ func printNodeData(w *tabwriter.Writer, nodeName string, nodeData *NodeCapacityD } } -func DisplayNamespaceData(namespaceCapacityData map[string]*NamespaceCapacityData, sortedNamespaceNames []string, displayDefault bool, displayHeaders bool, displayFormat string, displayAllNamespaces bool) { +func DisplayNamespaceData(namespaceCapacityData map[string]*NamespaceCapacityData, sortedNamespaceNames []string, displayDefault bool, displayHeaders bool, displayEphemeralStorage bool, displayFormat string, displayAllNamespaces bool) { switch displayFormat { case jsonDisplay: jsonNamespaceData, err := json.MarshalIndent(&namespaceCapacityData, "", " ") @@ -415,11 +419,23 @@ func DisplayNamespaceData(namespaceCapacityData map[string]*NamespaceCapacityDat w.Init(os.Stdout, 0, 5, 1, ' ', 0) if displayHeaders { if displayDefault { - fmt.Fprintln(w, "NAMESPACE\tPODS\t\t\tCPU\t\tMEMORY\t\t") + fmt.Fprintf(w, "NAMESPACE\tPODS\t\t\tCPU\t\tMEMORY\t\t") + if displayEphemeralStorage { + fmt.Fprintf(w, "EPHEMERAL STORAGE") + } + fmt.Fprintln(w, "") } else { - fmt.Fprintln(w, "NAMESPACE\tPODS\t\t\tCPU (cores)\t\tMEMORY (GiB)\t\t") + fmt.Fprintf(w, "NAMESPACE\tPODS\t\t\tCPU (cores)\t\tMEMORY (GiB)\t\t") + if displayEphemeralStorage { + fmt.Fprintf(w, "EPHEMERAL STORAGE (GB)") + } + fmt.Fprintln(w, "") } - fmt.Fprintln(w, "\tTotal\tNon-Term\tUnassigned\tRequests\tLimits\tRequests\tLimits") + fmt.Fprintf(w, "\tTotal\tNon-Term\tUnassigned\tRequests\tLimits\tRequests\tLimits\t") + if displayEphemeralStorage { + fmt.Fprintf(w, "Requests\tLimits") + } + fmt.Fprintln(w, "") } for _, k := range sortedNamespaceNames { if (namespaceCapacityData[k].TotalPodCount != 0) || displayAllNamespaces { @@ -427,10 +443,18 @@ func DisplayNamespaceData(namespaceCapacityData map[string]*NamespaceCapacityDat fmt.Fprintf(w, "%d\t%d\t%d\t", namespaceCapacityData[k].TotalPodCount, namespaceCapacityData[k].TotalNonTermPodCount, namespaceCapacityData[k].TotalUnassignedNodePodCount) if displayDefault { fmt.Fprintf(w, "%s\t%s\t", &namespaceCapacityData[k].TotalRequestsCPU, &namespaceCapacityData[k].TotalLimitsCPU) - fmt.Fprintf(w, "%s\t%s\t\n", &namespaceCapacityData[k].TotalRequestsMemory, &namespaceCapacityData[k].TotalLimitsMemory) + fmt.Fprintf(w, "%s\t%s\t", &namespaceCapacityData[k].TotalRequestsMemory, &namespaceCapacityData[k].TotalLimitsMemory) + if displayEphemeralStorage { + fmt.Fprintf(w, "%s\t%s\t", &namespaceCapacityData[k].TotalRequestsEphemeralStorage, &namespaceCapacityData[k].TotalLimitsEphemeralStorage) + } + fmt.Fprintln(w, "") } else { fmt.Fprintf(w, "%.1f\t%.1f\t", namespaceCapacityData[k].TotalRequestsCPUCores, namespaceCapacityData[k].TotalLimitsCPUCores) - fmt.Fprintf(w, "%.1f\t%.1f\t\n", namespaceCapacityData[k].TotalRequestsMemoryGiB, namespaceCapacityData[k].TotalLimitsMemoryGiB) + fmt.Fprintf(w, "%.1f\t%.1f\t", namespaceCapacityData[k].TotalRequestsMemoryGiB, namespaceCapacityData[k].TotalLimitsMemoryGiB) + if displayEphemeralStorage { + fmt.Fprintf(w, "%.1f\t%.1f\t", namespaceCapacityData[k].TotalRequestsEphemeralStorageGB, namespaceCapacityData[k].TotalLimitsEphemeralStorageGB) + } + fmt.Fprintln(w, "") } } }