Why Go Is Ideal for Ops Tools

Ops CLI tools demand high deployment convenience and execution efficiency — areas where Go has natural advantages:

AdvantageDescriptionCompared to Python
Single binaryCompiles to a standalone executable with no runtime dependenciesRequires Python environment + dependencies
Cross-platformGOOS/GOARCH cross-compilation — write once, run anywhereRequires virtualenv management
Startup speedMillisecond-level cold startInterpreter overhead
Concurrency modelLightweight goroutinesRequires threading/asyncio
Memory footprintLow memory usage as a static binaryInterpreter overhead
Mature ecosystemStandard library covers networking/files/cryptoRelies on third-party libraries

Kubernetes, Docker, Terraform, Prometheus — the core cloud-native tools are all written in Go. Go has become the de facto standard language for cloud-native ops tooling.

Getting Started with cobra

cobra is the most popular CLI framework in the Go ecosystem, providing command trees, flag parsing, auto-completion, and more. According to the cobra documentation, its design philosophy is composition over inheritance.

Installation and Project Initialization

# Install cobra CLI tool
go install github.com/spf13/cobra/cobra@latest

# Initialize project
mkdir k8s-pod-cleaner && cd k8s-pod-cleaner
go mod init github.com/xubaojin/k8s-pod-cleaner

# Generate project scaffold with cobra
cobra init --author "Xu Baojin" --license mit --package cmd

# Add subcommands
cobra add list
cobra add clean
cobra add logs

Command Tree Design

A well-designed CLI tool should have a clear command hierarchy:

k8s-pod-cleaner
├── list          # List abnormal Pods
   └── --status  # Filter by status
├── clean         # Batch cleanup
   └── --force   # Force delete
├── logs          # Log export
   └── --tail    # Line count limit
└── version       # Version info

Root Command and Persistent Flags

// cmd/root.go
package cmd

import (
    "os"
    "github.com/spf13/cobra"
)

var kubeconfig string
var namespace string

var rootCmd = &cobra.Command{
    Use:   "k8s-pod-cleaner",
    Short: "K8s abnormal Pod management tool",
    Long: `k8s-pod-cleaner is a CLI tool for managing abnormal Pods in Kubernetes clusters.

Features:
  - List Pods in abnormal states (CrashLoopBackOff, ImagePullBackOff, etc.)
  - Batch clean up abnormal Pods
  - Export logs from abnormal Pods`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

func init() {
    // PersistentFlag applies to all subcommands
    rootCmd.PersistentFlags().StringVarP(
        &kubeconfig, "kubeconfig", "k", "",
        "path to kubeconfig file (defaults to ~/.kube/config)",
    )
    rootCmd.PersistentFlags().StringVarP(
        &namespace, "namespace", "n", "",
        "Kubernetes namespace (defaults to all namespaces)",
    )
}

The distinction between PersistentFlag vs Local Flag is a core cobra concept:

  • PersistentFlags(): The flag is visible to the current command and all subcommands
  • Flags(): The flag is only visible to the current command

Subcommands and Flags

// cmd/list.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var statusFilter string

var listCmd = &cobra.Command{
    Use:   "list",
    Short: "List Pods in abnormal states",
    Run: func(cmd *cobra.Command, args []string) {
        client, err := NewK8sClient(kubeconfig)
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Failed to connect to cluster: %v\n", err)
            return
        }
        pods, err := client.ListAbnormalPods(namespace, statusFilter)
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Query failed: %v\n", err)
            return
        }
        printPodTable(pods)
    },
}

func init() {
    listCmd.Flags().StringVarP(
        &statusFilter, "status", "s", "",
        "filter by status: CrashLoopBackOff, ImagePullBackOff, Pending, Failed",
    )
    rootCmd.AddCommand(listCmd)
}

Building the k8s-pod-cleaner CLI Tool

K8s Client Wrapper

// pkg/k8s/client.go
package k8s

import (
    "context"
    "fmt"
    "path/filepath"

    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
)

type PodInfo struct {
    Namespace string
    Name      string
    Status    string
    Restarts  int32
    Age       string
    Node      string
    Reason    string
}

type Client struct {
    clientset *kubernetes.Clientset
}

func NewClient(kubeconfig string) (*Client, error) {
    if kubeconfig == "" {
        if home := homedir.HomeDir(); home != "" {
            kubeconfig = filepath.Join(home, ".kube", "config")
        }
    }

    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
    }

    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        return nil, fmt.Errorf("failed to create clientset: %w", err)
    }

    return &Client{clientset: clientset}, nil
}

// ListAbnormalPods lists Pods in abnormal states
func (c *Client) ListAbnormalPods(namespace, statusFilter string) ([]PodInfo, error) {
    ctx := context.Background()
    var pods []PodInfo

    nsList := []string{namespace}
    if namespace == "" {
        nsList = nil // nil means all namespaces
    }

    if namespace != "" {
        podList, err := c.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
        if err != nil {
            return nil, err
        }
        for _, pod := range podList.Items {
            if info := c.checkAbnormal(pod, statusFilter); info != nil {
                pods = append(pods, *info)
            }
        }
    } else {
        // List across all namespaces
        podList, err := c.clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
        if err != nil {
            return nil, err
        }
        for _, pod := range podList.Items {
            if info := c.checkAbnormal(pod, statusFilter); info != nil {
                pods = append(pods, *info)
            }
        }
    }

    return pods, nil
}

func (c *Client) checkAbnormal(pod corev1.Pod, filter string) *PodInfo {
    status := string(pod.Status.Phase)
    reason := ""

    // Check container statuses
    for _, cs := range pod.Status.ContainerStatuses {
        if cs.State.Waiting != nil {
            reason = cs.State.Waiting.Reason
            status = cs.State.Waiting.Reason // CrashLoopBackOff, ImagePullBackOff, etc.
        }
        if cs.State.Terminated != nil {
            reason = cs.State.Terminated.Reason
            status = "Terminated"
        }
    }

    // Abnormal status detection
    abnormal := map[string]bool{
        "CrashLoopBackOff":  true,
        "ImagePullBackOff":  true,
        "ErrImagePull":      true,
        "Pending":           true,
        "Failed":            true,
        "Terminated":        true,
    }

    if !abnormal[status] {
        return nil
    }

    // Status filter
    if filter != "" && filter != status {
        return nil
    }

    var restarts int32
    if len(pod.Status.ContainerStatuses) > 0 {
        restarts = pod.Status.ContainerStatuses[0].RestartCount
    }

    return &PodInfo{
        Namespace: pod.Namespace,
        Name:      pod.Name,
        Status:    status,
        Restarts:  restarts,
        Node:      pod.Spec.NodeName,
        Reason:    reason,
    }
}

// DeletePod deletes the specified Pod
func (c *Client) DeletePod(namespace, name string, force bool) error {
    ctx := context.Background()
    deleteOpts := metav1.DeleteOptions{}
    if force {
        gracePeriod := int64(0)
        deleteOpts.GracePeriodSeconds = &gracePeriod
    }
    return c.clientset.CoreV1().Pods(namespace).Delete(ctx, name, deleteOpts)
}

// GetPodLogs retrieves Pod logs
func (c *Client) GetPodLogs(namespace, name string, tailLines int64) (string, error) {
    ctx := context.Background()
    opts := &corev1.PodLogOptions{
        TailLines: &tailLines,
    }
    req := c.clientset.CoreV1().Pods(namespace).GetLogs(name, opts)
    logs, err := req.Stream(ctx)
    if err != nil {
        return "", err
    }
    defer logs.Close()

    buf := make([]byte, 0, 4096)
    tmp := make([]byte, 4096)
    for {
        n, err := logs.Read(tmp)
        if n > 0 {
            buf = append(buf, tmp[:n]...)
        }
        if err != nil {
            break
        }
    }
    return string(buf), nil
}

Clean Subcommand

// cmd/clean.go
package cmd

import (
    "fmt"
    "strings"

    "github.com/spf13/cobra"
)

var forceDelete bool
var dryRun bool

var cleanCmd = &cobra.Command{
    Use:   "clean",
    Short: "Batch clean up abnormal Pods",
    Run: func(cmd *cobra.Command, args []string) {
        client, err := NewK8sClient(kubeconfig)
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Failed to connect to cluster: %v\n", err)
            return
        }

        pods, err := client.ListAbnormalPods(namespace, "")
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Query failed: %v\n", err)
            return
        }

        if len(pods) == 0 {
            fmt.Println("No abnormal Pods to clean up")
            return
        }

        fmt.Printf("Found %d abnormal Pods:\n", len(pods))
        for _, p := range pods {
            fmt.Printf("  %s/%s [%s] restarts=%d\n", p.Namespace, p.Name, p.Status, p.Restarts)
        }

        if dryRun {
            fmt.Println("\n--dry-run mode, no actual deletion performed")
            return
        }

        success := 0
        failed := 0
        for _, p := range pods {
            err := client.DeletePod(p.Namespace, p.Name, forceDelete)
            if err != nil {
                fmt.Printf("  ✗ %s/%s deletion failed: %v\n", p.Namespace, p.Name, err)
                failed++
            } else {
                fmt.Printf("  ✓ %s/%s deleted\n", p.Namespace, p.Name)
                success++
            }
        }

        fmt.Printf("\nCleanup complete: %d succeeded, %d failed\n", success, failed)
    },
}

func init() {
    cleanCmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "force delete (gracePeriod=0)")
    cleanCmd.Flags().BoolVar(&dryRun, "dry-run", false, "show Pods to be deleted without actually deleting")
    rootCmd.AddCommand(cleanCmd)
}

Logs Subcommand

// cmd/logs.go
package cmd

import (
    "fmt"
    "os"
    "path/filepath"

    "github.com/spf13/cobra"
)

var tailLines int64
var outputDir string

var logsCmd = &cobra.Command{
    Use:   "logs",
    Short: "Export logs from abnormal Pods",
    Run: func(cmd *cobra.Command, args []string) {
        client, err := NewK8sClient(kubeconfig)
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Failed to connect to cluster: %v\n", err)
            return
        }

        pods, err := client.ListAbnormalPods(namespace, "")
        if err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Query failed: %v\n", err)
            return
        }

        if outputDir == "" {
            outputDir = "./pod-logs"
        }
        if err := os.MkdirAll(outputDir, 0755); err != nil {
            fmt.Fprintf(cmd.ErrOrStderr(), "Failed to create output directory: %v\n", err)
            return
        }

        for _, p := range pods {
            logs, err := client.GetPodLogs(p.Namespace, p.Name, tailLines)
            if err != nil {
                fmt.Printf("  ✗ %s/%s log retrieval failed: %v\n", p.Namespace, p.Name, err)
                continue
            }
            filename := filepath.Join(outputDir, fmt.Sprintf("%s_%s.log", p.Namespace, p.Name))
            if err := os.WriteFile(filename, []byte(logs), 0644); err != nil {
                fmt.Printf("  ✗ %s write failed: %v\n", filename, err)
                continue
            }
            fmt.Printf("  ✓ %s/%s → %s\n", p.Namespace, p.Name, filename)
        }
    },
}

func init() {
    logsCmd.Flags().Int64VarP(&tailLines, "tail", "t", 100, "get last N lines of logs")
    logsCmd.Flags().StringVarP(&outputDir, "output", "o", "", "log output directory (defaults to ./pod-logs)")
    rootCmd.AddCommand(logsCmd)
}

Output Formatting

// cmd/output.go
package cmd

import (
    "fmt"
    "os"
    "text/tabwriter"
)

func printPodTable(pods []PodInfo) {
    if len(pods) == 0 {
        fmt.Println("No abnormal Pods")
        return
    }

    w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
    fmt.Fprintln(w, "NAMESPACE\tNAME\tSTATUS\tRESTARTS\tNODE\tREASON")

    for _, p := range pods {
        fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%s\n",
            p.Namespace, p.Name, p.Status, p.Restarts, p.Node, p.Reason)
    }
    w.Flush()

    fmt.Printf("\nTotal: %d abnormal Pods\n", len(pods))
}

Usage Examples

# List abnormal Pods across all namespaces
k8s-pod-cleaner list

# List only CrashLoopBackOff Pods
k8s-pod-cleaner list -s CrashLoopBackOff

# Specify a namespace
k8s-pod-cleaner list -n production

# Dry-run cleanup (no actual deletion)
k8s-pod-cleaner clean --dry-run

# Force clean all abnormal Pods
k8s-pod-cleaner clean -f

# Export abnormal Pod logs (last 200 lines)
k8s-pod-cleaner logs -t 200 -o ./incident-logs

Cross-Platform Compilation and Version Management

Cross-Compilation

# Linux amd64
GOOS=linux GOARCH=amd64 go build -o bin/k8s-pod-cleaner-linux-amd64

# macOS arm64 (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o bin/k8s-pod-cleaner-darwin-arm64

# Windows amd64
GOOS=windows GOARCH=amd64 go build -o bin/k8s-pod-cleaner-windows-amd64.exe

Version Information Injection

// cmd/version.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var (
    Version   = "dev"
    Commit    = "none"
    BuildDate = "unknown"
)

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Show version information",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("k8s-pod-cleaner %s\n", Version)
        fmt.Printf("  commit: %s\n", Commit)
        fmt.Printf("  build:  %s\n", BuildDate)
        fmt.Printf("  go:     %s\n", runtime.Version())
    },
}

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

Inject version info at build time:

go build -ldflags "\
  -X github.com/xubaojin/k8s-pod-cleaner/cmd.Version=v1.0.0 \
  -X github.com/xubaojin/k8s-pod-cleaner/cmd.Commit=$(git rev-parse --short HEAD) \
  -X github.com/xubaojin/k8s-pod-cleaner/cmd.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -o bin/k8s-pod-cleaner

GoReleaser Automated Release

GoReleaser is a release automation tool for Go projects. It handles cross-compilation, packaging, changelog generation, and GitHub Releases in one step:

# .goreleaser.yaml
version: 2

project_name: k8s-pod-cleaner

before:
  hooks:
    - go mod tidy

builds:
  - id: k8s-pod-cleaner
    main: ./main.go
    binary: k8s-pod-cleaner
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w
      - -X github.com/xubaojin/k8s-pod-cleaner/cmd.Version={{.Version}}
      - -X github.com/xubaojin/k8s-pod-cleaner/cmd.Commit={{.ShortCommit}}
      - -X github.com/xubaojin/k8s-pod-cleaner/cmd.BuildDate={{.Date}}

archives:
  - id: default
    formats: [tar.gz]
    format_overrides:
      - goos: windows
        formats: [zip]
    name_template: >-
      {{ .ProjectName }}_{{ .Version }}_
      {{- if eq .Os "darwin" }}macOS
      {{- else if eq .Os "linux" }}Linux
      {{- else if eq .Os "windows" }}Windows
      {{- end }}_{{ .Arch }}      

checksum:
  name_template: 'checksums.txt'

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'
  groups:
    - title: 'New Features'
      regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
    - title: 'Bug Fixes'
      regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
    - title: 'Other'
      default: true

release:
  github:
    owner: xubaojin
    name: k8s-pod-cleaner
  draft: false
  prerelease: auto

Execute the release:

# Local test build
goreleaser release --snapshot --clean

# Official release (requires git tag)
git tag v1.0.0
git push origin v1.0.0
goreleaser release --clean

GoReleaser automatically handles:

  • Cross-compilation for 6 platforms (linux/darwin/windows × amd64/arm64)
  • Generating tar.gz/zip archives
  • Generating SHA256 checksum files
  • Auto-generating Changelog
  • Creating a GitHub Release and uploading artifacts

Summary

Go + cobra is the golden combination for building ops CLI tools. Go’s single-binary nature and cross-platform capabilities solve deployment pain points, while cobra’s command tree design keeps the user experience clear even for complex tools.

Key principles for building ops CLI tools: clean command hierarchy, clear flag semantics, parseable formatted output, and a dry-run mode. Combined with GoReleaser for one-command multi-platform releases, tool distribution and maintenance become effortless.

From kubectl to helm to argocd, the best CLI tools in the cloud-native ecosystem all follow this pattern. Master Go + cobra, and you’ve mastered the core skill for building professional ops tooling.

References & Acknowledgments

This article referenced the following materials during writing. We thank the original authors for their contributions:

  1. cobra — Cobra, referenced for technical content
  2. GoReleaser — Goreleaser, referenced for GoReleaser