028 Project 28: Kubernetes Rollout Checker
028 Build a Kubernetes Rollout Checker
Check whether a Deployment is fully rolled out: desired replicas match updated and ready counts. Exit 0 when healthy, 1 when pending, 2 on usage/API errors—ideal for CI gates after kubectl apply.
kubeconfig -> Get Deployment -> compare desired/updated/ready -> exit code
Problem statement
kroll -name DEPLOY [-n NS] [-wait DURATION] [-interval D]
- One-shot check by default
- Optional
-waitpolls until healthy or timeout - Print a one-line status for logs
Acceptance criteria
- Reads Deployment status fields correctly
- Exit 0 only when
ready == desired && updated == desired(and desired > 0 or allow 0) - Exit 1 when still rolling
- Exit 2 on bad flags / API errors
- Optional wait loop with timeout
Setup
mkdir kroll && cd kroll
go mod init example.com/kroll
go get k8s.io/client-go@latest
go get k8s.io/apimachinery@latest
go mod tidy
# go 1.27Full main.go
package main
import (
"context"
"flag"
"fmt"
"os"
"path/filepath"
"time"
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 status struct {
Desired int32
Updated int32
Ready int32
Avail int32
}
func (s status) Healthy() bool {
return s.Ready == s.Desired && s.Updated == s.Desired && s.Avail == s.Desired
}
func getStatus(ctx context.Context, cli *kubernetes.Clientset, ns, name string) (status, error) {
d, err := cli.AppsV1().Deployments(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return status{}, err
}
var desired int32 = 1
if d.Spec.Replicas != nil {
desired = *d.Spec.Replicas
}
return status{
Desired: desired,
Updated: d.Status.UpdatedReplicas,
Ready: d.Status.ReadyReplicas,
Avail: d.Status.AvailableReplicas,
}, nil
}
func main() {
var kubeconfig string
if home := homedir.HomeDir(); home != "" {
kubeconfig = filepath.Join(home, ".kube", "config")
}
kc := flag.String("kubeconfig", kubeconfig, "kubeconfig path")
name := flag.String("name", "", "deployment name")
ns := flag.String("n", "default", "namespace")
wait := flag.Duration("wait", 0, "max wait for healthy (0=check once)")
interval := flag.Duration("interval", 2*time.Second, "poll interval")
flag.Parse()
if *name == "" {
fmt.Fprintln(os.Stderr, "usage: kroll -name DEPLOY [-n NS] [-wait 2m]")
os.Exit(2)
}
cfg, err := clientcmd.BuildConfigFromFlags("", *kc)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
cli, err := kubernetes.NewForConfig(cfg)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
deadline := time.Now().Add(*wait)
for {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
st, err := getStatus(ctx, cli, *ns, *name)
cancel()
if err != nil {
fmt.Fprintln(os.Stderr, "get deployment:", err)
os.Exit(2)
}
fmt.Printf("deployment=%s/%s desired=%d updated=%d ready=%d available=%d\n",
*ns, *name, st.Desired, st.Updated, st.Ready, st.Avail)
if st.Healthy() {
fmt.Println("rollout: healthy")
os.Exit(0)
}
fmt.Println("rollout: pending")
if *wait == 0 || time.Now().After(deadline) {
if *wait > 0 {
fmt.Fprintln(os.Stderr, "timeout waiting for rollout")
}
os.Exit(1)
}
time.Sleep(*interval)
}
}Run and verification
kubectl create deploy roll-demo --image=nginx:1.27 --replicas=2
go run . -name roll-demo -n default
go run . -name roll-demo -wait 60s -interval 1s
kubectl set image deploy/roll-demo nginx=nginx:1.27-alpine
go run . -name roll-demo -wait 2mTests
Unit-test status.Healthy without a cluster:
package main
import "testing"
func TestHealthy(t *testing.T) {
if !(status{2, 2, 2, 2}).Healthy() {
t.Fatal("expected healthy")
}
if (status{2, 1, 1, 1}).Healthy() {
t.Fatal("expected pending")
}
}Stretch goals
- Also check
DeploymentProgressingcondition. - Support StatefulSet / DaemonSet.
- JSON output for CI annotations.
- Fail if
UnavailableReplicas > 0after deadline.
Pitfalls
| Pitfall | Fix |
|---|---|
Ignoring AvailableReplicas |
include in healthy check |
Nil Spec.Replicas |
default 1 per API conventions |
| Wait without timeout | always bound wait |
| Wrong exit codes for CI | document 0/1/2 |
Learning goals
- Deployment status fields for rollout gates
- Polling with timeout for CI
- Scriptable exit codes