026 Project 26: Kubernetes Pod Lister

Updated

September 8, 2026

026 Build a Kubernetes Pod Lister

List pods across namespaces using client-go. Produce operator-friendly tabular output suitable for scripts and CI smoke checks against a real or kind/minikube cluster.

kubeconfig -> clientset -> Pods.List -> print ns/name/phase/node

Problem statement

CLI kpods:

  • Load kubeconfig from -kubeconfig (default ~/.kube/config)
  • Optional -n namespace (empty = all namespaces)
  • Optional label selector -l
  • Print: NAMESPACE/NAME PHASE NODE READY
  • Non-zero exit if API call fails

Acceptance criteria

  • Lists pods with client-go
  • Namespace filter works
  • Clear errors when kubeconfig/cluster unavailable
  • Context with timeout (no hang forever)
  • Builds with Go modules

Setup

mkdir kpods && cd kpods
go mod init example.com/kpods
go get k8s.io/client-go@latest
go get k8s.io/apimachinery@latest
# go 1.27
go mod tidy

Requires a reachable cluster (kubectl get pods works).

Full main.go

package main

import (
    "context"
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "time"

    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"
)

func readyCount(p corev1.Pod) (ready, total int) {
    total = len(p.Status.ContainerStatuses)
    for _, cs := range p.Status.ContainerStatuses {
        if cs.Ready {
            ready++
        }
    }
    return ready, total
}

func main() {
    var kubeconfig string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = filepath.Join(home, ".kube", "config")
    }
    kc := flag.String("kubeconfig", kubeconfig, "kubeconfig path")
    namespace := flag.String("n", "", "namespace (empty=all)")
    label := flag.String("l", "", "label selector")
    timeout := flag.Duration("timeout", 10*time.Second, "API timeout")
    flag.Parse()

    cfg, err := clientcmd.BuildConfigFromFlags("", *kc)
    if err != nil {
        fmt.Fprintln(os.Stderr, "kubeconfig:", err)
        os.Exit(1)
    }
    cli, err := kubernetes.NewForConfig(cfg)
    if err != nil {
        fmt.Fprintln(os.Stderr, "client:", err)
        os.Exit(1)
    }

    ctx, cancel := context.WithTimeout(context.Background(), *timeout)
    defer cancel()

    ns := *namespace
    if ns == "" {
        ns = metav1.NamespaceAll
    }

    pods, err := cli.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
        LabelSelector: *label,
    })
    if err != nil {
        fmt.Fprintln(os.Stderr, "list pods:", err)
        os.Exit(1)
    }

    fmt.Printf("%-48s %-12s %-20s %s\n", "POD", "PHASE", "NODE", "READY")
    for _, p := range pods.Items {
        r, t := readyCount(p)
        fmt.Printf("%-48s %-12s %-20s %d/%d\n",
            p.Namespace+"/"+p.Name,
            string(p.Status.Phase),
            p.Spec.NodeName,
            r, t,
        )
    }
    fmt.Fprintf(os.Stderr, "total: %d\n", len(pods.Items))
}

Step-by-step build path

  1. Build rest config from kubeconfig.
  2. Create kubernetes.Clientset.
  3. Call Pods(ns).List with context timeout.
  4. Format rows for humans/scripts.
  5. Add label selector and readiness columns.

Run and verification

go run . 
go run . -n kube-system
go run . -l app=myapp
go run . -timeout 5s

# compare with kubectl
kubectl get pods -A --no-headers | wc -l

Without a cluster:

go run . -kubeconfig /nonexistent
# expect non-zero exit and readable error

Pitfalls

Pitfall Fix
No timeout use context.WithTimeout
Hard-coded HOME homedir.HomeDir + flag
Panic on errors print stderr + exit 1
Huge output add limit/continue token (stretch)
Version skew pin client-go close to cluster version

Stretch goals

  1. Watch mode (Watch + print events).
  2. JSON output (-o json).
  3. Filter by phase (-field-selector).
  4. In-cluster config path for running as a Pod.
  5. Table of restarts from ContainerStatuses.

Learning goals

  • Use client-go safely with context
  • Map API objects to operator-facing columns
  • Build automation-friendly cluster checks