kopf icon indicating copy to clipboard operation
kopf copied to clipboard

Update status of CRD

Open junglie85 opened this issue 4 years ago • 2 comments

Question

I would like to display the number of replicas available in the printer columns of my CRD based on the number of available replicas in a StatefulSet that my operator creates. I can't figure out how to subscribe to events and update my status when they change:

- name: Ready Members
  type: integer
  description: The number of ready members
  jsonPath: .status.readyReplicas
kubectl get mycrd some-resource
NAME                  DESIRED MEMBERS   READY MEMBERS   AGE
some-resource   4                                  99                             34m

I can set the number of replicas in the results delivery:

def update(_**):
    return {'readyReplicas': 99}

However, this means I have to put the readyReplicas under update in my CRD, which prevents me from setting the number of readyReplicas on creation.

I'd like to easily be able to get events from the StatefulSet created by my operator and whenever one of its replicas changes I want to update the status of my CRD at both creation and update. I'm not quite sure how to do that with Kopf and would appreciate some guidance.

Checklist

  • [x] I have read the documentation and searched there for the problem
  • [x] I have searched in the GitHub Issues for similar questions

Keywords

status

junglie85 avatar Jan 18 '21 09:01 junglie85

Sorry for the late response (just noticed the question). I'd suggest using labels to mark the parent-children relationships:

You can use the patch kwarg to directly decide what to store where:

@kopf.on.create('mycr')
def crt(patch, **_):
    patch.status['readyReplicas'] = 99

@kopf.on.update('mycr')
def upd(patch, status, **_):
    patch.status['readyReplicas'] = status.get('readyReplicas', 0) + 1

Cross-resource updates are not that easy. Kopf does not support that yet. You have to use a client library. E.g., with pykube-ng, it would be like this (I use KopfExample-aka-kex for the parent — to quickly test it):

import kopf
import yaml
import pykube


@kopf.on.create('kex')
def create_children(name, **_):
    obj = yaml.safe_load("""
        apiVersion: apps/v1
        kind: StatefulSet
        spec:
          selector:
            matchLabels:
              app: my # has to match .spec.template.metadata.labels
          serviceName: mysvc
          replicas: 3 # by default is 1
          template:
            metadata:
              labels:
                app: my # has to match .spec.selector.matchLabels
            spec:
              terminationGracePeriodSeconds: 10
              containers:
              - name: nginx
                image: k8s.gcr.io/nginx-slim:0.8
    """)
    kopf.adopt(obj)  # includes namespace, name, existing labels.
    kopf.label(obj, {'mycr': name}, nested=['spec.template'])
    api = pykube.HTTPClient(pykube.KubeConfig.from_env())
    pykube.StatefulSet(api, obj).create()


@kopf.on.create('statefulset', labels={'mycr': kopf.PRESENT})
@kopf.on.update('statefulset', labels={'mycr': kopf.PRESENT}, field='status')
@kopf.on.resume('statefulset', labels={'mycr': kopf.PRESENT})
def child_of_mycr_seen(namespace, labels, status, **_):
    parent_name = labels['mycr']
    replicas = status.get('readyReplicas')
    api = pykube.HTTPClient(pykube.KubeConfig.from_env())
    obj = Kex.objects(api, namespace=namespace).get_by_name(parent_name)
    obj.patch({'status': {'readyReplicas': replicas}})  # Beware of HTTP 404! Ignore?


@kopf.on.delete('statefulset', labels={'mycr': kopf.PRESENT})
def child_of_mycr_gone(namespace, labels, name, old, new, **_):
    parent_name = labels['mycr']
    api = pykube.HTTPClient(pykube.KubeConfig.from_env())
    obj = Kex.objects(api, namespace=namespace).get_by_name(parent_name)
    obj.patch({'status': {'readyReplicas': 0, 'status': 'deleted'}})  # Beware of HTTP 404! Ignore?


class Kex(pykube.objects.NamespacedAPIObject):
    version = "kopf.dev/v1"
    endpoint = "kopfexamples"
    kind = 'KopfExample'

Note a few things:

  • field="status" is important for on-update decorators. Otherwise, status is considered non-essential and the changes there are not noticed.
  • HTTP 404 can happen when patching if the parent is gone, but the statefulset is there yet. The best case would be to suppress pykube.ObjectNotFound exceptions.
  • The label mycr is used to establish the parent-child relationship — both for the filters on which StatefulSets to handle, and for the parent name to patch on the children changes (assuming they are in the same namespace).
  • There is no metadata in the YAML template: Kopf will automatically apply namespace, name, and existing labels on adoption of the resource.
  • nested= is optional — it can be used to later filter and handle individual pods of that CR/StatefulSet.

I've tested it locally — it works; even when the StatefulSet is rescaled.

nolar avatar Jan 31 '21 10:01 nolar

Thanks - I've not had time to check this out yet but appreciate you taking the time to help.

junglie85 avatar Feb 06 '21 11:02 junglie85