Robust Shell-Oriented Kubernetes Tools

Robust Shell-Oriented Kubernetes Tools

I manage Kubernetes workloads on daily basis, but the official tool kubectl makes this task very tedious. I've tried out some popular alternatives, but they all lacked the flexibility of the shell. Eventually, I built a small command line tool to handle the most common tasks. I've gradually expanded its capabilities but it also became tiring. Adding a new operation was too bothersome if I believed that I was probably going to use it just once.

Recently, I revised my solution and realized that I could achieve much more if I took a more minimalist approach. I extracted the core of my tool and found out how to quickly reproduce most of the operations with simple shell scripts. It's not very inventive, especially since I've already praised the most important tool fzf in my previous article, but I find it very convenient.

The Functions

A typical kubectl usage works as follows:

  1. Get the list of items you are interested in, e.g. kubectl get pods.

  2. Read the entries and copy the one I am interested in. This is the most annoying part since I need to reach for the mouse (or pretend I can do it more efficiently in tmux's copy mode)

  3. Paste the copied name into the final command, e.g. kubectl describe copied-pod-name.

In order to accelerate this loop, I've created 2 bash functions that query for the list of entries and allow me to search them with a fuzzy-finder fzf:

ksp()
{
        p=`kubectl get pods | \
             fzf --multi --header-lines=1 | \
             awk '{print $1}'`
        r="$?"
        echo "$p"
        return "$r"
}

# btw. ksp - Kubernetes Search Pod

The table of pods is passed to fzf to select one or more entries. The table is in the space-separated-values format and contains a header. I use it in fzf with --header-lines=1 flag to exclude the first line from the selection options.

The first column of the table contains the pod's name. I extract it with awk, because that's what is usually required by other kubectl subcommands. I capture and return the error code of the original pipeline, and print the selection to the standard output. Additionally, because the assignments happen within my original shell, the name of the selected pod is preserved in $p variable.

There is obviously a sibling function for each other kind of workload/resource I might be looking for, e.g.:

ksj()
{
        j=`kubectl get jobs | \
             fzf --multi --header-lines=1 | \
             awk '{print $1}'`
        r="$?"
        echo "$j"
        return "$r"
}

Basic Usage

The first way to use those functions utilize the returned variables. It is as straightforward as calling bare ksp. The variable $p is captured so I can later call a more ambitious command. This is convenient because often I don't know what I'd like to do with my resources. For example, I'd like to check whether my pod is failing and only then decide to tail the logs or delete it.

The hassle with the error code paid off by allowing me to easily chain both invocations

ksp && kubectl logs -f $p

Returning a value via a global variable may seem fishy, but there are hardly any good alternatives in Bash. I could call ksp in a subshell:

kubectl describe pod "$(ksp)"  # not a good idea!

but then the final command would be called even if ksp failed (e.g. if a user canceled the selection). I have enabled the selection of multiple entries with --multi flag. I can pipe it to xargs to call a command on each of the selections:

ksp | xargs -r kubectl delete pod

The -r flags prevents calling the command for an empty string. I would also usually add -p flag for such destructive commands to make xargs prompt me for confirmation before each execution. I could also call xargs if we expect only a single input. I think it's just a matter of taste. Using a variable is sometimes simpler and allows me to "return" additional values. For example, I set a $w variable that indicates a working directory of selected job, which makes it easy to quickly display some partial results. I've not included it in the snippets because it's based on some job template conventions.

Bonus: Cleaner History

An extra advantage of those tools is that the history is much cleaner and easier to search. It is sometimes the case that I need to recall some complex command I used literally months ago. Normally, I would enter the first word and repeatedly search backward through my history. Yet it takes some time if there are many simpler commands that start with the word and differ only in a provided argument. The problem is eliminated if all those different parameters are replaced with a universal $p or xargs.

Trivia

  • kubectl get has -o option that allows changing the output format. I recommend using jq for parsing the json format. kubectl has its own ways to do that, but I'd rather learn a syntax of a more universal tool.

  • In my previous article I mentioned two fzf alternatives: skim and rofi. I also described how to construct such simple pipelines in more detail.

  • As I also mentioned in the previous article, it is possible to bind actions to certain key combinations in fzf (and others), e.g. to bind <CTRL>+L for tailing the logs from the pod.

  • I don't like that fzf clears my whole screen while it displays the (usually short) list so I've added --height 12 flag to limit the height of the UI.

  • I also use fzf's --query "$USER " flag to set an initial query. Due to a naming convention, I use for pod names, this makes me see only my resources. I can quickly erase the text to see the rest of the entries. The space at the end of my initial query string is intentional.