banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Containers

banshee/containers-collection/containers/kubernetes/https/

workloads

The replicas wanted and ready of each deployment, statefulset and daemonset of a Kubernetes cluster, by its API.

  • containers
  • kubernetes
  • k8s
  • deployment
  • statefulset
  • daemonset
  • replicas
  • https
  • api

by banshee1 installation1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 113 lines
// The workloads of a Kubernetes cluster, or of one namespace, by its API with the token of a service account
// (GET /apis/apps/v1/deployments, statefulsets and daemonsets, or their /namespaces/<namespace>/ forms): for
// each one, how many replicas it wants and how many are ready. A workload is named
// <kind>/<namespace>/<name>, so that the selection of the probe keeps or leaves out a kind
// (daemonset/*) or a namespace (*/kube-system/*).
// The address is the API server's, the host's: https://<host:address>:<port>/.
need('protocol:http');
$address = need('host:address');
$token = need('protocol:http:token');
$port = variable('kubernetes:port', 'integer', array(
    'label'   => 'API port',
    'help'    => 'The port of the Kubernetes API server: 6443 for a cluster set up by kubeadm, often 443 for a managed one.',
    'default' => 6443,
));
$namespace = variable('kubernetes:namespace', 'text', array(
    'label'    => 'Namespace',
    'help'     => 'Only the workloads of this namespace, for a service account allowed in it alone. Empty: all namespaces.',
    'required' => false,
));

per('workload', 'Workloads');
output('state', 'text', array('label' => 'State', 'per' => 'workload'));
output('desired', 'number', array('label' => 'Replicas wanted', 'per' => 'workload'));
output('ready', 'number', array('label' => 'Replicas ready', 'per' => 'workload'));

watch.state('not-ready', 'Replicas not ready', 'state', array(
    'help'     => 'Warning when some replicas are not ready, critical when none is; a rollout under way passes before the time given.',
    'warning'  => array('degraded'),
    'critical' => array('down'),
    'for'      => '5 min',
));
watch.missing('missing', 'Workload missing', 'state', array('enabled' => false));

// The items of a list, read page by page: the API cuts a list at limit items and gives, under
// metadata.continue, the token of the next page, until the last. $query, when not empty, is added to the
// parameters of the address. The token goes in the header Authorization: Bearer; the answer is not secret,
// its body having carried none.
function api_list($base, $path, $query, $token) {
    $items = array();
    $next = '';
    while (true) {
        $url = $base . $path . '?limit=500';
        if ($query != '') {
            $url = $url . '&' . $query;
        }
        if ($next != '') {
            $url = $url . '&continue=' . url.encode($next);
        }
        $answer = http.get($url, headers=array('Authorization' => 'Bearer ' . $token));
        if ($answer['status'] == 401) {
            fail('misconfigured', 'Kubernetes refuses the token, code 401');
        }
        if ($answer['status'] == 403) {
            fail('misconfigured', 'The service account may not list ' . $path . ', code 403');
        }
        if ($answer['status'] != 200) {
            fail('unreachable', 'Kubernetes answers ' . $path . ' with code ' . $answer['status']);
        }
        $page = json.read($answer['body']);
        foreach ($page['items'] as $item) {
            $items = list.add($items, $item);
        }
        $next = list.get($page['metadata'], 'continue', '');
        if ($next == '') {
            break;
        }
    }
    return $items;
}

// The state of a workload from its replicas: ready when all are, degraded when some are, down when none is,
// idle when it wants none (scaled to zero, or a daemonset that fits no node).
function state_of($desired, $ready) {
    if ($desired == 0) {
        return 'idle';
    }
    if ($ready >= $desired) {
        return 'ready';
    }
    if ($ready == 0) {
        return 'down';
    }
    return 'degraded';
}

$base = 'https://' . $address . ':' . $port;
$prefix = '/apis/apps/v1/';
if ($namespace != '') {
    $prefix = '/apis/apps/v1/namespaces/' . url.encode($namespace) . '/';
}

// A deployment or a statefulset: {"metadata": {"namespace": "shop", "name": "web"}, "spec": {"replicas": 3},
// "status": {"readyReplicas": 2}}. A count at zero is not written: readyReplicas absent is none ready.
foreach (array('deployment' => 'deployments', 'statefulset' => 'statefulsets') as $kind => $resource) {
    foreach (api_list($base, $prefix . $resource, '', $token) as $workload) {
        $name = $kind . '/' . $workload['metadata']['namespace'] . '/' . $workload['metadata']['name'];
        $desired = list.get($workload['spec'], 'replicas', 1);
        $ready = list.get($workload['status'], 'readyReplicas', 0);
        value('state', state_of($desired, $ready), $name);
        value('desired', $desired, $name);
        value('ready', $ready, $name);
    }
}

// A daemonset: {"status": {"desiredNumberScheduled": 5, "numberReady": 5}}, one pod per node it fits.
foreach (api_list($base, $prefix . 'daemonsets', '', $token) as $workload) {
    $name = 'daemonset/' . $workload['metadata']['namespace'] . '/' . $workload['metadata']['name'];
    $desired = list.get($workload['status'], 'desiredNumberScheduled', 0);
    $ready = list.get($workload['status'], 'numberReady', 0);
    value('state', state_of($desired, $ready), $name);
    value('desired', $desired, $name);
    value('ready', $ready, $name);
}