The Kubernetes Python client is the official library for talking to the Kubernetes API from Python. It is auto-generated from the OpenAPI spec, which means every resource, every field, and every error you can hit from kubectl you can also hit from a Python script — for better and for worse. That is the honest version. The cheerful version (“Pythonic wrapper around your cluster!”) undersells the rough edges and oversells the abstraction. This post is for engineers who already have kubectl in muscle memory and want to know when the Python client is worth the import.
This post assumes you can read a Kubernetes manifest without flinching, that you have run kubectl apply more times than you can count, and that you are willing to spend an afternoon getting familiar with the client before betting the platform team on it.
Table of contents
- What the client actually is
- When the Python client earns its keep
- When you should just use kubectl instead
- The install that actually works
- Loading the kubeconfig the right way
- A real example: list, create, watch
- The pattern that prevents the 2 a.m. page
- The alternatives worth knowing
- The opinion this post is built on
- FAQ
What the client actually is
The kubernetes-client/python repository is generated. A code generator reads the OpenAPI spec from the Kubernetes source and emits one Python class per resource, one method per verb, and one enum per enum. The output is checked in to the repo. The generation pipeline runs whenever the upstream spec changes.
What this means in practice:
- The API surface is huge. Every Kubernetes resource is a Python class. Every field on every resource is a Python attribute. The client is feature-complete with kubectl on day one of any Kubernetes release.
- The names are exactly the kubectl names.
V1PodisPod.V1DeploymentisDeployment.extensions_v1beta1is the API group. The mental translation fromkubectl getto Python is mechanical, not conceptual. - The errors are exactly the API errors. A 404 from the cluster is a
ApiExceptionwithstatus=404. A 409 on a conflict is a 409. There is no friendly Pythonic wrapper. There is no domain model. There is the API. - The documentation is the API reference. Each generated class has docstrings pulled from the upstream description. The Kubernetes API reference is the source of truth; the Python docs mirror it.
This is the part most intros skip. The client is not a higher-level library. It is the API, with Python types. Anyone selling it as “Pythonic” is selling a different library, probably kr8s (more on that later).
When the Python client earns its keep
The client earns its keep in three situations.
When you need to do something kubectl cannot express as one command. Multi-step workflows — list pods, filter by label, scale each deployment in proportion to its current replica count, restart the ones that did not roll cleanly — are easier in Python than in a Bash script that shells out to kubectl fifty times. The client keeps the connection open, handles auth once, and exposes the full API surface.
When you need to react to cluster events. Watch loops in Python are native. A controller that subscribes to pod events, processes them, and calls back into the API is a small Python program. The same loop in Bash is a polling hack.
When you need the operation to be auditable. A Python script that calls the Kubernetes API logs every request with its parameters, response code, and retry count. A Bash script that shells out to kubectl logs whatever you remembered to print. For anything that needs to pass a review, the Python script is easier to defend.
Outside those three cases, the client is overkill. More on this below.
When you should just use kubectl instead
The Python client is the wrong choice when:
- You are running a one-off command.
kubectl get pods -n kube-systemis faster to type than the equivalent Python. - You are scripting shell pipelines.
kubectl get pods -o json | jq '.items[].metadata.name'is the right tool. The Python client is not a replacement forjq. - You do not need the response in Python. If the next step is “save to a file” or “post to Slack,”
kubectlplus a shell is faster. - You are operating on a remote cluster from your laptop. The client adds latency, dependency surface, and auth-state complexity.
kubectlalready knows how to do this. - You are new to Kubernetes. Learning the Python client before you have
kubectlin muscle memory is learning the wrong layer.
The rule of thumb: if your team already has kubectl muscle memory, do not replace it. Add the Python client to the few workflows that benefit from it.
The install that actually works
The install:
pip install kubernetes
That is it. The library is on PyPI under the name kubernetes. The import is from kubernetes import client, config, watch. There is no kubernetes-client package. There is no need to clone the repo.
The kubernetes package depends on urllib3, six, certifi, python-dateutil, pyyaml, requests, requests-oauthlib, and websocket-client. These are all in the standard Python data stack. No surprises.
For an application that ships as a Docker image, pin the version:
RUN pip install kubernetes==31.0.0
The client version roughly tracks the Kubernetes minor version. Pinning both the client and the cluster’s minor version avoids the surface where a field added in 1.29 is missing from the 1.28 client you happen to have installed.
For a PaaS like RunxBuild, the version pin lives in the service’s dependency manifest. Bumping the client is one config change, not a Dockerfile rewrite.
Loading the kubeconfig the right way
The first real tripwire is kubeconfig loading. The right pattern:
from kubernetes import client, config
# When running inside a pod with a service account:
config.load_incluster_config()
# When running locally with a kubeconfig at ~/.kube/config:
config.load_kube_config()
# When running locally with a custom kubeconfig path:
config.load_kube_config(config_file="/path/to/kubeconfig.yaml")
# When talking to a remote cluster with a custom CA:
config.load_kube_config(
config_file="...",
client_configuration=client.Configuration.get_default_copy(),
)
The two entry points (load_incluster_config and load_kube_config) cover 95% of cases. The trap is mixing them. Calling load_kube_config from inside a pod works, but the kubeconfig in the pod is the one mounted by the platform, not the one on your laptop. Calling load_incluster_config from your laptop fails because there is no service account token. Pick the right one for the environment, and do not write a fallback that tries both.
For production code, prefer load_incluster_config when running inside a pod and load_kube_config when running outside. The pattern:
import os
from kubernetes import client, config
if os.getenv("KUBERNETES_SERVICE_HOST"):
config.load_incluster_config()
else:
config.load_kube_config()
The KUBERNETES_SERVICE_HOST environment variable is set by every kubelet on every pod. If it is set, you are inside. If it is not, you are outside.
A real example: list, create, watch
The three operations you will actually use.
List pods in a namespace:
from kubernetes import client, config
config.load_kube_config()
v1 = client.CoreV1Api()
ret = v1.list_namespaced_pod(namespace="default")
for pod in ret.items:
print(pod.metadata.name, pod.status.phase)
v1 is the CoreV1 API client. There is one per API group: CoreV1Api for pods and services, AppsV1Api for deployments and statefulsets, BatchV1Api for jobs and cronjobs, and so on. Pick the right one for the resource you are touching.
Create a deployment:
from kubernetes import client, config
config.load_kube_config()
apps = client.AppsV1Api()
deployment = client.V1Deployment(
metadata=client.V1ObjectMeta(name="hello"),
spec=client.V1DeploymentSpec(
replicas=2,
selector=client.V1LabelSelector(match_labels={"app": "hello"}),
template=client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "hello"}),
spec=client.V1PodSpec(
containers=[
client.V1Container(
name="hello",
image="nginx:1.27",
ports=[client.V1ContainerPort(container_port=80)],
)
]
),
),
),
)
apps.create_namespaced_deployment(namespace="default", body=deployment)
This is the part where Python feels verbose. The YAML is shorter. The Python is more explicit, which is a feature for code that needs to be reviewed and a bug for code that needs to be written fast.
Watch pods in a namespace:
from kubernetes import client, config, watch
config.load_kube_config()
v1 = client.CoreV1Api()
w = watch.Watch()
for event in w.stream(v1.list_namespaced_pod, namespace="default"):
print(event["type"], event["object"].metadata.name, event["object"].status.phase)
watch.Watch().stream() returns a generator that yields events as they happen. The loop runs until you break out or the watch times out (the default is around 5 minutes; pass timeout_seconds= to control it).
The watch loop is the operation the client was designed for. A kubectl get pods -w is fine for one terminal; a watch loop in Python is the right answer for anything that needs to react to cluster state.
The pattern that prevents the 2 a.m. page
The pattern that keeps a Python client script from paging you at 2 a.m. is the one that bakes in retries, timeouts, and pagination from the first line of code.
Retries. The Kubernetes API returns 429 (Too Many Requests) and 5xx on transient failures. The client does not retry by default. Add a retry wrapper:
from kubernetes.client.rest import ApiException
def with_retry(fn, *args, max_retries=3, **kwargs):
for attempt in range(max_retries):
try:
return fn(*args, **kwargs)
except ApiException as e:
if e.status in (429, 500, 502, 503, 504) and attempt < max_retries - 1:
continue
raise
Timeouts. The default urllib3 timeout is 10 seconds. For cluster operations, raise it:
import urllib3
from kubernetes import client
configuration = client.Configuration.get_default_copy()
configuration.timeout = 30
client.Configuration.set_default(configuration)
Pagination. Every list operation returns a paginated result. The default page size is 500. For a cluster with more than 500 pods in a namespace, the response is truncated. Use the limit and continue parameters:
ret = v1.list_namespaced_pod(namespace="default", limit=200)
while ret.metadata._continue:
ret = v1.list_namespaced_pod(
namespace="default",
limit=200,
continue_=ret.metadata._continue,
)
# process ret.items
The pattern is mechanical but easy to forget. A script that does not handle pagination will silently miss resources on a busy cluster.
Logging. Log every request and response at INFO level. The default client logger is silent. Wire it up:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("kubernetes")
logger.setLevel(logging.INFO)
A script that logs every API call is a script you can debug at 2 a.m. A script that does not is a script you rewrite at 2 a.m.
The alternatives worth knowing
kr8s. A higher-level Python client built on the same API. The interface is closer to kubectl than to the OpenAPI spec. A read of pods.get(name="x", namespace="default") returns a pod object with the same methods a developer would expect. For scripts that do not need the full API surface, kr8s is a real productivity gain. The trade-off is that it is younger and the API surface is narrower.
kubectl via subprocess. The pattern of shelling out to kubectl from Python. The advantage is that the tool does the heavy lifting (auth, retries, output formatting). The disadvantage is that parsing kubectl output is brittle. For one-off scripts where the next step is “show me what happened,” shelling out is fine. For scripts that need to act on the response, it is not.
Helm + Python. For deployments that are already Helm charts, the Python Helm SDK is the right layer. The Python client is for talking to the API directly, not for managing releases.
CD tools (ArgoCD, Flux). For anything that looks like “keep this manifest applied,” a CD tool is the right answer. The Python client is for the workflows the CD tool does not cover.
The opinion this post is built on
The opinion is that the Kubernetes Python client is a power tool, not a starter tool. It is the right answer when you need the API surface. It is the wrong answer when you need a friendly abstraction. The kubectl muscle memory is worth keeping. The Python script that augments it is worth writing. The library that promises to replace both is worth being suspicious of.
A useful exercise: pick one workflow that today is a Bash script shelling out to kubectl, and rewrite it in Python with the official client. The rewrite is an afternoon. The wins are auditable logs, native retry, and a script you can run from CI without pretending the YAML you pasted is reproducible.
FAQ
What is the Kubernetes Python client?
The official Python library for the Kubernetes API. Generated from the OpenAPI spec, maintained at github.com/kubernetes-client/python, distributed on PyPI as the kubernetes package. Every Kubernetes resource is a Python class; every field on every resource is a Python attribute.
Should I use the Python client or kubectl?
For one-off commands and shell pipelines, use kubectl. For multi-step workflows, watch loops, and audited operations, use the Python client. The two are complementary, not competitors.
How do I install the Kubernetes Python client?
pip install kubernetes. That is the only step for local development. For production, pin the version: pip install kubernetes==31.0.0.
How do I authenticate from inside a Kubernetes pod?
config.load_incluster_config() reads the service account token mounted by the kubelet at /var/run/secrets/kubernetes.io/serviceaccount/. This is the default and the right answer for any workload running inside the cluster.
How do I authenticate from my laptop?
config.load_kube_config() reads the kubeconfig at ~/.kube/config (or the path in $KUBECONFIG). For multi-cluster setups, pass context="my-cluster" to select a context.
What is the difference between the kubernetes client and kr8s?
The official kubernetes client is generated from the OpenAPI spec. kr8s is a higher-level wrapper that exposes a friendlier interface. kr8s is faster to write for common operations; the official client is more complete for the full API surface.
Does the Python client support watch loops?
Yes. from kubernetes import watch and use watch.Watch().stream(api.list_namespaced_pod, namespace="default"). The watch loop returns a generator of events with type (ADDED, MODIFIED, DELETED) and object.
How do I handle pagination in the Python client?
Use the limit and continue_ parameters on list operations. The metadata._continue field of the response is the token for the next page. The pattern is a while loop that continues until metadata._continue is empty.
Can I use the Python client with a managed Kubernetes service?
Yes. The client speaks the standard Kubernetes API. For EKS, GKE, AKS, and any other conformant cluster, the kubeconfig from the provider is enough. The client does not know or care that the cluster is managed.
What is the difference between CoreV1Api, AppsV1Api, and BatchV1Api?
One per API group. CoreV1Api is for pods, services, configmaps, secrets, namespaces, and the rest of the core API. AppsV1Api is for deployments, statefulsets, daemonsets, replicasets. BatchV1Api is for jobs and cronjobs. Pick the one that matches the resource.
Does the Python client retry on errors?
No. The default behavior is to raise ApiException on any non-2xx response. Add a retry wrapper for 429 and 5xx responses, with backoff. The official urllib3 retry logic does not kick in for API responses.
How do I watch for events from a controller?
Use watch.Watch().stream() with a list operation. The event object has a type (ADDED, MODIFIED, DELETED) and an object (the resource). The watch loop runs until you break out, the timeout expires, or the connection drops.
What is the right tool for declarative deployments?
Helm, Kustomize, ArgoCD, or Flux. The Python client is for imperative workflows — scripts and controllers — not for declarative deployments. The two layers complement each other.
How does RunxBuild handle Kubernetes automation?
RunxBuild deploys services as containers with a managed API. For teams that need to talk to an external Kubernetes cluster from a RunxBuild service, the Python client works out of the box with the right service account and kubeconfig in environment variables. The hosting calculator shows the cost of the worker hours the automation will spend.