Last year, I started building operators with kubebuilder for a variety of things. Throughout the operators, I used conditions extensively to manage each step of a reconciliation process for a custom resource.
Unfortunately, the sigs.k8s.io's Conditions left me wanting more. I had some issues with conflicts, broken states, etc. And as I dug more into the problem, I started creating a sort of framework around my usage of conditions.
I finally took some times recently to package those findings into a library for others to use. I call it Konditionner. At its core, the goal of Konditionner is to offer:
import "github.com/pier-oliviert/konditionner/pkg/konditions" BuildCondition konditions.ConditionType = "Builds" NetworkCondition konditions.ConditionType = "Network" PodCondition konditions.ConditionType = "Pod" DependenciesCondition konditions.ConditionType = "Dependency" VariablesCondition konditions.ConditionType = "Variables" type MyCRD struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec MySpec `json:"spec,omitempty"` Status MyStatus `json:"status,omitempty"` } // ConditionalResource interface, more about it in the Advisory Lock // section func (m MyCRD) *konditions.Conditions { return &m.Status.Conditions } type MySpec struct { // ... My Fields ... } type MyStatus struct { // ... My Fields ... Conditions konditions.Conditions `json:"conditions,omitempty"` }
The MyCRD custom resource is pretty bare but it's ready to be used! Konditionner doesn't come with any type defined, as that's specific to every use. Here, I created 5 of them, which means that I'll work with 5 conditions in the reconciliation loop.
Let's pretend I have a reconciliation loop where I want to create a pod. The condition's goal is to move from Initialized and progress towards one of two outcome: Created if it was successful, Error if there was an error in the process.
Since reconciliation loop work on top of a distributed database(etcd) and a cache layer, I've found it to be a lot more reliable to create a "lock" on the condition before executing a task.
func (r Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var record MyCRD if err := r.Get(ctx, req, &record); err != nil { return ctrl.Result{}, err } lock := konditions.NewLock(record, r.Client, PodCondition) if lock.Condition().Status == konditions.ConditionError { // Nothing to do return ctrl.Result{}, err } if lock.Condition().Status == konditions.ConditionInitialized { lock.Execute(ctx, func(condition konditions.Condition) error { pod, err := createPod() if err != nil { return err } condition.Status = konditions.ConditionCreated condition.Reason = fmt.Sprintf("Pod created: %s", pod.Name) record.Conditions().setCondition(condition) return nil }) } }
For the lock to work, the record needs adhere to the konditions.ConditionalResource interface. This is why, at the top, the custom resource has the Conditions() method defined:
func (m MyCRD) *konditions.Conditions { return &m.Status.Conditions }
The documentation is available on pkg.go.dev and the source is available on Github.
The above is the detailed content of Konditionner: Manage conditions in your kcustom resources. For more information, please follow other related articles on the PHP Chinese website!