Update status of CRD
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
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.ObjectNotFoundexceptions. - The label
mycris 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
metadatain 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.
Thanks - I've not had time to check this out yet but appreciate you taking the time to help.