kubernetes review
Our Python 3.12 sandbox imported kubernetes 36.0.3 successfully, but the import alone took 2.21 seconds and the install occupied 58 MB across 22 packages. This is the official generated Python client for the Kubernetes API. CoreV1Api covers pods, services, secrets, and namespaces; AppsV1Api handles deployments and related workloads; watch streams changes; stream supports exec and attach. Version 36.0.3 matches the Kubernetes 1.36 API surface, and the project marks the 34, 35, and 36 GA client lines as maintained. The breadth is useful for cluster automation, though generated methods and models make small scripts feel much larger than their job.
Use kubernetes when complete official API coverage and version matching matter more than install weight or generated-call ergonomics. For controllers or mostly custom-resource code, an operator framework or smaller dynamic client removes a lot of plumbing.
We installed it
| Install | ✓ · 0.9s | 22 packages on disk · 58 MB |
| Import | ✓ | import kubernetes in 2.21s · pure Python · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does kubernetes install cleanly?
Yes. In a fresh container with an empty cache, pip install kubernetes finished in 0.9s, leaving 22 packages and 58 MB on disk. pip-audit reported no known vulnerabilities.
What does kubernetes need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import kubernetes succeeded in 2.21s.
kubernetes or kopf: which should you use?
kopf: Use it for a Python operator with handlers, retries, finalizers, and status updates. Use kubernetes when complete official API coverage and version matching matter more than install weight or generated-call ergonomics.
When should you not use kubernetes?
You are building an operator with retries, finalizers, reconciliation state, and event handlers; this client supplies API calls, not an operator framework
Use it if
- You are writing Python automation that must cover the official Kubernetes 1.36 API, including typed resource models
- The code runs in a pod and should authenticate through its mounted service-account token and cluster CA
- You need pod exec or attach through the library's WebSocket-based stream helper
- You need both synchronous calls and an asyncio client under one official package
- You are building an operator with retries, finalizers, reconciliation state, and event handlers; this client supplies API calls, not an operator framework
- Your application image has a tight size or cold-start budget; our install occupied 58 MB across 22 packages and import took 2.21 seconds
- Your clusters span distant Kubernetes minors; the README marks only an exact client and server match as fully sharing the same API objects
- Most work targets custom resources; CustomObjectsApi returns dictionaries, so the generated model layer gives you little benefit
- You plan to reuse one API client after stream() exec or attach; the troubleshooting guide says stream overwrites its request protocol and later ordinary calls can fail
Setup reality
Our fresh Python 3.12 install of kubernetes 36.0.3 succeeded in 0.9 seconds. It left 22 packages using 58 MB, and pip-audit reported 0 known vulnerabilities. The package declares 16 direct dependencies, requires Python 3.10 or newer, and is pure Python. It does not ship py.typed, so type checkers cannot treat the whole installed package as a declared typed distribution. import kubernetes worked and took 2.21 seconds.
Local scripts normally call config.load_kube_config(), which reads the selected kubeconfig context and may run an external credential plugin. That means aws, gke-gcloud-auth-plugin, or az must be installed when the kubeconfig refers to one. In a pod, call config.load_incluster_config() and grant the service account only the RBAC verbs and resources it needs. A 403 arrives as ApiException; the useful Kubernetes Status message is in its body.
Client 36 tracks Kubernetes 1.36. The compatibility table marks older or newer clusters as sharing only part of the API surface, so pin the client major to the clusters you operate. Lists should use label selectors and pagination on large clusters. Watches can end when the server closes the connection or a resource version expires. Production watchers need a relist-and-resume loop instead of assuming one generator runs forever.
Use kubernetes.stream.stream() for exec and attach. Calling the generated connect method directly does not perform the supported WebSocket flow. The README also warns that stream changes the API client's request protocol, so create a new client before returning to normal REST calls. The asyncio package needs an async ApiClient context manager to close its session. Generated method names are long, and many options arrive as keyword arguments, so keep the generated API reference open while wiring uncommon endpoints.
Patterns
Load config for either execution location load-config
from kubernetes import client, config
from kubernetes.config.config_exception import ConfigException
try:
config.load_incluster_config() # running as a pod
except ConfigException:
config.load_kube_config() # local kubeconfig
v1 = client.CoreV1Api()
print(v1.list_namespace().items[0].metadata.name)Try in-cluster configuration first when one script runs locally and as a pod. An explicit kubeconfig context avoids sending a local command to the wrong cluster.
Filter pods at the API server list-with-selectors
v1 = client.CoreV1Api()
pods = v1.list_namespaced_pod(
namespace='default',
label_selector='app=web,tier!=canary',
field_selector='status.phase=Running',
_request_timeout=30,
)
for p in pods.items:
print(p.metadata.name, p.status.pod_ip)Label and field selectors reduce response work. Kubernetes supports field selection only for documented fields, and the server rejects unsupported ones.
Paginate a cluster-wide pod list paginate-large-lists
v1 = client.CoreV1Api()
continue_token = None
while True:
resp = v1.list_pod_for_all_namespaces(limit=500, _continue=continue_token)
for pod in resp.items:
handle(pod)
continue_token = resp.metadata._continue
if not continue_token:
breakThe argument is _continue because continue is a Python keyword. Reuse the returned token without changing the other list parameters.
Create objects from a YAML file apply-manifest
from kubernetes import client, config, utils
config.load_kube_config()
k8s = client.ApiClient()
utils.create_from_yaml(k8s, 'deployment.yaml', namespace='staging')
# or apply semantics instead of create
utils.create_from_yaml(k8s, 'deployment.yaml', apply=True)The default action is create, so an existing object produces a 409 inside FailToCreateError. Use apply only when server-side apply semantics suit the manifest.
Patch deployment replicas patch-resource
apps = client.AppsV1Api()
apps.patch_namespaced_deployment(
name='web',
namespace='default',
body={'spec': {'replicas': 5}},
)
# scale subresource is cheaper for just replicas
apps.patch_namespaced_deployment_scale(
name='web', namespace='default', body={'spec': {'replicas': 5}}
)A patch leaves omitted fields alone. The scale subresource is the narrower endpoint when replicas are the only intended change.
Watch pod changes watch-events
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)
if done:
w.stop()A watch is a long-running HTTP stream, not a permanent subscription. Relist and restart when the resource version expires or the server closes it.
Execute a command in a pod exec-into-pod
from kubernetes.stream import stream
resp = stream(
v1.connect_get_namespaced_pod_exec,
'web-0',
'default',
command=['/bin/sh', '-c', 'cat /etc/hostname'],
container='app',
stderr=True,
stdout=True,
stdin=False,
tty=False,
_preload_content=True,
)
print(resp)Wrap the generated exec method with stream(). Create another API client before ordinary REST calls because stream changes the existing client's protocol handling.
Read a custom resource custom-resources
co = client.CustomObjectsApi()
obj = co.get_namespaced_custom_object(
group='cert-manager.io',
version='v1',
namespace='default',
plural='certificates',
name='web-tls',
)
print(obj['status']['conditions'])CustomObjectsApi returns dictionaries. plural must match the CRD resource name, such as certificates, rather than its singular Kind.
Discover a resource at runtime dynamic-client
from kubernetes import config, dynamic
from kubernetes.client import api_client
client_ = dynamic.DynamicClient(
api_client.ApiClient(configuration=config.load_kube_config())
)
api = client_.resources.get(api_version='apps/v1', kind='Deployment')
for d in api.get(namespace='default').items:
print(d.metadata.name, d.spec.replicas)The dynamic client discovers resource metadata from the API server. This helps with unknown kinds but gives up generated model attributes.
Handle Kubernetes API failures handle-api-errors
import json
from kubernetes.client.rest import ApiException
try:
v1.read_namespaced_secret('db-creds', 'default')
except ApiException as exc:
if exc.status == 404:
create_secret()
elif exc.status == 403:
reason = json.loads(exc.body)['message']
raise PermissionError(reason) from exc
else:
raiseApiException covers HTTP failures. Branch on status and parse body for the server's Status message instead of relying on the short reason phrase.
Maintain a local informer cache shared-informer
from kubernetes import client, config
from kubernetes.informer import SharedInformer, ADDED, MODIFIED, DELETED
config.load_kube_config()
v1 = client.CoreV1Api()
informer = SharedInformer(
v1.list_pod_for_all_namespaces,
resync_period=300,
label_selector='app=web',
)
informer.add_event_handler(ADDED, lambda obj: print('added', obj.metadata.name))
informer.add_event_handler(DELETED, lambda obj: print('gone', obj.metadata.name))
informer.start()
pods = informer.cache.list() # served from memory
one = informer.cache.get_by_key('default/web-0')Handlers run around a shared local cache. Keep callbacks quick and thread-safe because this helper does not provide a controller workqueue.
Use the asyncio client async-client
import asyncio
from kubernetes.aio import client, config
from kubernetes.aio.client.api_client import ApiClient
async def main():
await config.load_kube_config()
async with ApiClient() as api:
v1 = client.CoreV1Api(api)
pods = await v1.list_pod_for_all_namespaces()
for p in pods.items:
print(p.metadata.namespace, p.metadata.name)
asyncio.run(main())Close ApiClient with async with. Leaving it open can produce an unclosed aiohttp session at shutdown.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| kopf | PyPI | Use it for a Python operator with handlers, retries, finalizers, and status updates |
| lightkube | PyPI | Use it when a smaller resource-oriented client is easier for application code |
| pykube-ng | PyPI | Use it for object-style scripting around common Kubernetes resources |
| kr8s | PyPI | Use it for a compact sync and async API centered on resource objects |
More infra guides
boto3 · opentelemetry-api · psutil · distro · @opentelemetry/api · google-cloud-storage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

