Skip to content

Commit

Permalink
adding initial readme and goreleaser config, a bit of code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
robscott committed Feb 3, 2019
1 parent 6355f74 commit 06fefab
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
main

# Test binary, built with `go test -c`
*.test
Expand Down
28 changes: 28 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
builds:
- env:
- CGO_ENABLED=0
archive:
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brew:
github:
owner: robscott
name: homebrew-tap
folder: Formula
description: Reverse Lookup for Kubernetes RBAC
test: |
system "#{bin}/kube-capacity version"
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# kube-capacity

[![Go Report Card](https://goreportcard.com/badge/github.com/robscott/kube-capacity)](https://goreportcard.com/report/github.com/robscott/kube-capacity)

This is a simple CLI that provides an overview of the resource requests, limits, and utilization in a Kubernetes cluster. It attempts to combine the best parts of the output from `kubectl top` and `kubectl describe` into an easy to use CLI focused on cluster resources.

## Installation
Go binaries are automatically built with each release by [GoReleaser](https://github.com/goreleaser/goreleaser). These can be accessed on the GitHub [releases page](github.com/robscott/kube-capacity/releases) for this project.

### Homebrew
This project can also be installed with Homebrew:
```
brew install robscott/tap/kube-capacity
```

## Usage
By default, kube-capacity will output a list of nodes with the total CPU and Memory resource requests and limits for all the pods running on them. For clusters with more than one node, the first line will also include cluster wide totals. That output will look something like this:

```bash
kube-capacity

NODE CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS
* 560m (28%) 130m (7%) 572Mi (9%) 770Mi (13%)
example-node-1 220m (22%) 10m (1%) 192Mi (6%) 360Mi (12%)
example-node-2 340m (34%) 120m (12%) 380Mi (13%) 410Mi (14%)
```

### Including Pods
For more detailed output, kube-capacity can include pods in the output. When `-p` or `--pods` are passed to kube-capacity, it will include pod specific output that looks like this:

```bash
kube-capacity --pods

NODE NAMESPACE POD CPU REQUESTS CPU LIMITS MEMORY REQUESTS MEMORY LIMITS
* * * 560m (28%) 780m (38%) 572Mi (9%) 770Mi (13%)

example-node-1 * * 220m (22%) 320m (32%) 192Mi (6%) 360Mi (12%)
example-node-1 kube-system metrics-server-lwc6z 100m (10%) 200m (20%) 100Mi (3%) 200Mi (7%)
example-node-1 kube-system coredns-7b5bcb98f8 120m (12%) 120m (12%) 92Mi (3%) 160Mi (5%)

example-node-2 * * 340m (34%) 460m (46%) 380Mi (13%) 410Mi (14%)
example-node-2 kube-system kube-proxy-3ki7 200m (20%) 280m (28%) 210Mi (7%) 210Mi (7%)
example-node-2 tiller tiller-deploy 140m (14%) 180m (18%) 170Mi (5%) 200Mi (7%)
```

### Including Utilization
To help understand how resource utilization compares to configured requests and limits, kube-capacity can include utilization metrics in the output. It's important to note that this output relies on [metrics-server](https://github.com/kubernetes-incubator/metrics-server) functioning correctly in your cluster. When `-u` or `--util` are passed to kube-capacity, it will include resource utilization information that looks like this:

```bash
kube-capacity --util

NODE CPU REQUESTS CPU LIMITS CPU UTIL MEMORY REQUESTS MEMORY LIMITS MEMORY UTIL
* 560m (28%) 130m (7%) 40m (2%) 572Mi (9%) 770Mi (13%) 470Mi (8%)
example-node-1 220m (22%) 10m (1%) 10m (1%) 192Mi (6%) 360Mi (12%) 210Mi (7%)
example-node-2 340m (34%) 120m (12%) 30m (3%) 380Mi (13%) 410Mi (14%) 260Mi (9%)
```

### Including Pods and Utilization
For more detailed output, kube-capacity can include both pods and resource utilization in the output. When `--util` and `--pods` are passed to kube-capacity, it will result in a wide output that looks like this:

```bash
kube-capacity --pods --util

NODE NAMESPACE POD CPU REQUESTS CPU LIMITS CPU UTIL MEMORY REQUESTS MEMORY LIMITS MEMORY UTIL
* * * 560m (28%) 780m (38%) 340m (17%) 572Mi (9%) 770Mi (13%) 470Mi (8%)

example-node-1 * * 220m (22%) 320m (32%) 160m (16%) 192Mi (6%) 360Mi (12%) 210Mi (7%)
example-node-1 kube-system metrics-server-lwc6z 100m (10%) 200m (20%) 70m (7%) 100Mi (3%) 200Mi (7%) 120Mi (4%)
example-node-1 kube-system coredns-7b5bcb98f8 120m (12%) 120m (12%) 90m (9%) 92Mi (3%) 160Mi (5%) 90Mi (3%)

example-node-2 * * 340m (34%) 460m (46%) 180m (18%) 380Mi (13%) 410Mi (14%) 260Mi (9%)
example-node-2 kube-system kube-proxy-3ki7 200m (20%) 280m (28%) 110m (11%) 210Mi (7%) 210Mi (7%) 120Mi (4%)
example-node-2 tiller tiller-deploy 140m (14%) 180m (18%) 70m (7%) 170Mi (6%) 200Mi (7%) 140Mi (5%)
```

It's worth noting that utilization numbers from pods will likely not add up to the total node utilization numbers. Unlike request and limit numbers where node and cluster level numbers represent a sum of pod values, node metrics come directly from metrics-server and will likely include other forms of resource utilization.

## Similar Projects
There are already some great projects out there that have similar goals.

- [kube-resource-report](https://github.com/hjacobs/kube-resource-report): generates HTML/CSS report for resource requests and limits across multiple clusters.
- [kubetop](https://github.com/LeastAuthority/kubetop): a CLI similar to top for Kubernetes, focused on resource utilization (not requests and limits).

## License
Apache License 2.0
52 changes: 35 additions & 17 deletions pkg/capacity/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,64 @@ import (
"fmt"

"github.com/robscott/kube-capacity/pkg/kube"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

// List gathers cluster resource data and outputs it
func List(args []string, showPods bool, showUtil bool) {
podList, nodeList := getPodsAndNodes()
pmList, nmList := getMetrics()
cm := buildClusterMetric(podList, pmList, nodeList, nmList)
printList(&cm, showPods, showUtil)
}

func getPodsAndNodes() (*corev1.PodList, *corev1.NodeList) {
clientset, err := kube.NewClientSet()
if err != nil {
fmt.Println("Error connecting to Kubernetes")
panic(err.Error())
}

nodeList, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error listing Nodes")
panic(err.Error())
}

podList, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error listing Nodes")
panic(err.Error())
}

return podList, nodeList
}

func getMetrics() (*v1beta1.PodMetricsList, *v1beta1.NodeMetricsList) {
mClientset, err := kube.NewMetricsClientSet()
if err != nil {
fmt.Println("Error connecting to Metrics Server")
panic(err.Error())
}

nodeList, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
nmList, err := mClientset.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error listing Nodes")
fmt.Println("Error getting metrics")
panic(err.Error())
}

podList, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
pmList, err := mClientset.MetricsV1beta1().PodMetricses("").List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error listing Nodes")
fmt.Println("Error getting metrics")
panic(err.Error())
}

return pmList, nmList
}

func buildClusterMetric(podList *corev1.PodList, pmList *v1beta1.PodMetricsList, nodeList *corev1.NodeList, nmList *v1beta1.NodeMetricsList) clusterMetric {
cm := clusterMetric{
cpu: &resourceMetric{},
memory: &resourceMetric{},
Expand Down Expand Up @@ -73,12 +103,6 @@ func List(args []string, showPods bool, showUtil bool) {
cm.addNodeMetric(cm.nodeMetrics[node.Name])
}

nmList, err := mClientset.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error getting metrics")
panic(err.Error())
}

for _, node := range nmList.Items {
nm := cm.nodeMetrics[node.GetName()]
cm.cpu.utilization.Add(node.Usage["cpu"])
Expand All @@ -87,12 +111,6 @@ func List(args []string, showPods bool, showUtil bool) {
nm.memory.utilization = node.Usage["memory"]
}

pmList, err := mClientset.MetricsV1beta1().PodMetricses("").List(metav1.ListOptions{})
if err != nil {
fmt.Println("Error getting metrics")
panic(err.Error())
}

for _, pod := range pmList.Items {
pm := cm.podMetrics[fmt.Sprintf("%s-%s", pod.GetNamespace(), pod.GetName())]
for _, container := range pod.Containers {
Expand All @@ -101,5 +119,5 @@ func List(args []string, showPods bool, showUtil bool) {
}
}

printList(&cm, showPods, showUtil)
return cm
}
4 changes: 2 additions & 2 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ var showUtil bool

var rootCmd = &cobra.Command{
Use: "kube-capacity",
Short: "kube-capacity provides an overview of the resource utilization in a Kubernetes cluster",
Long: "kube-capacity provides an overview of resources available for each node in a Kubernetes cluster",
Short: "kube-capacity provides an overview of the resource requests, limits, and utilization in a Kubernetes cluster",
Long: "kube-capacity provides an overview of the resource requests, limits, and utilization in a Kubernetes cluster",
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.ParseFlags(args); err != nil {
Expand Down
33 changes: 33 additions & 0 deletions pkg/cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2019 Rob Scott
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of kube-capacity",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("kube-capacity version 0.1.0")
},
}
4 changes: 4 additions & 0 deletions pkg/kube/clientset.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ import (
"k8s.io/client-go/tools/clientcmd"
metrics "k8s.io/metrics/pkg/client/clientset/versioned"

// Required for GKE
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// Required for OIDC
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)

// NewClientSet returns a new Kubernetes clientset
func NewClientSet() (*kubernetes.Clientset, error) {
config, err := getKubeConfig()
if err != nil {
Expand All @@ -37,6 +40,7 @@ func NewClientSet() (*kubernetes.Clientset, error) {
return kubernetes.NewForConfig(config)
}

// NewMetricsClientSet returns a new clientset for Kubernetes metrics
func NewMetricsClientSet() (*metrics.Clientset, error) {
config, err := getKubeConfig()
if err != nil {
Expand Down

0 comments on commit 06fefab

Please sign in to comment.