blog.maisumvictor.dev β zsh
Building CLI Tools in Go for Kubernetes Operations
> Published:
Go is the lingua franca of cloud-native tooling. Letβs build a practical CLI tool that interacts with Kubernetes clusters.
Why Go for Kubernetes Tools?
- Native Kubernetes client: The official
client-golibrary - Single binary: Easy distribution, no dependencies
- Fast execution: Compiled language beats scripts
- Rich ecosystem: Cobra, Viper, and more
Project Setup
mkdir k8s-tool && cd k8s-tool
go mod init github.com/maisumvictor/k8s-tool
go get github.com/spf13/cobra@latest
go get k8s.io/[email protected]
Basic Structure
// main.go
package main
import (
"fmt"
"os"
"github.com/maisumvictor/k8s-tool/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
The Root Command
// cmd/root.go
package cmd
import (
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
var (
kubeconfig string
namespace string
clientset *kubernetes.Clientset
)
var rootCmd = &cobra.Command{
Use: "k8s-tool",
Short: "A CLI tool for Kubernetes operations",
Long: `k8s-tool provides utilities for common Kubernetes tasks
like checking pod status, cleaning up resources, and generating reports.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return initKubernetesClient()
},
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to kubeconfig file")
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "Kubernetes namespace")
}
func initKubernetesClient() error {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if kubeconfig != "" {
loadingRules.ExplicitPath = kubeconfig
}
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
config, err := kubeConfig.ClientConfig()
if err != nil {
return fmt.Errorf("failed to create k8s config: %w", err)
}
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("failed to create k8s client: %w", err)
}
return nil
}
Listing Pods with Status
// cmd/pods.go
package cmd
import (
"context"
"fmt"
"os"
"text/tabwriter"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var podsCmd = &cobra.Command{
Use: "pods",
Short: "List pods with enhanced status information",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list pods: %w", err)
}
// Create tabwriter for aligned output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "NAME\tSTATUS\tRESTARTS\tAGE\tNODE")
for _, pod := range pods.Items {
status := getPodStatus(&pod)
age := time.Since(pod.CreationTimestamp.Time).Round(time.Second)
fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\n",
pod.Name,
status,
getRestartCount(&pod),
formatDuration(age),
pod.Spec.NodeName,
)
}
return w.Flush()
},
}
func getPodStatus(pod *corev1.Pod) string {
phase := pod.Status.Phase
switch phase {
case corev1.PodRunning:
// Check if all containers are ready
allReady := true
for _, cs := range pod.Status.ContainerStatuses {
if !cs.Ready {
allReady = false
break
}
}
if allReady {
return color.GreenString("Running")
}
return color.YellowString("NotReady")
case corev1.PodPending:
return color.YellowString("Pending")
case corev1.PodFailed:
return color.RedString("Failed")
case corev1.PodSucceeded:
return color.BlueString("Completed")
default:
return string(phase)
}
}
func getRestartCount(pod *corev1.Pod) int32 {
var total int32
for _, cs := range pod.Status.ContainerStatuses {
total += cs.RestartCount
}
return total
}
func formatDuration(d time.Duration) string {
if d.Hours() >= 24 {
return fmt.Sprintf("%dd", int(d.Hours()/24))
}
if d.Hours() >= 1 {
return fmt.Sprintf("%dh", int(d.Hours()))
}
return fmt.Sprintf("%dm", int(d.Minutes()))
}
func init() {
rootCmd.AddCommand(podsCmd)
}
Resource Cleanup Command
// cmd/cleanup.go
package cmd
import (
"context"
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
olderThan string
dryRun bool
)
var cleanupCmd = &cobra.Command{
Use: "cleanup",
Short: "Clean up stale Kubernetes resources",
Long: `Remove completed pods, failed jobs, and unused configmaps older than specified duration.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
if dryRun {
fmt.Println("π DRY RUN MODE - No resources will be deleted")
}
// Clean up completed pods
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
FieldSelector: "status.phase=Succeeded",
})
if err != nil {
return err
}
fmt.Printf("Found %d completed pods\n", len(pods.Items))
if !dryRun {
for _, pod := range pods.Items {
err := clientset.CoreV1().Pods(namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})
if err != nil {
fmt.Printf("β Failed to delete pod %s: %v\n", pod.Name, err)
} else {
fmt.Printf("β
Deleted pod: %s\n", pod.Name)
}
}
}
return nil
},
}
func init() {
cleanupCmd.Flags().StringVar(&olderThan, "older-than", "24h", "Delete resources older than this duration")
cleanupCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show what would be deleted without deleting")
rootCmd.AddCommand(cleanupCmd)
}
Building and Distributing
# Build for current platform
go build -o k8s-tool
# Cross-compile for multiple platforms
GOOS=linux GOARCH=amd64 go build -o k8s-tool-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o k8s-tool-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o k8s-tool-windows-amd64.exe
# Install locally
go install github.com/maisumvictor/k8s-tool@latest
Usage Examples
# List pods in default namespace
k8s-tool pods
# List pods in specific namespace
k8s-tool pods -n kube-system
# Use specific kubeconfig
k8s-tool --kubeconfig ~/.kube/prod pods
# Preview cleanup
k8s-tool cleanup --dry-run
# Actually cleanup
k8s-tool cleanup --older-than 48h
Next Steps
- Add more commands (jobs, configmaps, secrets)
- Implement JSON/YAML output for scripting
- Add completion scripts for bash/zsh
- Create a Helm plugin wrapper
The complete source code is available on GitHub.