Background
My friend and I wanted to get more involved in the CNCF (Cloud Native Computing Foundation) / Kubernetes ecosystem by making a kubectl
plugin. We have both been writing golang at our jobs, but wanted an excuse to play with some of the Kubernetes client-go libraries so we could natively talk to the Kubernetes API server. This blog post will talk about what kurt
does, some of the things we are doing around tech/automation, and how we got kurt
into the krew
package ecosystem.
kurt
We made kurt
to automate the troubleshooting task of seeing what is restarting in your cluster by organizing pod restart counts into groupings that will help give you context. Kubernetes can be good at hiding application issues from an operator by restarting pods automatically when there are issues. If you are not looking for pod restarts, you might have a significant problem that Kubernetes gracefully hides from you. This is an awesome feature of Kubernetes, but when you first jump into troubleshooting what is going on in a cluster, you might want to see what is restarting. kurt
effectively does a kubectl get pods --all-namespaces
with the collect function. This function iterates through all the pods and gathers pod restart counts while also grouping into categories such as node, label and namespace. For example, you could run the following to see pod restart counts grouped by node:
kurt nodes
If there was one particular node that had vastly more restarts than other nodes, this might indicate a hardware issue, or possibly lead you to re-provision the affected machine.
You may also want to get the same sort of information grouped by namespace. To see this, you could run:
kurt namespaces
If you group your applications based on namespace, this might help you hone in on the specific application to troubleshoot.
If you have multiple services in a namespace, and they all have a label with the key app
, you could run the following to see the top restarting services grouped this way:
kurt labels -n mynamespace -l app
If you just want to get an overview across all of these groupings, you could run:
kurt all
Which gives an output like this:
kurt: KUbernetes Restart Tracker
==========
Namespace Restarts
default 2
test 1
kube-system 0
==========
Node Restarts
minikube-m02 2
minikube-m03 1
minikube 0
==========
Label Restarts
run:nginx 3
component:etcd 0
k8s-app:kube-proxy 0
addonmanager.kubernetes.io/mode:Reconcile 0
integration-test:storage-provisioner 0
==========
Pod Namespace Restarts
nginx default 2
nginx test 1
kube-apiserver-minikube kube-system 0
storage-provisioner kube-system 0
etcd-minikube kube-system 0
You can also limit the scope of your query to specific namespaces, or label selectors.
All options available can be found with kurt help
.
What’s a kubectl plugin?
A kubectl
plugin is a pattern for extending the functionality of kubectl
with other custom tooling. More information can be found in the offical Kubernetes docs, but the gist is that any binary or script in your $PATH
named like kubectl-myplugin
can be called with kubectl
like:
kubectl myplugin
Since we made kurt
in golang, it can be run as a standalone binary without the kubectl
prefix. However, since it is made to interact with Kubernetes we decided to also focus on the plugin experience. This is where krew
comes in.
From krew
’s docs: “Krew is the plugin manager for kubectl command-line tool.” krew
is essentially the apt
/npm
/pip
of Kubernetes and is a SIG CLI project. krew
helps us package kurt
as a kubectl
plugin by submitting a manifest which gives users the latest version to be installed in the correct location, and re-named as kubectl-kurt
. Users can then use it like:
kubectl kurt
Using this plugin ecosystem is valuable to users because it allows any plugin to be installed and upgraded easily with the following:
kubectl krew install kurt
kubectl krew upgrade kurt
We also get the benefit of being discoverable on the krew
platform, and can view some high level analytics on number of downloads. In addition, the krew
team gave us some valuable tips on best practices which makes kurt
more usable as a plugin.
Tech used in this project
We wanted to use kurt
as a good use-case to try various technologies and learn more about the kubectl
plugin ecosystem. In addition to trying new things, our goal was to automate as many tasks around releasing a golang client application as possible.
Cobra:
To get the awesome CLI experience in kurt
, we used a library called cobra. Cobra is used in many projects such as kubectl, terraform and much more. There was a bit of a learning curve to get Cobra working correctly since there are some opinions taken on repo structure, but once it is in place it is extremely easy to add new subcommands and CLI flags.
For example, once the basic project scaffolding was in place, this is all that was needed to add a subcommand like nodes:
var cmdNodes = &cobra.Command{
Use: "nodes",
Short: "Only print node restart counts",
Long: "Only print node restart counts",
Aliases: []string{"no", "node"},
Run: func(cmd *cobra.Command, args []string) {
printNode = true
printAll = false
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
In addition to an awesome structure for adding subcommands and CLI flags, it also gave us things like shell compltion for free. For example, the following will give you bash shell completion for kurt
:
source <(kurt completion bash)
Once you get over the initial learning curve of cobra, the value that it gives is amazing. I will likely use it for any other golang CLI tool I make in the future.
goreleaser:
We used goreleaser to help us with the automation of cross-compiling and releasing kurt
for various platforms. You can do this manually inside any CI platform, but goreleaser gives a simple config file that allows you to specify the platforms/architectures you want, and will automate the archival and checksum file in a github release for you. Setting this up in github actions was very straightforward so any time we make push new version tag, everything is cross-compiled and released:
name: goreleaser
on:
push:
tags:
- "v*.*.*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Other automation and testing:
- Dependabot to automate 3rd party version bumps.
- CodeQL to check for vulnerabilities in our source code.
- Custom CI to automate unit tests (with
go test
) and ensure all golang files are formatted withgo fmt
.
These all run any time a PR to main
is submitted!
Future of kurt
We have a few ideas of how to enhance kurt
with:
If you have other ideas of how we can improve, questions, or a feature idea for kurt
, feel free to submit an issue or leave a comment in the Disqus chat!