aiopykube
=========
Throughout the lifetime of this library we have used almost every Python Kubernetes library available.
They all seem to have their own drawbacks but my favourite for clean code and readibility is `pykube-ng `_.
However, ``pykube`` is built on ``requests`` and doesn't natively support ``asyncio`` which we heavily use in Dask. To make it a little easier to work with we have a
``dask_kubernetes.aiopykube`` submodule which provides an async shim that has the same API as ``pykube`` and runs all IO calls in a threadpool executor.
The shim also includes a ``dask_kubernetes.aiopykube.dask`` submodule with custom objects to represent :doc:`Dask custom resources `.
Examples
--------
Iterate over Pods
^^^^^^^^^^^^^^^^^
.. code-block:: python
from dask_kubernetes.aiopykube import HTTPClient, KubeConfig
from dask_kubernetes.aiopykube.objects import Pod
api = HTTPClient(KubeConfig.from_env())
async for pod in Pod.objects(namespace="default"):
# Do something
Create a new Pod
^^^^^^^^^^^^^^^^
.. code-block:: python
from dask_kubernetes.aiopykube import HTTPClient, KubeConfig
from dask_kubernetes.aiopykube.objects import Pod
api = HTTPClient(KubeConfig.from_env())
pod = Pod(
api,
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": name},
"spec": {
"containers": [
{"name": "pause", "image": "gcr.io/google_containers/pause"}
]
},
},
)
await pod.create()
Scale an existing deployment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: python
from dask_kubernetes.aiopykube import HTTPClient, KubeConfig
from dask_kubernetes.aiopykube.objects import Deployment
api = HTTPClient(KubeConfig.from_env())
deployment = await Deployment.objects(api).get_by_name("mydeployment")
await deployment.scale(5)
Delete a DaskCluster custom resource
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: python
from dask_kubernetes.aiopykube import HTTPClient, KubeConfig
from dask_kubernetes.aiopykube.dask import DaskCluster
api = HTTPClient(KubeConfig.from_env())
cluster = await DaskCluster.objects(api, namespace=namespace).get_by_name("mycluster")
await cluster.delete()
API
---
dask_kubernetes.aiopykube.query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autosummary::
dask_kubernetes.aiopykube.query.Query
dask_kubernetes.aiopykube.query.WatchQuery
Query
*****
.. autoclass:: dask_kubernetes.aiopykube.query.Query
:members:
WatchQuery
**********
.. autoclass:: dask_kubernetes.aiopykube.query.WatchQuery
:members:
dask_kubernetes.aiopykube.dask
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autosummary::
dask_kubernetes.aiopykube.dask.DaskCluster
dask_kubernetes.aiopykube.dask.DaskWorkerGroup
dask_kubernetes.aiopykube.dask.DaskJob
dask_kubernetes.aiopykube.dask.DaskAutoscaler
DaskCluster
***********
.. autoclass:: dask_kubernetes.aiopykube.dask.DaskCluster
:members:
DaskWorkerGroup
***************
.. autoclass:: dask_kubernetes.aiopykube.dask.DaskWorkerGroup
:members:
DaskJob
*******
.. autoclass:: dask_kubernetes.aiopykube.dask.DaskJob
:members:
DaskAutoscaler
**************
.. autoclass:: dask_kubernetes.aiopykube.dask.DaskAutoscaler
:members:
dask_kubernetes.aiopykube.objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autosummary::
dask_kubernetes.aiopykube.objects.ConfigMap
dask_kubernetes.aiopykube.objects.CronJob
dask_kubernetes.aiopykube.objects.DaemonSet
dask_kubernetes.aiopykube.objects.Deployment
dask_kubernetes.aiopykube.objects.Endpoint
dask_kubernetes.aiopykube.objects.Event
dask_kubernetes.aiopykube.objects.LimitRange
dask_kubernetes.aiopykube.objects.ResourceQuota
dask_kubernetes.aiopykube.objects.ServiceAccount
dask_kubernetes.aiopykube.objects.Ingress
dask_kubernetes.aiopykube.objects.Job
dask_kubernetes.aiopykube.objects.Namespace
dask_kubernetes.aiopykube.objects.Node
dask_kubernetes.aiopykube.objects.Pod
dask_kubernetes.aiopykube.objects.ReplicationController
dask_kubernetes.aiopykube.objects.ReplicaSet
dask_kubernetes.aiopykube.objects.Secret
dask_kubernetes.aiopykube.objects.Service
dask_kubernetes.aiopykube.objects.PersistentVolume
dask_kubernetes.aiopykube.objects.PersistentVolumeClaim
dask_kubernetes.aiopykube.objects.HorizontalPodAutoscaler
dask_kubernetes.aiopykube.objects.StatefulSet
dask_kubernetes.aiopykube.objects.Role
dask_kubernetes.aiopykube.objects.RoleBinding
dask_kubernetes.aiopykube.objects.ClusterRole
dask_kubernetes.aiopykube.objects.ClusterRoleBinding
dask_kubernetes.aiopykube.objects.PodSecurityPolicy
dask_kubernetes.aiopykube.objects.PodDisruptionBudget
dask_kubernetes.aiopykube.objects.CustomResourceDefinition
ConfigMap
*********
.. autoclass:: dask_kubernetes.aiopykube.objects.ConfigMap
:members:
CronJob
*******
.. autoclass:: dask_kubernetes.aiopykube.objects.CronJob
:members:
DaemonSet
*********
.. autoclass:: dask_kubernetes.aiopykube.objects.DaemonSet
:members:
Deployment
**********
.. autoclass:: dask_kubernetes.aiopykube.objects.Deployment
:members:
Endpoint
********
.. autoclass:: dask_kubernetes.aiopykube.objects.Endpoint
:members:
Event
*****
.. autoclass:: dask_kubernetes.aiopykube.objects.Event
:members:
LimitRange
**********
.. autoclass:: dask_kubernetes.aiopykube.objects.LimitRange
:members:
ResourceQuota
*************
.. autoclass:: dask_kubernetes.aiopykube.objects.ResourceQuota
:members:
ServiceAccount
**************
.. autoclass:: dask_kubernetes.aiopykube.objects.ServiceAccount
:members:
Ingress
*******
.. autoclass:: dask_kubernetes.aiopykube.objects.Ingress
:members:
Job
***
.. autoclass:: dask_kubernetes.aiopykube.objects.Job
:members:
Namespace
*********
.. autoclass:: dask_kubernetes.aiopykube.objects.Namespace
:members:
Node
****
.. autoclass:: dask_kubernetes.aiopykube.objects.Node
:members:
Pod
***
.. autoclass:: dask_kubernetes.aiopykube.objects.Pod
:members:
ReplicationController
*********************
.. autoclass:: dask_kubernetes.aiopykube.objects.ReplicationController
:members:
ReplicaSet
**********
.. autoclass:: dask_kubernetes.aiopykube.objects.ReplicaSet
:members:
Secret
******
.. autoclass:: dask_kubernetes.aiopykube.objects.Secret
:members:
Service
*******
.. autoclass:: dask_kubernetes.aiopykube.objects.Service
:members:
PersistentVolume
****************
.. autoclass:: dask_kubernetes.aiopykube.objects.PersistentVolume
:members:
PersistentVolumeClaim
*********************
.. autoclass:: dask_kubernetes.aiopykube.objects.PersistentVolumeClaim
:members:
HorizontalPodAutoscaler
***********************
.. autoclass:: dask_kubernetes.aiopykube.objects.HorizontalPodAutoscaler
:members:
StatefulSet
***********
.. autoclass:: dask_kubernetes.aiopykube.objects.StatefulSet
:members:
Role
****
.. autoclass:: dask_kubernetes.aiopykube.objects.Role
:members:
RoleBinding
***********
.. autoclass:: dask_kubernetes.aiopykube.objects.RoleBinding
:members:
ClusterRole
***********
.. autoclass:: dask_kubernetes.aiopykube.objects.ClusterRole
:members:
ClusterRoleBinding
******************
.. autoclass:: dask_kubernetes.aiopykube.objects.ClusterRoleBinding
:members:
PodSecurityPolicy
*****************
.. autoclass:: dask_kubernetes.aiopykube.objects.PodSecurityPolicy
:members:
PodDisruptionBudget
*******************
.. autoclass:: dask_kubernetes.aiopykube.objects.PodDisruptionBudget
:members:
CustomResourceDefinition
************************
.. autoclass:: dask_kubernetes.aiopykube.objects.CustomResourceDefinition
:members:
FAQ
---
Why roll our own wrapper?
^^^^^^^^^^^^^^^^^^^^^^^^^
There does appear to be an ``aiopykube`` package on PyPI but it hasn't been updated for a long time and doesn't link to any source on GitHub or other source repository.
This probably fills the same role but we're apprehensive to depend on it in this state.
Why not release this is a separate package?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In theory we could pull the implementation out of ``dask-kubernetes`` into another dependency. If there is demand from the community to do this we could definitely consider it.
Why not use ``kubernetes``, ``kubernetes-asyncio``, ``aiokubernetes``, etc?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The most popular Kubernetes libraries for Python are swagger generated SDKs based on the Kubernetes API spec.
This results in libraries that do not feel Pythonic and only have complex reference documentation without any explaination, how-to or tutorial content.
It's true that ``pykube`` is also a little lacking in documentation, however the code is simple enough and written by a human so code-spelunking isn't too unpleasant.
Why use a threadpool executor instead of ``aiohttp``?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The way ``pykube`` leverages ``requests`` has a lot of status checking and error handling (which is great). However, this would mean rewriting far more of the code in our shim if we
were to switch out the underlying IO library. To try and keep the shim as lightweight as possible we've simply wrapped all methods that make blocking IO calls in calls to ``run_in_executor``.
If we were to ever spin this shim out into a separate package we would probably advocate for a deeper rewrite using ``aiohttp`` instead of ``requests``.
Why are some methods not implemented?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are a couple of design decisions in ``pykube`` which have limited how much of it can be converted to ``asyncio``.
There are a few properties, such as ``pukube.query.Query.query_cache``, that make HTTP calls. Using any kind of IO in a property or dunder like ``__len__`` is generally frowned upon
and therefore there is no ``asyncio`` way to make or call a property asynchronously.
Instead of changing the API we've set these to raise ``NotImplementedError`` with useful exceptions that suggest an alternative way to achieve the same thing.