feat(resolver): add CRD informer-backed schema resolver with lazy extraction#1176
feat(resolver): add CRD informer-backed schema resolver with lazy extraction#1176a-hilaly wants to merge 4 commits into
Conversation
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: a-hilaly The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
1d49aeb to
895a18e
Compare
|
/retest |
4f25b71 to
c975bbe
Compare
c975bbe to
2987ee4
Compare
2987ee4 to
90a5f68
Compare
5444a98 to
e3f43b6
Compare
jakobmoellerdev
left a comment
There was a problem hiding this comment.
While I like the general idea of a CRD informer to give us events on the schemas, I don't really like how its implemented here in a fully custom index informer. I would much prefer the normal controller runtime informer setup that is present in the other controllers. What was the main reasoning for choosing this way here?
| } | ||
| crdInformerFactory := apiextensionsinformers.NewSharedInformerFactory(apiextensionsClientset, 0) | ||
| crdInformer := crdInformerFactory.Apiextensions().V1().CustomResourceDefinitions() | ||
| if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { |
There was a problem hiding this comment.
this is a complete custom implementation of basically a normal controller for CRDs, I think this should be unified to controller runtime
There was a problem hiding this comment.
Additionally I think we should restrict the informer to only watch for kro based CRDs to limit reach of the events
|
/hold |
…raction Replace the TTL-only schema resolution with a three-layer chained resolver: core (compiled-in) -> CRD (informer-backed) -> fallback (TTL-cached discovery). The CRD resolver uses a custom GVK indexer on the CRD informer for O(1) lookups and lazily extracts OpenAPI schemas on first access, caching them until the CRD is updated or deleted. This gives event-driven invalidation without paying the memory cost of converting schemas for every CRD in the cluster -- only CRDs that kro actually references are materialized. Builder is a pure consumer that takes a SchemaResolver interface with no lifecycle concerns. Resolver wiring, ordering, and lifecycle are owned by cmd/controller/main.go. The CRD resolver takes a CustomResourceDefinitionInformer directly rather than a clientset, and the informer factory is registered with the controller manager for lifecycle management. The cache starts empty with no onAdd handler. Schemas are populated on demand and evicted by onUpdate/onDelete handlers. onUpdate evicts GVKs from both old and new CRD objects to handle served-version changes. DefaultCoreResolver and DefaultFallbackResolver provide opinionated defaults while CRD resolver wiring remains explicit at the call site.
803cb4c to
f635ee3
Compare
f635ee3 to
2398e2b
Compare
2398e2b to
098ccea
Compare
jakobmoellerdev
left a comment
There was a problem hiding this comment.
looks much better already!
| // Used to evict schemas when a CRD is deleted (the object is already | ||
| // gone from the cache by the time the reconciler runs). | ||
| crdGVKs map[string][]schema.GroupVersionKind | ||
| cache cache.Cache |
There was a problem hiding this comment.
i think you can just pass the controller runtime client here
| // called before the manager is started. | ||
| func (r *CRDSchemaResolver) SetupWithManager(mgr ctrl.Manager) error { | ||
| if err := mgr.GetCache().IndexField( | ||
| context.Background(), |
| crd := &apiextensionsv1.CustomResourceDefinition{} | ||
| if err := r.cache.Get(ctx, client.ObjectKey{Name: req.Name}, crd); err != nil { | ||
| if apierrors.IsNotFound(err) { | ||
| // CRD deleted — evict using the reverse index. |
There was a problem hiding this comment.
nit: move cache functions out so you dont have confusing lock paths
|
|
||
| // Look up the CRD from the cache's GVK field index. | ||
| var crdList apiextensionsv1.CustomResourceDefinitionList | ||
| if err := r.cache.List(context.Background(), &crdList, |
There was a problem hiding this comment.
nit, needs different context
| // injectKubeEnvelope adds the standard Kubernetes envelope fields (metadata, | ||
| // apiVersion, kind) to a CRD schema. CRD specs only contain spec/status — | ||
| // the API server injects the rest when serving aggregated OpenAPI. | ||
| func injectKubeEnvelope(s *spec.Schema, namespaced bool) { |
There was a problem hiding this comment.
nit, should probably move to kroschema
| } | ||
|
|
||
| // convertCRDSchema converts a CRD JSONSchemaProps to a kube-openapi spec.Schema. | ||
| func convertCRDSchema(jsonSchema *apiextensionsv1.JSONSchemaProps) (*spec.Schema, error) { |
There was a problem hiding this comment.
nit: dont we already have a schema converter for this somewhere?
|
PR needs rebase. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@a-hilaly: The following tests failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
Replace the TTL-only schema resolution with a three-layer chained resolver:
core (compiled-in) -> CRD (informer-backed) -> fallback (TTL-cached discovery)
The CRD resolver uses a custom GVK indexer on the CRD informer for O(1)
lookups and lazily extracts
OpenAPIschemas on first access, caching themuntil the CRD is updated or deleted. This gives event driven invalidation
without paying the memory cost of converting schemas for every CRD in the
cluster -- only CRDs that kro actually references are materialized.
Builder is a pure consumer that takes a
SchemaResolverinterface with nolifecycle concerns. Resolver wiring, ordering, and lifecycle are owned by
cmd/controller/main.go. The CRD resolver takes aCustomResourceDefinitionInformerdirectly rather than aclientset, and theinformer factory is registered with the controller manager for lifecycle
management.