Decouple conversion webhook registration from leader election - #678
Conversation
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
There was a problem hiding this comment.
Thank you @sergenyalcin for debugging this issue & fixing it, lgtm. Expressed some concerns about the breaking changes below, let's discuss.
| func SetupWebhookWithManager(mgr ctrl.Manager) error { | ||
| if err := ctrl.NewWebhookManagedBy(mgr, &{{ .TypePackageAlias }}{{ .CRD.Kind }}{}). | ||
| Complete(); err != nil { | ||
| return errors.Wrap(err, "cannot register webhook for the kind {{ .TypePackageAlias }}{{ .CRD.Kind }}") |
There was a problem hiding this comment.
In case the same (version, kind) exists in a different group:
| return errors.Wrap(err, "cannot register webhook for the kind {{ .TypePackageAlias }}{{ .CRD.Kind }}") | |
| return errors.Wrapf(err, "cannot register webhook for the kind %s", {{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind.String()) |
|
|
||
| // register webhooks for the kind {{ .TypePackageAlias }}{{ .CRD.Kind }} | ||
| // if they're enabled. | ||
| if o.StartWebhooks { |
There was a problem hiding this comment.
One concern here is some providers out in the wild might still be relying on this condition (and using this template shipped with upjet). Maybe we need to document the change in the release notes.
There was a problem hiding this comment.
nit: newly introduced SetupWebhookWithManager(mgr ctrl.Manager) can accept o tjcontroller.Options, keeping the logic intact
There was a problem hiding this comment.
I'll add a detailed section about this change to the Release Notes.
| ) | ||
|
|
||
| // SetupWebhookWithManager registers the conversion webhook for {{ .CRD.Kind }}. | ||
| func SetupWebhookWithManager(mgr ctrl.Manager) error { |
There was a problem hiding this comment.
Please see the comment below. We need to document the newly generated webhook setup functions and ask the provider authors to call them. Not all providers will have this bug (not all of them will be using CRD-gating). What could happen is that although they set the controller.Options.StartWebhooks to True, their webhooks won't be started. The good news is that they will get a change to see the changes (especially the removal of the previously generated condition section in Setup function for the webhooks and the newly generated SetupWebhookWithManager function) when they run make generate, so in my opinion, not a huge deal. But we had better still explain the changes and instruct the provider authors how to call the newly generated SetupWebhookWithManager_<API group> function(s) in some release notes. What do you think?
There was a problem hiding this comment.
just to double-check: AFAIU, in "gated" setups, when there are MRAPs disabling a particular Kind, this will register its webhook unconditionally, although the CRD won't exist in cluster.
I think this will still work with controller-runtime, but we should test it to make sure
There was a problem hiding this comment.
I'll add an instruction to the Release Notes.
@erhancagirici, I've tested this and the webhook registration doesn't be an issue for the unregistered CRDs or controller-runtime.
| func SetupWebhookWithManager{{ .Group }}(mgr ctrl.Manager) error { | ||
| for _, setup := range []func(ctrl.Manager) error{ | ||
| {{- range $alias := .Aliases }} | ||
| {{ $alias }}SetupWebhookWithManager, |
There was a problem hiding this comment.
nit: another alternative could be to add {{ $alias }}SetupWebhookWithManager, into Setup_<apiGroup> and SetupGated_<apiGroup> templates:
this would:
- keep
Setup_<apiGroup>logically the same - decouple Webhook registration for
SetupGated_<apiGroup>, the actual setup is still gated, webhooks are registered without gate. - no extra call to
SetupWebhookWithManager_<apiGroup>required from provider developers
On the other hand, the current approach has the advantage of keeping webhook registration clean and explicit.
There was a problem hiding this comment.
Yes, this was an option, but I preferred it because of the clear separation.
Bump github.com/crossplane/upjet/v2 v2.2.0 -> v2.3.0, github.com/crossplane/crossplane-runtime/v2 v2.1.0 -> v2.2.0 and sigs.k8s.io/controller-runtime v0.22.4 -> v0.23.3 (go mod tidy also pulls controller-tools v0.20.0), then regenerate. upjet v2.3.0 decouples conversion-webhook registration from the controller Setup() function (crossplane/upjet#678): per-resource registration moves to a standalone SetupWebhookWithManager and the generated zz_setup.go gains a SetupWebhookWithManager aggregator. The hand-written providerconfig packages gain a matching SetupWebhookWithManager so the regenerated aggregator compiles. This provider does not enable conversion webhooks (StartWebhooks is never set, and no CRD uses a Webhook conversion strategy), so per the v2.3.0 migration notes the entry point is unaffected and needs no change. crossplane-runtime v2.2.0 also carries the ResolveMultiple reference-resolver fix that preserves the authored order of multi-reference lists instead of sorting by resolved UUID (refs crossplane-contrib#462). Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Gábor Somogyi <gabor.somogyi@gmail.com>
Bump github.com/crossplane/upjet/v2 v2.2.0 -> v2.3.0, github.com/crossplane/crossplane-runtime/v2 v2.1.0 -> v2.2.0 and sigs.k8s.io/controller-runtime v0.22.4 -> v0.23.3 (go mod tidy also pulls controller-tools v0.20.0), then regenerate. upjet v2.3.0 decouples conversion-webhook registration from the controller Setup() function (crossplane/upjet#678): per-resource registration moves to a standalone SetupWebhookWithManager and the generated zz_setup.go gains a SetupWebhookWithManager aggregator. The hand-written providerconfig packages gain a matching SetupWebhookWithManager so the regenerated aggregator compiles. This provider does not enable conversion webhooks (StartWebhooks is never set, and no CRD uses a Webhook conversion strategy), so per the v2.3.0 migration notes the entry point is unaffected and needs no change. crossplane-runtime v2.2.0 also carries the ResolveMultiple reference-resolver fix that preserves the authored order of multi-reference lists instead of sorting by resolved UUID (refs #462). Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Gábor Somogyi <gabor.somogyi@gmail.com>
Description of your changes
Conversion webhook registration was accidentally gated behind leader election through the
SetupGated/gate mechanism.SetupGatedstores a closure that only fires on the elected leader, so follower pods never calledctrl.NewWebhookManagedByand their webhook servers returned 404 for every conversion request. With two replicas and--leader-election, some conversion webhook calls fail.The fix separates webhook registration from reconciler setup:
controller.go.tmpl: addsSetupWebhookWithManager— a standalone function that registers the conversion webhook for a single resource kind. Removes theif o.StartWebhooks { ... }block fromSetup; webhook registration is no longer the reconciler's responsibility.setup.go.tmpl: adds theSetupWebhookWithManager{{ .Group }}aggregator, following the same pattern asSetup{{ .Group }}andSetupGated{{ .Group }}, so providers can register all webhooks for a group in a single call.Providers call
SetupWebhookWithManager_<group>once beforemgr.Start()on every pod, independent of the gate and leader election. Reconciler setup remains behind the gate and runs only on the leader.See the generated provider with these changes: crossplane-contrib/provider-upjet-gcp#953 and crossplane-contrib/provider-upjet-aws#2122
I have:
make reviewableto ensure this PR is ready for review.backport release-x.ylabels to auto-backport this PR if necessary.How has this code been tested
Tested locally by sending webhook calls to the webhook handlers.