Hedronite · Dev Lesson · Polyglot-Dev / Python · Thu 2026-09-24

Python kubernetes client — StorageClass and PVC bind census

List provisioners and PVC phases before you trust a Bound claim.

Lesson Class: Dev (Python touching K8s)
Ops Pair: AKS Azure Disk CSI StorageClass + PVC bind
Tooling: StorageV1Api · CoreV1Api · default-class annotation
Grounding: CKA Q14 · Poulton Ch.10 · Python for DevOps referenced
StorageClass rows
provisioner · reclaim · bind mode · default flag
PVC rows
phase · class · accessModes · volume name
Fail closed
ApiException exits non-zero; optional strict default
Inventory the classes. Count the defaults. Read the phases.

<!-- hal:authoritative:yaml -->

List provisioners and PVC phases before you trust a Bound claim. Guessing the default class is how silent Immediate disks ship.

§I - Frame

Use the official Python kubernetes client to inventory StorageClasses and PersistentVolumeClaims. Pair with Ops AKS Azure Disk CSI work. Assume AKS-shaped names (disk.csi.azure.com, managed-csi) when present; still print whatever the cluster actually has.

Do not reopen boto3 EKS Pod Identity associations (09-21), Gateway HTTPRoute CustomObjects (09-18), or PSA namespace label walking (09-12). Tonight is storage.k8s.io StorageClass rows plus core PVC bind status.

§II - Client bootstrap

from kubernetes import client, config
from kubernetes.client.rest import ApiException

config.load_kube_config()  # or load_incluster_config()
storage = client.StorageV1Api()
core = client.CoreV1Api()
DEFAULT_ANN = "storageclass.kubernetes.io/is-default-class"

Prefer a kubeconfig context pointed at an AKS lab. Catch ApiException and exit non-zero rather than inventing empty success. A census that hides API errors teaches the wrong instinct for Maghrib review.

§III - StorageClass census rows

def sc_rows():
    rows = []
    for sc in storage.list_storage_class().items:
        ann = sc.metadata.annotations or {}
        params = sc.parameters or {}
        rows.append({
            "name": sc.metadata.name,
            "provisioner": sc.provisioner,
            "reclaim": sc.reclaim_policy,
            "bind_mode": sc.volume_binding_mode,
            "is_default": ann.get(DEFAULT_ANN) == "true",
            "sku": params.get("skuname") or params.get("storageaccounttype"),
            "allow_expand": bool(sc.allow_volume_expansion),
        })
    return rows

Reviewer checks:

  1. Exactly one is_default=True (Q14 instinct). Zero or two is a ship-stop comment.
  2. AKS disk classes should show provisioner disk.csi.azure.com when CSI is installed.
  3. bind_mode should be WaitForFirstConsumer on modern managed-csi; flag Immediate on multi-zone pools.
  4. Print sku when parameters carry skuname (Azure Disk) without requiring every class to have it.
  5. Count how many classes use Delete vs Retain reclaim; AKS built-ins are usually Delete.

§IV - PVC phase census

def pvc_rows(namespace=None):
    if namespace:
        items = core.list_namespaced_persistent_volume_claim(namespace).items
    else:
        items = core.list_persistent_volume_claim_for_all_namespaces().items
    rows = []
    for pvc in items:
        spec, status = pvc.spec, pvc.status
        req = None
        if spec.resources and spec.resources.requests:
            req = spec.resources.requests.get("storage")
        cap = None
        if status and status.capacity:
            cap = status.capacity.get("storage")
        rows.append({
            "ns": pvc.metadata.namespace,
            "name": pvc.metadata.name,
            "phase": (status.phase if status else None),
            "class": spec.storage_class_name,
            "access": list(spec.access_modes or []),
            "req": req,
            "volume": spec.volume_name,
            "capacity": cap,
        })
    return rows

Phase vocabulary that matters tonight: Pending (waiting on provisioner, bind mode, or consumer), Bound (has volume_name), Lost (rare; bad volume reference). For WaitForFirstConsumer, Pending with no Pod is expected; Bound without a Pod on Immediate is also expected. Do not treat Pending as failure until you know the bind mode and whether a consumer exists.

Optional enrichment: for Bound rows, core.read_persistent_volume(volume) and print persistent_volume_reclaim_policy plus CSI driver. Keep that optional; exit zero requires SC + PVC tables only.

§V - Report shape and done criteria

Print markdown a reviewer can skim:

# storage census
## storageclasses
- managed-csi | disk.csi.azure.com | Delete | WaitForFirstConsumer | default=True | sku=StandardSSD_LRS
- managed-csi-premium | disk.csi.azure.com | Delete | WaitForFirstConsumer | default=False | sku=Premium_LRS
## defaults
- count=1
## pvcs
- demo/app-data | Bound | class=managed-csi | RWO | req=20Gi | volume=pvc-...
## anomalies
- defaults!=1
- Immediate bind_mode on disk.csi.azure.com class
- Pending PVC whose class uses WaitForFirstConsumer and has no consumer Pod (informational)

Success: StorageClass table includes provisioner, reclaim, bind mode, default flag; default count is computed; PVC table includes phase, class, accessModes, requested size, bound volume name when Bound; exit zero on successful lists. Exit non-zero if API list fails. Optional --strict-default fails when default count is not 1 (useful CI gate after Cert Q14 patches).

Wire the script as a small module with if __name__ == "__main__" argparse for --namespace and --strict-default. Keep credentials out of the repo; rely on kubeconfig or in-cluster config only.

§VI - What this census is not

Do not call Azure identity APIs. Do not list Gateway or HTTPRoute objects. Do not walk PSA labels. Do not write terratest or Go. Do not apply manifests from Python tonight; Maghrib can deepen mutate paths later. Read-only inventory is enough to pair Ops and Cert.

§VII - Close

Ops declares AKS disk CSI classes and PVC bind edges. Python proves the live cluster still advertises those classes and shows claim phases. Pair Cert for Q14 StorageClass create/default and reclaim vocabulary. Maghrib owns quiz.html later.

Related