reconciler-logic
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReconciler 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 operatorTypedReconciler — Preferred Pattern
TypedReconciler —— 推荐模式
The preferred implementation uses , which handles type assertion and provides a strongly-typed :
operator.TypedReconcilerReconcileFuncgo
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.ReconcileActionReconcileActionCreatedReconcileActionUpdatedReconcileActionDeletedReconcileActionResyncedTo requeue a resource after a delay (e.g. for polling an external system), set on the result:
RequeueAftergo
return operator.ReconcileResult{RequeueAfter: 10 * time.Second}, nil推荐使用 实现,它会处理类型断言并提供强类型的 :
operator.TypedReconcilerReconcileFuncgo
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.ReconcileActionReconcileActionCreatedReconcileActionUpdatedReconcileActionDeletedReconcileActionResynced若要在延迟后重新将资源加入队列(例如轮询外部系统),可在结果中设置 :
RequeueAftergo
return operator.ReconcileResult{RequeueAfter: 10 * time.Second}, nilStatus Updates with resource.UpdateObject
resource.UpdateObject使用 resource.UpdateObject
更新状态
resource.UpdateObjectAlways use for status updates — it handles conflicts by fetching the latest version before applying the update function, avoiding errors common when multiple reconcile events race:
resource.UpdateObject409 Conflictgo
_, 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 for status — it sends the full object and races with spec changes made by users.
client.Update状态更新请始终使用 ——它会在应用更新函数前获取最新版本以处理冲突,避免多个 reconcile 事件竞争时常见的 错误:
resource.UpdateObject409 Conflictgo
_, 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"},
)请勿使用 更新状态——它会发送完整对象,可能与用户对规格(spec)的修改产生冲突。
client.UpdateGeneration-Based Skip
基于世代的跳过逻辑
Check at the top of the reconcile function to avoid re-processing unchanged resources:
LastObservedGenerationgo
if obj.GetGeneration() == obj.Status.LastObservedGeneration {
return operator.ReconcileResult{}, nil
}在 reconcile 函数开头检查 ,可避免重复处理未变更的资源:
LastObservedGenerationgo
if obj.GetGeneration() == obj.Status.LastObservedGeneration {
return operator.ReconcileResult{}, nil
}ReconcileOptions
ReconcileOptions
Control how the informer watches resources via on the entry:
BasicReconcileOptionsAppManagedKindgo
{
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: falseOpinionatedReconciler通过 条目里的 控制informer如何监听资源:
AppManagedKindBasicReconcileOptionsgo
{
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: falseOpinionatedReconcilerWatcher — Alternative to Reconciler
Watcher —— Reconciler 的替代方案
A receives distinct , , and callbacks instead of a unified reconcile loop:
WatcherAddUpdateDeletego
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 instead of in . Reconcilers are the preferred pattern; the default scaffolding still uses watchers.
WatcherReconcilerAppManagedKindWatcherAddUpdateDeletego
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
}在 中注册 而非 。Reconciler 是推荐模式,但默认的脚手架仍使用 watcher。
AppManagedKindWatcherReconcilerUnmanagedKinds — Watching Related Resources
UnmanagedKinds —— 监听关联资源
To watch a kind your app doesn't own (e.g. a ConfigMap or a kind from another app), use in :
UnmanagedKindsAppConfiggo
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 或其他应用的类型),可在 中使用 :
AppConfigUnmanagedKindsgo
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
}