Cluster Resource Watcher

A Kubernetes client tool that demonstrates three core client-go patterns: listing pods across all namespaces, creating a ConfigMap resource, and watching for pod changes in real-time using a shared informer. It auto-detects whether it's running in-cluster or locally (via ~/.kube/config) and handles graceful shutdown on Ctrl+C.

Setup

mkdir cluster-resource-watcher
cd cluster-resource-watcher
go mod init github.com/yourorg/cluster-resource-watcher
go get k8s.io/client-go@latest
go get k8s.io/apimachinery@latest
go get k8s.io/api@latest

main.go

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

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

// buildClient returns a Clientset using in-cluster config when running inside
// a pod, and falls back to ~/.kube/config for local development.
func buildClient() (*kubernetes.Clientset, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		kubeconfig := filepath.Join(homedir.HomeDir(), ".kube", "config")
		config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			return nil, err
		}
	}
	return kubernetes.NewForConfig(config)
}

func main() {
	clientset, err := buildClient()
	if err != nil {
		log.Fatalf("building client: %v", err)
	}

	// 1. List existing pods across all namespaces
	fmt.Println("=== Current Pods ===")
	pods, err := clientset.CoreV1().Pods("").List(
		context.Background(), metav1.ListOptions{},
	)
	if err != nil {
		log.Fatalf("listing pods: %v", err)
	}
	for _, pod := range pods.Items {
		fmt.Printf("  %s/%s [%s]\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}

	// 2. Create a ConfigMap
	fmt.Println("\n=== Creating ConfigMap ===")
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "devops-tool-config",
			Namespace: "default",
		},
		Data: map[string]string{
			"version":   "1.0.0",
			"log_level": "debug",
		},
	}
	created, err := clientset.CoreV1().ConfigMaps("default").Create(
		context.Background(), cm, metav1.CreateOptions{},
	)
	if err != nil {
		log.Printf("create configmap: %v (may already exist)", err)
	} else {
		fmt.Printf("  Created: %s (uid: %s)\n", created.Name, created.UID)
	}

	// 3. Watch pods with a shared informer
	fmt.Println("\n=== Watching Pods (Ctrl+C to stop) ===")

	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	stopCh := make(chan struct{})
	go func() {
		<-ctx.Done()
		close(stopCh)
	}()

	factory := informers.NewSharedInformerFactory(clientset, 60*time.Second)
	podInformer := factory.Core().V1().Pods().Informer()

	podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			pod, ok := obj.(*corev1.Pod)
			if !ok {
				return
			}
			fmt.Printf("  [+] %s/%s\n", pod.Namespace, pod.Name)
		},
		DeleteFunc: func(obj interface{}) {
			// Objects deleted during a connectivity gap arrive wrapped in
			// DeletedFinalStateUnknown. Unwrap before type-asserting.
			if d, ok := obj.(cache.DeletedFinalStateUnknown); ok {
				obj = d.Obj
			}
			pod, ok := obj.(*corev1.Pod)
			if !ok {
				return
			}
			fmt.Printf("  [-] %s/%s\n", pod.Namespace, pod.Name)
		},
	})

	factory.Start(stopCh)
	if ok := cache.WaitForCacheSync(stopCh, podInformer.HasSynced); !ok {
		log.Fatal("failed to sync informer cache")
	}

	fmt.Println("  watching... (Ctrl+C to stop)")
	<-stopCh
	fmt.Println("\nShutting down.")
}

Running It

# requires a running cluster and a valid kubeconfig
go run main.go

Expected output:

=== Current Pods ===
  default/api-abc123 [Running]
  default/worker-def456 [Running]

=== Creating ConfigMap ===
  Created: devops-tool-config (uid: 7a3f2c91-4e8b-11ee-be56-0242ac120002)

=== Watching Pods (Ctrl+C to stop) ===
  watching... (Ctrl+C to stop)
  [+] default/api-xyz789
  [-] default/api-abc123

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy