reconciler-logic

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Reconciler Logic

Reconciler 逻辑

Reconcilers provide the asynchronous business logic layer of a grafana-app-sdk app. When a resource is created, updated, or deleted, the SDK enqueues a reconcile event. The reconciler's job is to observe the current state of the resource and take whatever actions are needed to drive the system toward the desired state.
Reconcilers run asynchronously after a resource has been persisted — they are distinct from admission handlers, which run synchronously on ingress.
Reconciler 是 grafana-app-sdk 应用的异步业务逻辑层。当资源被创建、更新或删除时,SDK 会将一个 reconcile 事件加入队列。Reconciler 的职责是观察资源的当前状态,并采取必要的操作推动系统向期望状态发展。
Reconciler 会在资源持久化后异步运行——这与准入处理器(admission handlers)不同,后者在入口处同步运行。

Getting Stubs

获取代码模板

For standalone apps, generate reconciler stubs with:
bash
grafana-app-sdk project component add operator
对于独立应用,可通过以下命令生成 Reconciler 代码模板:
bash
grafana-app-sdk project component add operator

TypedReconciler — Preferred Pattern

TypedReconciler —— 推荐模式

The preferred implementation uses
operator.TypedReconciler
, which handles type assertion and provides a strongly-typed
ReconcileFunc
:
go
type MyKindReconciler struct {
    operator.TypedReconciler[*v1alpha1.MyKind]
    client resource.Client
}

func NewMyKindReconciler(client resource.Client) *MyKindReconciler {
    r := &MyKindReconciler{client: client}
    r.ReconcileFunc = r.reconcile  // wire the typed func
    return r
}

func (r *MyKindReconciler) reconcile(
    ctx context.Context,
    req operator.TypedReconcileRequest[*v1alpha1.MyKind],
) (operator.ReconcileResult, error) {
    obj := req.Object

    // Skip if already reconciled this generation
    if obj.GetGeneration() == obj.Status.LastObservedGeneration && req.Action != operator.ReconcileActionDeleted {
        return operator.ReconcileResult{}, nil
    }

    log := logging.FromContext(ctx).With("name", obj.GetName(), "namespace", obj.GetNamespace())
    log.Info("reconciling", "action", operator.ResourceActionFromReconcileAction(req.Action))

    // Handle deletion
    if req.Action == operator.ReconcileActionDeleted {
        return operator.ReconcileResult{}, nil
    }

    // ... business logic ...

    // Atomic status update with conflict resolution
    _, err := resource.UpdateObject(ctx, r.client, obj.GetStaticMetadata().Identifier(),
        func(obj *v1alpha1.MyKind, _ bool) (*v1alpha1.MyKind, error) {
            obj.Status.LastObservedGeneration = obj.GetGeneration()
            obj.Status.State = "Ready"
            return obj, nil
        },
        resource.UpdateOptions{Subresource: "status"},
    )
    return operator.ReconcileResult{}, err
}
operator.ReconcileAction
values:
ReconcileActionCreated
,
ReconcileActionUpdated
,
ReconcileActionDeleted
,
ReconcileActionResynced
.
To requeue a resource after a delay (e.g. for polling an external system), set
RequeueAfter
on the result:
go
return operator.ReconcileResult{RequeueAfter: 10 * time.Second}, nil
推荐使用
operator.TypedReconciler
实现,它会处理类型断言并提供强类型的
ReconcileFunc
go
type MyKindReconciler struct {
    operator.TypedReconciler[*v1alpha1.MyKind]
    client resource.Client
}

func NewMyKindReconciler(client resource.Client) *MyKindReconciler {
    r := &MyKindReconciler{client: client}
    r.ReconcileFunc = r.reconcile  // 绑定类型化函数
    return r
}

func (r *MyKindReconciler) reconcile(
    ctx context.Context,
    req operator.TypedReconcileRequest[*v1alpha1.MyKind],
) (operator.ReconcileResult, error) {
    obj := req.Object

    // 若当前世代已完成 reconcile,则跳过
    if obj.GetGeneration() == obj.Status.LastObservedGeneration && req.Action != operator.ReconcileActionDeleted {
        return operator.ReconcileResult{}, nil
    }

    log := logging.FromContext(ctx).With("name", obj.GetName(), "namespace", obj.GetNamespace())
    log.Info("reconciling", "action", operator.ResourceActionFromReconcileAction(req.Action))

    // 处理删除操作
    if req.Action == operator.ReconcileActionDeleted {
        return operator.ReconcileResult{}, nil
    }

    // ... 业务逻辑 ...

    // 带冲突解决的原子状态更新
    _, err := resource.UpdateObject(ctx, r.client, obj.GetStaticMetadata().Identifier(),
        func(obj *v1alpha1.MyKind, _ bool) (*v1alpha1.MyKind, error) {
            obj.Status.LastObservedGeneration = obj.GetGeneration()
            obj.Status.State = "Ready"
            return obj, nil
        },
        resource.UpdateOptions{Subresource: "status"},
    )
    return operator.ReconcileResult{}, err
}
operator.ReconcileAction
的取值包括:
ReconcileActionCreated
ReconcileActionUpdated
ReconcileActionDeleted
ReconcileActionResynced
若要在延迟后重新将资源加入队列(例如轮询外部系统),可在结果中设置
RequeueAfter
go
return operator.ReconcileResult{RequeueAfter: 10 * time.Second}, nil

Status Updates with
resource.UpdateObject

使用
resource.UpdateObject
更新状态

Always use
resource.UpdateObject
for status updates — it handles conflicts by fetching the latest version before applying the update function, avoiding
409 Conflict
errors common when multiple reconcile events race:
go
_, err := resource.UpdateObject(ctx, r.client, identifier,
    func(obj *v1alpha1.MyKind, exists bool) (*v1alpha1.MyKind, error) {
        obj.Status.LastObservedGeneration = obj.GetGeneration()
        obj.Status.State = "Ready"
        obj.Status.Message = ""
        return obj, nil
    },
    resource.UpdateOptions{Subresource: "status"},
)
Do not use
client.Update
for status — it sends the full object and races with spec changes made by users.
状态更新请始终使用
resource.UpdateObject
——它会在应用更新函数前获取最新版本以处理冲突,避免多个 reconcile 事件竞争时常见的
409 Conflict
错误:
go
_, err := resource.UpdateObject(ctx, r.client, identifier,
    func(obj *v1alpha1.MyKind, exists bool) (*v1alpha1.MyKind, error) {
        obj.Status.LastObservedGeneration = obj.GetGeneration()
        obj.Status.State = "Ready"
        obj.Status.Message = ""
        return obj, nil
    },
    resource.UpdateOptions{Subresource: "status"},
)
请勿使用
client.Update
更新状态——它会发送完整对象,可能与用户对规格(spec)的修改产生冲突。

Generation-Based Skip

基于世代的跳过逻辑

Check
LastObservedGeneration
at the top of the reconcile function to avoid re-processing unchanged resources:
go
if obj.GetGeneration() == obj.Status.LastObservedGeneration {
    return operator.ReconcileResult{}, nil
}
在 reconcile 函数开头检查
LastObservedGeneration
,可避免重复处理未变更的资源:
go
if obj.GetGeneration() == obj.Status.LastObservedGeneration {
    return operator.ReconcileResult{}, nil
}

ReconcileOptions

ReconcileOptions

Control how the informer watches resources via
BasicReconcileOptions
on the
AppManagedKind
entry:
go
{
    Kind:       mykindv1alpha1.MyKindKind(),
    Reconciler: reconciler,
    ReconcileOptions: simple.BasicReconcileOptions{
        Namespace:      "my-namespace",          // watch one namespace; default is all
        LabelFilters:   []string{"env=prod"},    // only reconcile matching resources
        FieldSelectors: []string{"status.phase=Running"},
        UsePlain:       false,                   // false = wrap in OpinionatedReconciler (default)
                                                 // true  = use reconciler directly, no finalizer management
    },
},
UsePlain: false
(default) wraps your reconciler in the
OpinionatedReconciler
, which manages finalizers automatically to ensure clean deletion.
通过
AppManagedKind
条目里的
BasicReconcileOptions
控制informer如何监听资源:
go
{
    Kind:       mykindv1alpha1.MyKindKind(),
    Reconciler: reconciler,
    ReconcileOptions: simple.BasicReconcileOptions{
        Namespace:      "my-namespace",          // 监听单个命名空间;默认监听所有
        LabelFilters:   []string{"env=prod"},    // 仅 reconcile 匹配标签的资源
        FieldSelectors: []string{"status.phase=Running"},
        UsePlain:       false,                   // false = 使用 OpinionatedReconciler 包装(默认)
                                                 // true  = 直接使用 reconciler,不管理终结器
    },
},
UsePlain: false
(默认)会将你的 reconciler 包装进
OpinionatedReconciler
,它会自动管理终结器以确保资源被干净删除。

Watcher — Alternative to Reconciler

Watcher —— Reconciler 的替代方案

A
Watcher
receives distinct
Add
,
Update
, and
Delete
callbacks instead of a unified reconcile loop:
go
type MyKindWatcher struct {
    client resource.Client
}

func (w *MyKindWatcher) Add(ctx context.Context, obj resource.Object) error {
    typed := obj.(*v1alpha1.MyKind)
    // handle create
    return nil
}

func (w *MyKindWatcher) Update(ctx context.Context, obj, old resource.Object) error {
    typed := obj.(*v1alpha1.MyKind)
    // handle update
    return nil
}

func (w *MyKindWatcher) Delete(ctx context.Context, obj resource.Object) error {
    // handle delete
    return nil
}

func (w *MyKindWatcher) Sync(ctx context.Context, obj resource.Object) error {
    // called on resync; handle like Add if needed
    return nil
}
Register with
Watcher
instead of
Reconciler
in
AppManagedKind
. Reconcilers are the preferred pattern; the default scaffolding still uses watchers.
Watcher
会接收独立的
Add
Update
Delete
回调,而非统一的 reconcile 循环:
go
type MyKindWatcher struct {
    client resource.Client
}

func (w *MyKindWatcher) Add(ctx context.Context, obj resource.Object) error {
    typed := obj.(*v1alpha1.MyKind)
    // 处理创建操作
    return nil
}

func (w *MyKindWatcher) Update(ctx context.Context, obj, old resource.Object) error {
    typed := obj.(*v1alpha1.MyKind)
    // 处理更新操作
    return nil
}

func (w *MyKindWatcher) Delete(ctx context.Context, obj resource.Object) error {
    // 处理删除操作
    return nil
}

func (w *MyKindWatcher) Sync(ctx context.Context, obj resource.Object) error {
    // 重新同步时调用;若需要可按 Add 逻辑处理
    return nil
}
AppManagedKind
中注册
Watcher
而非
Reconciler
。Reconciler 是推荐模式,但默认的脚手架仍使用 watcher。

UnmanagedKinds — Watching Related Resources

UnmanagedKinds —— 监听关联资源

To watch a kind your app doesn't own (e.g. a ConfigMap or a kind from another app), use
UnmanagedKinds
in
AppConfig
:
go
UnmanagedKinds: []simple.AppUnmanagedKind{
    {
        Kind:       corev1.ConfigMapKind(),
        Reconciler: &ConfigMapReconciler{},
        ReconcileOptions: simple.UnmanagedKindReconcileOptions{
            Namespace:      "my-namespace",
            LabelFilters:   []string{"app=my-app"},
            UseOpinionated: false, // don't add finalizers to unmanaged resources
        },
    },
},
若要监听应用不拥有的类型(例如 ConfigMap 或其他应用的类型),可在
AppConfig
中使用
UnmanagedKinds
go
UnmanagedKinds: []simple.AppUnmanagedKind{
    {
        Kind:       corev1.ConfigMapKind(),
        Reconciler: &ConfigMapReconciler{},
        ReconcileOptions: simple.UnmanagedKindReconcileOptions{
            Namespace:      "my-namespace",
            LabelFilters:   []string{"app=my-app"},
            UseOpinionated: false, // 不为非托管资源添加终结器
        },
    },
},

Registration in app.go

在 app.go 中注册

go
func New(cfg app.Config) (app.App, error) {
    cfg.KubeConfig.APIPath = "/apis"

    client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()).
        ClientFor(mykindv1alpha2.MyKindKind())
    if err != nil {
        return nil, fmt.Errorf("creating client: %w", err)
    }

    a, err := simple.NewApp(simple.AppConfig{
        Name:       "my-app",
        KubeConfig: cfg.KubeConfig,
        ManagedKinds: []simple.AppManagedKind{
            {
                Kind:       mykindv1alpha1.MyKindKind(),
                Validator:  NewValidator(),
                Mutator:    NewMutator(),
            },
            {
                // Attach reconciler to latest version only
                Kind:       mykindv1alpha2.MyKindKind(),
                Reconciler: NewMyKindReconciler(client),
                Validator:  NewValidator(),
                Mutator:    NewMutator(),
            },
        },
    })
    if err != nil {
      return nil, fmt.Errorf("error creating app: %w", err)
    }
    if err = a.ValidateManifest(cfg.ManifestData); err != nil {
        return nil, fmt.Errorf("app manifest validation failed: %w", err)
    }
    return a, nil
}
go
func New(cfg app.Config) (app.App, error) {
    cfg.KubeConfig.APIPath = "/apis"

    client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()).
        ClientFor(mykindv1alpha2.MyKindKind())
    if err != nil {
        return nil, fmt.Errorf("creating client: %w", err)
    }

    a, err := simple.NewApp(simple.AppConfig{
        Name:       "my-app",
        KubeConfig: cfg.KubeConfig,
        ManagedKinds: []simple.AppManagedKind{
            {
                Kind:       mykindv1alpha1.MyKindKind(),
                Validator:  NewValidator(),
                Mutator:    NewMutator(),
            },
            {
                // 仅将 reconciler 附加到最新版本
                Kind:       mykindv1alpha2.MyKindKind(),
                Reconciler: NewMyKindReconciler(client),
                Validator:  NewValidator(),
                Mutator:    NewMutator(),
            },
        },
    })
    if err != nil {
      return nil, fmt.Errorf("error creating app: %w", err)
    }
    if err = a.ValidateManifest(cfg.ManifestData); err != nil {
        return nil, fmt.Errorf("app manifest validation failed: %w", err)
    }
    return a, nil
}

Resources

参考资源