026 Project 26: Kubernetes Pod Lister
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
-nnamespace (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 tidyRequires 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
- Build rest config from kubeconfig.
- Create
kubernetes.Clientset. - Call
Pods(ns).Listwith context timeout. - Format rows for humans/scripts.
- 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 -lWithout a cluster:
go run . -kubeconfig /nonexistent
# expect non-zero exit and readable errorPitfalls
| 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
- Watch mode (
Watch+ print events). - JSON output (
-o json). - Filter by phase (
-field-selector). - In-cluster config path for running as a Pod.
- Table of restarts from
ContainerStatuses.
Learning goals
- Use
client-gosafely with context - Map API objects to operator-facing columns
- Build automation-friendly cluster checks