Skip to content
This repository has been archived by the owner on Sep 14, 2020. It is now read-only.

Update the EphemeralVolumeClaim story-telling #97

Merged
merged 8 commits into from
Jan 30, 2020
22 changes: 16 additions & 6 deletions docs/walkthrough/creation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ We want to create a real ``PersistentVolumeClaim`` object
immediately when an ``EphemeralVolumeClaim`` is created this way:

.. code-block:: yaml
:name: evc
:caption: evc.yaml
:name: evc
:caption: evc.yaml

apiVersion: zalando.org/v1
kind: EphemeralVolumeClaim
metadata:
name: my-claim
spec:
size: 10G
size: 1G

.. code-block:: bash

Expand All @@ -28,8 +28,8 @@ First, let's define a template of the persistent volume claim
(with the Python template string, so that no extra template engines are needed):

.. code-block:: yaml
:name: pvc
:caption: pvc.yaml
:name: pvc
:caption: pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
Expand All @@ -46,13 +46,14 @@ First, let's define a template of the persistent volume claim


Let's extend our only handler.
We will use the official Kubernetes client library:
We will use the official Kubernetes client library (``pip install kubernetes``):

.. code-block:: python
:name: creation
:linenos:
:caption: ephemeral.py

import os
import kopf
import kubernetes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: IMO it might be worth a note pip install kubernetes.

import yaml
Expand Down Expand Up @@ -92,5 +93,14 @@ Wait 1-2 seconds, and take a look:

Now, the PVC can be attached to the pods by the same name, as EVC is named.

.. note::
If you have to re-run the operator, and hit a HTTP 409 error saying
"persistentvolumeclaims "my-claim" already exists",
then remove it manually:

.. code-block:: bash

kubectl delete pvc my-claim

.. seealso::
See also :doc:`/handlers`, :doc:`/errors`, :doc:`/hierarchies`.
17 changes: 10 additions & 7 deletions docs/walkthrough/deletion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,25 @@ __ https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
Let's extend the creation handler:

.. code-block:: python
:name: adopting
:linenos:
:caption: ephemeral.py
:emphasize-lines: 18
:name: adopting
:linenos:
:caption: ephemeral.py
:emphasize-lines: 18

import os
import kopf
import kubernetes
import yaml

@kopf.on.create('zalando.org', 'v1', 'ephemeralvolumeclaims')
def create_fn(meta, spec, namespace, logger, **kwargs):
def create_fn(meta, spec, namespace, logger, body, **kwargs):

name = meta.get('name')
size = spec.get('size')
if not size:
raise kopf.PermanentError(f"Size must be set. Got {size!r}.")

path = os.path.join(os.path.dirname(__file__), 'pvc-tpl.yaml')
path = os.path.join(os.path.dirname(__file__), 'pvc.yaml')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os is not previously imported

tmpl = open(path, 'rt').read()
text = tmpl.format(name=name, size=size)
data = yaml.safe_load(text)
Expand All @@ -62,7 +63,9 @@ Let's extend the creation handler:

logger.info(f"PVC child is created: %s", obj)

With this one line, `kopf.adopt` marks the PVC as child of EVC.
return {'pvc-name': obj.metadata.name}

With this one line, `kopf.adopt` marks the PVC as a child of EVC.
This includes: the name auto-generation (if absent), the label propagation,
the namespace assignment to the parent's object namespace,
and, finally, the owner referencing.
Expand Down
23 changes: 18 additions & 5 deletions docs/walkthrough/diffs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ but we will use another feature of Kopf to track one specific field only:
:emphasize-lines: 1, 5

@kopf.on.field('zalando.org', 'v1', 'ephemeralvolumeclaims', field='metadata.labels')
def relabel(old, new, **kwargs):
def relabel(old, new, status, namespace, **kwargs):

pvc_name = status['pvc-name']
pvc_name = status['create_fn']['pvc-name']
pvc_patch = {'metadata': {'labels': new}}

api = kubernetes.client.CoreV1Api()
Expand Down Expand Up @@ -73,7 +73,7 @@ A diff-object has this structure (as an example)::
[('add', ('metadata', 'labels', 'label1'), None, 'new-value'),
('change', ('metadata', 'labels', 'label2'), 'old-value', 'new-value'),
('remove', ('metadata', 'labels', 'label3'), 'old-value', None),
('change', ('spec', 'size'), '10G', '100G')]
('change', ('spec', 'size'), '1G', '2G')]

For the field-handlers, it will be the same,
just the field path will be relative to the handled field,
Expand All @@ -95,10 +95,10 @@ exactly as needed for the patch object (i.e. the field is present there):
:emphasize-lines: 4

@kopf.on.field('zalando.org', 'v1', 'ephemeralvolumeclaims', field='metadata.labels')
def relabel(diff, **kwargs):
def relabel(diff, status, namespace, **kwargs):

labels_patch = {field[0]: new for op, field, old, new in diff}
pvc_name = status['pvc-name']
pvc_name = status['create_fn']['pvc-name']
pvc_patch = {'metadata': {'labels': labels_patch}}

api = kubernetes.client.CoreV1Api()
Expand All @@ -112,3 +112,16 @@ Note that the unrelated labels that were put on the PVC ---e.g., manually,
from the template, by other controllers/operators, beside the labels
coming from the parent EVC--- are persisted and never touched
(unless the same-named label is applied to EVC and propagated to the PVC).

.. code-block:: bash

kubectl describe pvc my-claim

.. code-block:: none

Name: my-claim
Namespace: default
StorageClass: standard
Status: Bound
Labels: application=some-app
owner=me
8 changes: 8 additions & 0 deletions docs/walkthrough/prerequisites.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ Otherwise, let's install minikube locally (e.g. for MacOS):
* `Install kubectl <https://kubernetes.io/docs/tasks/tools/install-kubectl/>`_
* :doc:`Install minikube </minikube>` (a local Kubernetes cluster)
* :doc:`Install Kopf </install>`

.. warning::
Unfortunately, Minikube cannot handle the PVC/PV resizing,
as it uses the HostPath provider internally.
You can either skip the :doc:`updates` step of this tutorial
(where the sizes of the volumes are changed),
or you can use an external Kubernetes cluster
with real dynamically sized volumes.
12 changes: 10 additions & 2 deletions docs/walkthrough/problem.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ __ https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-co
- name: main
resources:
requests:
ephemeral-storage: 10G
ephemeral-storage: 1G
limits:
ephemeral-storage: 10G
ephemeral-storage: 1G

There is a `PersistentVolumeClaim`__ resource kind, but it is persistent,
i.e. not deleted after they are created (only manually deletable).
Expand Down Expand Up @@ -74,3 +74,11 @@ The lifecycle of an ``EphemeralVolumeClaim`` is this:

* Deletes the ``PersistentVolumeClaim`` after either the pod is finished,
or the wait time has elapsed.

.. seealso::
This documentation only highlights the main patterns & tricks of Kopf,
but does not dive deep into the implementation of the operator's domain.
The fully functional solution for ``EphemeralVolumeClaim`` resources,
which is used for this documentation, is available at the following link:

* https://github.com/nolar/ephemeral-volume-claims
8 changes: 4 additions & 4 deletions docs/walkthrough/resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Custom Resource Definition
Let us define a CRD (custom resource definition) for our object.

.. code-block:: yaml
:caption: crd.yaml
:name: crd-yaml
:caption: crd.yaml
:name: crd-yaml

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
Expand Down Expand Up @@ -57,8 +57,8 @@ logic behind the objects yet.
Let's make a sample object:

.. code-block:: yaml
:caption: obj.yaml
:name: obj-yaml
:caption: obj.yaml
:name: obj-yaml

apiVersion: zalando.org/v1
kind: EphemeralVolumeClaim
Expand Down
23 changes: 17 additions & 6 deletions docs/walkthrough/starting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Let's start with the an operator skeleton that does nothing useful --
just to see how it can be started.

.. code-block:: python
:name: skeleton
:linenos:
:caption: ephemeral.py
:name: skeleton
:linenos:
:caption: ephemeral.py

import kopf

Expand All @@ -37,9 +37,20 @@ The output looks like this:

.. code-block:: none

... TODO...

.. todo:: add the outpit sample^^
[2019-05-31 10:42:11,870] kopf.config [DEBUG ] configured via kubeconfig file
[2019-05-31 10:42:11,913] kopf.reactor.peering [WARNING ] Default peering object not found, falling back to the standalone mode.
[2019-05-31 10:42:12,037] kopf.reactor.handlin [DEBUG ] [default/my-claim] First appearance: {'apiVersion': 'zalando.org/v1', 'kind': 'EphemeralVolumeClaim', 'metadata': {'annotations': {'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"zalando.org/v1","kind":"EphemeralVolumeClaim","metadata":{"annotations":{},"name":"my-claim","namespace":"default"}}\n'}, 'creationTimestamp': '2019-05-29T00:41:57Z', 'generation': 1, 'name': 'my-claim', 'namespace': 'default', 'resourceVersion': '47720', 'selfLink': '/apis/zalando.org/v1/namespaces/default/ephemeralvolumeclaims/my-claim', 'uid': '904c2b9b-81aa-11e9-a202-a6e6b278a294'}}
[2019-05-31 10:42:12,038] kopf.reactor.handlin [DEBUG ] [default/my-claim] Adding the finalizer, thus preventing the actual deletion.
[2019-05-31 10:42:12,038] kopf.reactor.handlin [DEBUG ] [default/my-claim] Patching with: {'metadata': {'finalizers': ['KopfFinalizerMarker']}}
[2019-05-31 10:42:12,165] kopf.reactor.handlin [DEBUG ] [default/my-claim] Creation event: {'apiVersion': 'zalando.org/v1', 'kind': 'EphemeralVolumeClaim', 'metadata': {'annotations': {'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"zalando.org/v1","kind":"EphemeralVolumeClaim","metadata":{"annotations":{},"name":"my-claim","namespace":"default"}}\n'}, 'creationTimestamp': '2019-05-29T00:41:57Z', 'finalizers': ['KopfFinalizerMarker'], 'generation': 1, 'name': 'my-claim', 'namespace': 'default', 'resourceVersion': '47732', 'selfLink': '/apis/zalando.org/v1/namespaces/default/ephemeralvolumeclaims/my-claim', 'uid': '904c2b9b-81aa-11e9-a202-a6e6b278a294'}}
A handler is called with body: {'apiVersion': 'zalando.org/v1', 'kind': 'EphemeralVolumeClaim', 'metadata': {'annotations': {'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"zalando.org/v1","kind":"EphemeralVolumeClaim","metadata":{"annotations":{},"name":"my-claim","namespace":"default"}}\n'}, 'creationTimestamp': '2019-05-29T00:41:57Z', 'finalizers': ['KopfFinalizerMarker'], 'generation': 1, 'name': 'my-claim', 'namespace': 'default', 'resourceVersion': '47732', 'selfLink': '/apis/zalando.org/v1/namespaces/default/ephemeralvolumeclaims/my-claim', 'uid': '904c2b9b-81aa-11e9-a202-a6e6b278a294'}, 'spec': {}, 'status': {}}
[2019-05-31 10:42:12,168] kopf.reactor.handlin [DEBUG ] [default/my-claim] Invoking handler 'create_fn'.
[2019-05-31 10:42:12,173] kopf.reactor.handlin [INFO ] [default/my-claim] Handler 'create_fn' succeeded.
[2019-05-31 10:42:12,210] kopf.reactor.handlin [INFO ] [default/my-claim] All handlers succeeded for creation.
[2019-05-31 10:42:12,223] kopf.reactor.handlin [DEBUG ] [default/my-claim] Patching with: {'status': {'kopf': {'progress': None}}, 'metadata': {'annotations': {'kopf.zalando.org/last-handled-configuration': '{"apiVersion": "zalando.org/v1", "kind": "EphemeralVolumeClaim", "metadata": {"name": "my-claim", "namespace": "default"}, "spec": {}}'}}}
[2019-05-31 10:42:12,342] kopf.reactor.handlin [DEBUG ] [default/my-claim] Update event: {'apiVersion': 'zalando.org/v1', 'kind': 'EphemeralVolumeClaim', 'metadata': {'annotations': {'kopf.zalando.org/last-handled-configuration': '{"apiVersion": "zalando.org/v1", "kind": "EphemeralVolumeClaim", "metadata": {"name": "my-claim", "namespace": "default"}, "spec": {}}', 'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"zalando.org/v1","kind":"EphemeralVolumeClaim","metadata":{"annotations":{},"name":"my-claim","namespace":"default"}}\n'}, 'creationTimestamp': '2019-05-29T00:41:57Z', 'finalizers': ['KopfFinalizerMarker'], 'generation': 2, 'name': 'my-claim', 'namespace': 'default', 'resourceVersion': '47735', 'selfLink': '/apis/zalando.org/v1/namespaces/default/ephemeralvolumeclaims/my-claim', 'uid': '904c2b9b-81aa-11e9-a202-a6e6b278a294'}, 'status': {'kopf': {}}}
[2019-05-31 10:42:12,343] kopf.reactor.handlin [INFO ] [default/my-claim] All handlers succeeded for update.
[2019-05-31 10:42:12,362] kopf.reactor.handlin [DEBUG ] [default/my-claim] Patching with: {'status': {'kopf': {'progress': None}}, 'metadata': {'annotations': {'kopf.zalando.org/last-handled-configuration': '{"apiVersion": "zalando.org/v1", "kind": "EphemeralVolumeClaim", "metadata": {"name": "my-claim", "namespace": "default"}, "spec": {}}'}}}

Note that the operator has noticed an object created before the operator
was even started, and handled it -- since it was not handled before.
Expand Down
73 changes: 53 additions & 20 deletions docs/walkthrough/updates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@
Updating the objects
====================

.. warning::
Unfortunately, Minikube cannot handle the PVC/PV resizing,
as it uses the HostPath provider internally.
You can either skip this step of the tutorial,
or you can use an external Kubernetes cluster
with real dynamically sized volumes.

Previously (:doc:`creation`),
we have implemented a handler for the creation of an ``EphemeralVolumeClaim`` (EVC),
and created the corresponding ``PersistantVolumeClaim`` (PVC).

What will happen if we change the size of the EVC when it already exists?
E.g., with:

.. code-block:: bash

kubectl edit evc my-claim

Or by patching it:

.. code-block:: bash

kubectl patch evc my-claim -p '{"spec": {"resources": {"requests": {"storage": "100G"}}}}'

The PVC must be updated accordingly to match its parent EVC.

First, we have to remember the name of the created PVC:
Expand All @@ -28,17 +23,17 @@ with one additional line:
.. code-block:: python
:linenos:
:caption: ephemeral.py
:emphasize-lines: 23
:emphasize-lines: 24

@kopf.on.create('zalando.org', 'v1', 'ephemeralvolumeclaims')
def create_fn(spec, meta, namespace, logger, **kwargs):
def create_fn(body, spec, meta, namespace, logger, **kwargs):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need body here. delete kopf.adopt(data, owner=body) because you only show it later in cascaded deletion.


name = meta.get('name')
size = spec.get('size')
if not size:
raise kopf.PermanentError(f"Size must be set. Got {size!r}.")

path = os.path.join(os.path.dirname(__file__), 'pvc-tpl.yaml')
path = os.path.join(os.path.dirname(__file__), 'pvc.yaml')
tmpl = open(path, 'rt').read()
text = tmpl.format(size=size, name=name)
data = yaml.safe_load(text)
Expand All @@ -61,11 +56,16 @@ We can see that with kubectl:

.. code-block:: bash

kubectl describe evc my-claim
kubectl get -o yaml evc my-claim

.. code-block:: none
.. code-block:: yaml

TODO
spec:
size: 1G
status:
create_fn:
pvc-name: my-claim
kopf: {}

Let's add a yet another handler, but for the "update" cause.
This handler gets this stored PVC name from the creation handler,
Expand All @@ -74,11 +74,11 @@ and patches the PVC with the new size from the EVC::
@kopf.on.update('zalando.org', 'v1', 'ephemeralvolumeclaims')
def update_fn(spec, status, namespace, logger, **kwargs):

size = spec.get('create_fn', {}).get('size', None)
size = spec.get('size', None)
if not size:
raise kopf.PermanentError(f"Size must be set. Got {size!r}.")

pvc_name = status['pvc-name']
pvc_name = status['create_fn']['pvc-name']
pvc_patch = {'spec': {'resources': {'requests': {'storage': size}}}}

api = kubernetes.client.CoreV1Api()
Expand All @@ -89,3 +89,36 @@ and patches the PVC with the new size from the EVC::
)

logger.info(f"PVC child is updated: %s", obj)

Now, let's change the EVC's size:

.. code-block:: bash

kubectl edit evc my-claim

Or by patching it:

.. code-block:: bash

kubectl patch evc my-claim --type merge -p '{"spec": {"size": "2G"}}'

Keep in mind the PVC size can only be increased, never decreased.

Give the operator few seconds to handle the change.

Check the size of the actual PV behind the PVC, which is now increased:

.. code-block:: bash

kubectl get pv

.. code-block:: none

NAME CAPACITY ACCESS MODES ...
pvc-a37b65bd-8384-11e9-b857-42010a800265 2Gi RWO ...

.. warning::
Kubernetes & ``kubectl`` improperly show the capacity of PVCs:
it remains the same (1G) event after the change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

event --> even

The size of actual PV (Persistent Volume) of each PVC is important!
This issue is not related to Kopf, so we go around it.