reconcile不触发90%是watch未注册或rbac权限缺失;需检查setupwithmanager是否调用for()和owns()、crd plural名是否正确、scope与get方式是否匹配。

Reconcile 函数不触发,90% 是 Watch 没注册或权限没配对
你改完代码 make run 启动本地 Operator,但 CR 创建后日志完全没反应——不是代码卡死,也不是 Reconcile 写错了,而是 Manager 根本没开始监听那个资源。常见原因有三个:
-
SetupWithManager里漏了For(&MyApp{}),或者用了Owns(&Deployment{})却没加Watches监听 Deployment 变更 - RBAC 权限缺失:CRD 的
plural名写错(比如myapps写成myapp),kubectl auth can-i list myapps返回no - CRD scope 是
Cluster,但你在 Reconcile 里用req.NamespacedName去Get,实际该用types.NamespacedName{Name: req.Name}(去掉 Namespace)
验证方法:启动后立刻执行 kubectl get events -A --field-selector involvedObject.kind=MyApp,如果连事件都没,说明 Watch 完全没生效。
Reconcile 报错 “not found”,但对象明明存在
日志里反复出现 Reconciler error: not found,查 kubectl describe myapp/myinstance 却能看到资源,这通常不是资源真丢了,而是 OwnerReference 或 Finalizer 导致的“逻辑删除”。
- CR 被删了,但 Finalizer 还在,Operator 会继续 Reconcile 直到清理完成;此时
Get返回NotFound是正常行为,你要在 Reconcile 开头加if apierrors.IsNotFound(err)分支处理 - OwnerReference 的
uid字段和实际对象不匹配(比如手动编辑过 YAML、跨集群迁移过),Kubernetes 会拒绝关联,client.Get就会静默失败 - CRD 升级后旧版本对象未配置 conversion webhook,
Get解码失败也会伪装成NotFound(实际是 unmarshal error)
别直接 panic,先打一行 log.Info("fetching CR", "name", req.Name, "error", err),再结合 kubectl get myapp/myinstance -o yaml 对比 metadata.uid 和 OwnerReference 中的值。
status.conditions 不更新,或更新后立马被覆盖
Operator 看起来在跑,但 kubectl get myapp -o wide 一直显示 Unknown,或者刚写进去的 condition 下一秒就变回 False——根本原因是并发 Reconcile 写冲突,或没走 patch。
- 不要用
client.Status().Update(ctx, cr)整体覆盖 status,它会把其他 controller 写的 condition 一并冲掉 - 每次更新前必须先
client.Get(ctx, key, cr)拿最新对象,再修改cr.Status.Conditions,最后client.Status().Patch(...) - 多个 goroutine 同时操作同一个 CR 实例时,condition 的
lastTransitionTime字段会因时间戳微差导致 patch 冲突,建议用metav1.Now()统一赋值
最简单的验证方式:删掉所有 status 更新代码,只留一行 cr.Status.ObservedGeneration = cr.Generation,再看是否能稳定写入。
本地调试时 client.Get 拿不到 Pod 日志
你想在 Reconcile 里读某个 Pod 的日志做诊断,写了 r.Client.Get(ctx, podKey, &pod) 然后调 pod.Logs(),结果 panic ——因为 client.Client 是带 cache 的读写客户端,Logs() 方法根本不存在。
- Pod 日志必须走纯 REST 调用,得用
corev1clientset.CoreV1().Pods(namespace).GetLogs(podName, &opts) - 这个 clientset 需要从
rest.Config初始化,不能复用r.Client;且GetLogs().Do(ctx).Raw()是阻塞调用,千万别在 Reconcile 主流程里同步执行 - 正确做法是:把日志采集逻辑抽成异步 job,用
ctrl.Log.WithName("log-collector")打日志,状态只存摘要(如 “last log error: timeout”)到status.conditions
真正容易被忽略的是:controller-runtime 的 cache 默认不包含 PodLog 这类非资源端点,你永远不能绕过 clientset 直接从 client 拿日志——这不是限制,是设计使然。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











