Sep 15, 2022

Extending kubectl Utility With Plugins

Extending kubectl Utility With Plugins

Introduction

We can all agree on the fact that Kubernetes is quite expansive. It has become the de facto tool for cloud-native application deployment because of its flexibility. One often uses the kubectl CLI to interact with their Kubernetes cluster, and while kubectl is quite extensive in itself with regard to all the operations you can perform with it, its usability can be further extended using plugins.

kubectl plugins can extend the usability of the CLI tool by adding functional capabilities specific to a Kubernetes application or in general providing more features that can be accessed as kubectl sub-commands. The best part is it does not require editing kubectl’s source code or recompiling it, thereby making the plugins truly modular in the sense that they can be simply plugged in and used.

Installing Plugins

Although there’s more than one way to distribute and install kubectl plugins, the simplest way is to use krew. It is a package manager for kubectl and makes the installation and management of kubectl plugins a cakewalk. To get started, you would have to first install krew on your machine. You can refer to this installation document to do so.

Once installed, you shall first index all the available krew plugins, a list of which can be accessed here. To do so, run the following command:

kubectl krew update

Did you notice how krew itself is a kubectl plugin as well? A plugin to manage all other plugins! If you’re willing to, you can check its source code here.

Now, you can view the list of available plugins with the following command:

kubectl krew search

To install any plugin, say the whoami plugin, run the following command:

kubectl krew install whoami

Once installed, you can simply follow the usage guide to run the plugin commands as such:

kubectl whoami

You can also update, uninstall, and add a custom index for the plugins using krew among other things, for which you can refer to the documentation.

Developing Plugins

Developing plugins for kubectl is a fairly simple process, if we abstract out the business logic of the plugin. The plugin itself is nothing but a standalone CLI application that uses appropriate commands to achieve its intended functionalities. So the question remains how do we add this standalone CLI as a kubectl plugin? Let’s find out.

I have made a demo plugin called kubectl-count, which we will be using for this small demo. The complete source code can be found here: https://github.com/neelanjan00/kubectl-count. In a nutshell, this plugin allows you to count the instances of Kubernetes resources present in your cluster. So for example, you can count the number of pods in a namespace, the number of nodes in your cluster, or the total number of deployments in all the namespaces of your cluster.

The source code of the plugin is pretty straightforward, we use the Cobra CLI to create a boilerplate CLI project.

package cmd import ( "os" "github.com/spf13/cobra" ) // stores value of the all-namespaces flag var allNamespaces bool // stores value of the namespace flag var namespace string // stores value of the selector flag var selector string // countCmd represents the count command var countCmd = &cobra.Command{ Use: "count", Short: "Count Kubernetes resource instances.", Long: `Count Kubernetes resource instances.`, Run: func(cmd *cobra.Command, args []string) { cmd.Usage() }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { err := countCmd.Execute() if err != nil { os.Exit(1) } }

Then, we add the commands for each of the supported Kubernetes resources for which we will count the resources. We do so using the Kubernetes client-go library. For example, this is the logic for the command for counting the number of pods.

package cmd import ( "context" "fmt" "github.com/neelanjan00/kubectl-count/client" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // podsCmd represents the pods command var podsCmd = &cobra.Command{ Use: "pods", Aliases: []string{"po", "pod"}, Short: "Count pods in a namespace.", Long: `Count pods in a namespace, optionally filtered by a label.`, Run: func(cmd *cobra.Command, args []string) { if allNamespaces { namespace = "" } listOptions := metav1.ListOptions{} if selector != "" { listOptions.LabelSelector = selector } list, err := client.GetClient().CoreV1().Pods(namespace).List(context.Background(), listOptions) if err != nil { panic(err) } fmt.Println(len(list.Items)) }, } func init() { countCmd.AddCommand(podsCmd) podsCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") podsCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "resource namespace") podsCmd.PersistentFlags().StringVarP(&selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.") }

We are using flags here for specifying the namespace, the label selector, and all-namespaces, just as you would do in kubectl. Also, the aliases for the pods command such as po and pod are also available to be used. Similarly, we have created definitions for other commands as well, corresponding to the resources that they represent.

Lastly, if you’re wondering how we’re obtaining the Kubernetes client here, we use the existing .kubeconfig file in the machine and use it to generate a client.

package client import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/kubernetes" ) // GetClient returns a Kubernetes clientset func GetClient() *kubernetes.Clientset { config, err := genericclioptions.NewConfigFlags(true).ToRESTConfig() if err != nil { panic(err) } client, err := kubernetes.NewForConfig(config) if err != nil { panic(err) } return client }

We can simply run this CLI program as it is like the following:

go run kubectl-client.go po

The above command should give the number of pods in your default namespace, given that you have a valid Kubernetes cluster config file on your machine.

Now that we have our plugin ready, how shall we use it along with kubectl? To do so, first, we need to build a binary for this go program and then we need to rename the binary file (or an executable shell file, in case you want to make your plugins in bash) file such that the root sub-command of the plugin id preceded by kubectl-. For example, since the root sub-command for this plugin is count, the name of the binary will be kubectl-count. After this, we just need to move this file to the /usr/local/bin directory.

The naming convention we used here will allow kubectl to search for this plugin and any other plugin that you will be installing, given that the directory /usr/local/bin is in your system path. Now you can simply use this plugin to run the command that you had previously run to count the pods like so:

kubectl count po

Conclusion

kubectl plugins are an excellent way to increase the usability of the CLI tool. While there’s an ever-soaring list of plugins that you can simply plug and play using krew, it’s also pretty feasible to develop your own plugins which can abridge the lack of any functionality in kubectl without complicating the developer experience or the end-user experience.

CONTACT ME

© Neelanjan Manna, 2020 - 2024