Repository: gardener/gardener
Pull request #12156: Speed-up bring-up of Gardener and a seed using `gardener-operator`-based setup
Author: login: rfranzke
Labels: name: kind/enhancement
description: Enhancement, improvement, extension
name: area/dev-productivity
description: Developer productivity related (how to improve development)
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: cla: yes
description: Indicates the PR's author has signed the cla-assistant.io CLA.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
Created: 2025-05-21T10:21:21Z | Merged: 2025-05-22T10:44:55Z

**How to categorize this PR?**
<!--
Please select area, kind, and priority for this pull request. This helps the community categorizing it.
Replace below TODOs or exchange the existing identifiers with those that fit best in your opinion.
If multiple identifiers make sense you can also state the commands multiple times, e.g.
  /area control-plane
  /area auto-scaling
  ...

"/area" identifiers:     audit-logging|auto-scaling|backup|compliance|control-plane-migration|control-plane|cost|delivery|dev-productivity|disaster-recovery|documentation|high-availability|logging|metering|monitoring|networking|open-source|ops-productivity|os|performance|quality|robustness|scalability|security|storage|testing|usability|user-management
"/kind" identifiers:     api-change|bug|cleanup|discussion|enhancement|epic|flake|impediment|poc|post-mortem|question|regression|task|technical-debt|test

For Gardener Enhancement Proposals (GEPs), please check the following [documentation](https://github.com/gardener/gardener/tree/master/docs/proposals/README.md) before submitting this pull request.
-->
/area dev-productivity
/kind enhancement

**What this PR does / why we need it**:
This PR speeds up the bring-up of Gardener and a seed (i.e., `make kind-operator-up operator-seed-up`) significantly. This will soon replace the default `gardener*-up` rules based on the deprecated `controlplane` Helm chart.

Benchmarks:
- `master` branch: `12m40s` for `operator-seed-up` on M3 Max (48Gi, 1Ti)
- this branch: `7m15s` for `operator-seed-up` on M1 Max (64Gi, 1Ti)
- `master` branch: `4m57s` for make gardener-up on M3 Max (48Gi, 1Ti)

Naturally, `make gardener-up` is still a bit faster, but the difference to `operator-seed-up` is much smaller now. There might be more opportunity to even tune it further in the future.

**Which issue(s) this PR fixes**:
Part of https://github.com/gardener/gardener/issues/11958

**Special notes for your reviewer**:
/cc @oliver-goetz @ScheererJ 

**Release note**:
<!--
Write your release note:
1. Enter your release note in the below block.
2. If no release note is required, just write "NONE" within the block.

Format of block header: <category> <target_group>
Possible values:
- category:       breaking|noteworthy|feature|bugfix|doc|other
- target_group:   user|operator|developer|dependency
-->
```other operator
NONE
```



Diff:
diff --git a/Makefile b/Makefile
index ba9379ec820..32ddeb86acd 100644
--- a/Makefile
+++ b/Makefile
@@ -416,6 +416,7 @@ operator-seed-up operator-seed-dev: $(SKAFFOLD) $(HELM) $(KUBECTL) operator-up
 	$(SKAFFOLD) $(SKAFFOLD_MODE) -m gardenlet -p $(SKAFFOLD_PROFILE) -f=skaffold.yaml --kubeconfig=$(VIRTUAL_GARDEN_KUBECONFIG) \
 		--cache-artifacts=$(shell ./hack/get-skaffold-cache-artifacts.sh) \
 		--status-check=false --platform="linux/$(SYSTEM_ARCH)" 	# deployments don't exist in virtual-garden, see https://skaffold.dev/docs/status-check/; nodes don't exist in virtual-garden, ensure skaffold use the host architecture instead of amd64, see https://skaffold.dev/docs/workflows/handling-platforms/
+	TIMEOUT=900 ./hack/usage/wait-for.sh garden local VirtualGardenAPIServerAvailable RuntimeComponentsHealthy VirtualComponentsHealthy
 
 operator-seed-down: $(SKAFFOLD) $(HELM) $(KUBECTL)
 	./hack/operator-seed-down.sh --path-kind-kubeconfig $(KUBECONFIG) --path-garden-kubeconfig $(VIRTUAL_GARDEN_KUBECONFIG)
diff --git a/charts/gardener/admission-local/charts/runtime/templates/deployment.yaml b/charts/gardener/admission-local/charts/runtime/templates/deployment.yaml
index d9284400a5d..015e5292bb7 100644
--- a/charts/gardener/admission-local/charts/runtime/templates/deployment.yaml
+++ b/charts/gardener/admission-local/charts/runtime/templates/deployment.yaml
@@ -9,8 +9,12 @@ metadata:
     high-availability-config.resources.gardener.cloud/type: server
     {{- end }}
 spec:
-  revisionHistoryLimit: 5
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 100%
   replicas: {{ .Values.replicaCount }}
+  revisionHistoryLimit: 2
   selector:
     matchLabels:
 {{ include "labels" . | indent 6 }}
@@ -62,8 +66,8 @@ spec:
             path: /healthz
             port: {{ .Values.healthPort }}
             scheme: HTTP
-          initialDelaySeconds: 5
-          periodSeconds: 10
+          initialDelaySeconds: 3
+          periodSeconds: 5
         {{- end }}
         {{- if .Values.readinessProbe.enable }}
         readinessProbe:
@@ -71,7 +75,8 @@ spec:
             path: /readyz
             port: {{ .Values.healthPort }}
             scheme: HTTP
-          initialDelaySeconds: 5
+          initialDelaySeconds: 3
+          periodSeconds: 5
           {{- end }}
 {{- if .Values.resources }}
         resources:
diff --git a/charts/gardener/provider-local/templates/deployment.yaml b/charts/gardener/provider-local/templates/deployment.yaml
index f84ab32c6cb..8ebf5975ea6 100644
--- a/charts/gardener/provider-local/templates/deployment.yaml
+++ b/charts/gardener/provider-local/templates/deployment.yaml
@@ -11,6 +11,10 @@ metadata:
 {{ include "labels" . | indent 4 }}
     high-availability-config.resources.gardener.cloud/type: server
 spec:
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 100%
   replicas: {{ .Values.replicaCount }}
   revisionHistoryLimit: 2
   selector:
@@ -69,13 +73,11 @@ spec:
         - --heartbeat-namespace={{ .Release.Namespace }}
         - --heartbeat-renew-interval-seconds={{ .Values.controllers.heartbeat.renewIntervalSeconds }}
         {{- if .Values.gardener.runtimeCluster.enabled }}
-        - --disable-webhooks="*"
+        - --disable-webhooks=controlplane,dnsconfig,networkpolicy,prometheus,shoot
         - --controllers=backupbucket,dnsrecord,local-ext-shoot
         - --extension-class=garden
         {{- else }}
-        - --webhook-config-namespace={{ .Release.Namespace }}
-        - --webhook-config-service-port={{ .Values.webhookConfig.servicePort }}
-        - --webhook-config-server-port={{ tpl .Values.webhookConfig.serverPort . }}
+        - --disable-webhooks="rollout-speedup"
         {{- range .Values.webhooks.prometheus.remoteWriteURLs }}
         - --prometheus-remote-write-url={{ . }}
         {{- end }}
@@ -85,6 +87,9 @@ spec:
         - --disable-controllers={{ .Values.disableControllers | join "," }}
         - --disable-webhooks={{ .Values.disableWebhooks | join "," }}
         {{- end }}
+        - --webhook-config-namespace={{ .Release.Namespace }}
+        - --webhook-config-service-port={{ .Values.webhookConfig.servicePort }}
+        - --webhook-config-server-port={{ tpl .Values.webhookConfig.serverPort . }}
         - --metrics-bind-address=:{{ tpl .Values.metricsPort . }}
         - --health-bind-address=:{{ tpl .Values.healthPort . }}
         {{- if .Values.gardener.version }}
@@ -109,13 +114,15 @@ spec:
             path: /healthz
             port: {{ tpl .Values.healthPort . }}
             scheme: HTTP
-          initialDelaySeconds: 10
+          initialDelaySeconds: 3
+          periodSeconds: 5
         readinessProbe:
           httpGet:
             path: /readyz
             port: {{ tpl .Values.healthPort . }}
             scheme: HTTP
-          initialDelaySeconds: 5
+          initialDelaySeconds: 3
+          periodSeconds: 5
         ports:
         - name: webhook-server
           containerPort: {{ tpl .Values.webhookConfig.serverPort . }}
diff --git a/charts/gardener/provider-local/templates/service.yaml b/charts/gardener/provider-local/templates/service.yaml
index ce144db5a60..b8b107ba938 100644
--- a/charts/gardener/provider-local/templates/service.yaml
+++ b/charts/gardener/provider-local/templates/service.yaml
@@ -1,7 +1,7 @@
 apiVersion: v1
 kind: Service
 metadata:
-  name: {{ include "name" . }}
+  name: gardener-extension-provider-local
   namespace: {{ .Release.Namespace }}
   annotations:
     networking.resources.gardener.cloud/from-world-to-ports: '[{"protocol":"TCP","port":{{ tpl .Values.webhookConfig.serverPort . }}}]'
diff --git a/cmd/gardener-extension-provider-local/app/options.go b/cmd/gardener-extension-provider-local/app/options.go
index 3d35054015c..af25fe08135 100644
--- a/cmd/gardener-extension-provider-local/app/options.go
+++ b/cmd/gardener-extension-provider-local/app/options.go
@@ -35,6 +35,7 @@ import (
 	networkpolicywebhook "github.com/gardener/gardener/pkg/provider-local/webhook/networkpolicy"
 	nodewebhook "github.com/gardener/gardener/pkg/provider-local/webhook/node"
 	prometheuswebhook "github.com/gardener/gardener/pkg/provider-local/webhook/prometheus"
+	rolloutspeedupwebhook "github.com/gardener/gardener/pkg/provider-local/webhook/rolloutspeedup"
 	shootwebhook "github.com/gardener/gardener/pkg/provider-local/webhook/shoot"
 )
 
@@ -65,6 +66,7 @@ func WebhookSwitchOptions() *extensionscmdwebhook.SwitchOptions {
 		extensionscmdwebhook.Switch(extensionscontrolplanewebhook.WebhookName, controlplanewebhook.AddToManager),
 		extensionscmdwebhook.Switch(extensionsshootwebhook.WebhookName, shootwebhook.AddToManager),
 		extensionscmdwebhook.Switch(dnsconfigwebhook.WebhookName, dnsconfigwebhook.AddToManager),
+		extensionscmdwebhook.Switch(rolloutspeedupwebhook.WebhookName, rolloutspeedupwebhook.AddToManager),
 		extensionscmdwebhook.Switch(networkpolicywebhook.WebhookName, networkpolicywebhook.AddToManager),
 		extensionscmdwebhook.Switch(nodewebhook.WebhookName, nodewebhook.AddToManager),
 		extensionscmdwebhook.Switch(nodewebhook.WebhookNameShoot, nodewebhook.AddShootWebhookToManager),
diff --git a/hack/usage/wait-for.sh b/hack/usage/wait-for.sh
index 302a1abf653..87ecff1cd81 100755
--- a/hack/usage/wait-for.sh
+++ b/hack/usage/wait-for.sh
@@ -9,7 +9,7 @@
 # It takes the resource type, object name, and a list of conditions as arguments
 set -euo pipefail
 
-if [ "$#" -lt 3 ]; then
+if [ "$#" -lt 2 ]; then
   echo "Usage: $0 <resource_type> <object_name> <condition_1> <condition_2> ... <condition_n>
 Note: Namespace/Timeout will be used from the 'NAMESPACE'/'TIMEOUT' environment variable if set, otherwise it is optional.
       TIMEOUT: The operation will be retried until the timeout[default 600 seconds] is reached, with a 5 second sleep interval between each retry.
@@ -22,6 +22,7 @@ OBJECT_NAME=$2
 shift 2
 CONDITIONS=("$@")
 NAMESPACE=${NAMESPACE:-}
+SKIP_LAST_OPERATION_CHECK=${SKIP_LAST_OPERATION_CHECK:-false}
 
 # The number of retries before failing
 TIMEOUT=${TIMEOUT:-600}
@@ -48,7 +49,7 @@ while [ "${retries}" -lt "${TIMEOUT}" ]; do
   fi
 
   # Check last operation state
-  if [ "$LAST_OPERATION_STATE" != "Succeeded" ]; then
+  if [ "$SKIP_LAST_OPERATION_CHECK" != "true" ] && [ "$LAST_OPERATION_STATE" != "Succeeded" ]; then
     LAST_OPERATION_SUCCEEDED=false
   fi
 
diff --git a/pkg/operator/controller/extension/extension/reconciler_delete.go b/pkg/operator/controller/extension/extension/reconciler_delete.go
index 28980e4284c..e4d5afa6e9d 100644
--- a/pkg/operator/controller/extension/extension/reconciler_delete.go
+++ b/pkg/operator/controller/extension/extension/reconciler_delete.go
@@ -7,6 +7,8 @@ package extension
 import (
 	"context"
 	"errors"
+	"fmt"
+	"reflect"
 
 	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -31,7 +33,8 @@ func (r *Reconciler) delete(
 	defer cancel()
 
 	var (
-		g = flow.NewGraph("Extension deletion")
+		reconcileResult reconcile.Result
+		g               = flow.NewGraph("Extension deletion")
 
 		_ = g.Add(flow.Task{
 			Name: "Deleting ControllerRegistration and ControllerDeployment",
@@ -50,7 +53,9 @@ func (r *Reconciler) delete(
 		_ = g.Add(flow.Task{
 			Name: "Handling Extension in runtime cluster",
 			Fn: func(ctx context.Context) error {
-				return r.deployExtensionInRuntime(ctx, log, extension)
+				var err error
+				reconcileResult, err = r.deployExtensionInRuntime(ctx, log, extension)
+				return err
 			},
 		})
 	)
@@ -61,7 +66,13 @@ func (r *Reconciler) delete(
 		Log: log,
 	}); err != nil {
 		conditions.installed = v1beta1helper.UpdatedConditionWithClock(r.Clock, conditions.installed, gardencorev1beta1.ConditionFalse, ReasonDeleteFailed, err.Error())
-		return reconcile.Result{}, errors.Join(err, r.updateExtensionStatus(ctx, log, extension, conditions))
+		if updateErr := r.updateExtensionStatus(ctx, log, extension, conditions); updateErr != nil {
+			return reconcile.Result{}, errors.Join(err, fmt.Errorf("failed to update extension status: %w", updateErr))
+		}
+		if !reflect.DeepEqual(reconcileResult, reconcile.Result{}) {
+			return reconcileResult, nil
+		}
+		return reconcile.Result{}, err
 	}
 
 	if operator.IsExtensionInRuntimeRequired(extension) {
diff --git a/pkg/operator/controller/extension/extension/reconciler_reconcile.go b/pkg/operator/controller/extension/extension/reconciler_reconcile.go
index 8653b6df90b..b04e8cd7ddb 100644
--- a/pkg/operator/controller/extension/extension/reconciler_reconcile.go
+++ b/pkg/operator/controller/extension/extension/reconciler_reconcile.go
@@ -67,7 +67,9 @@ func (r *Reconciler) reconcile(
 		deployExtensionInRuntime = g.Add(flow.Task{
 			Name: "Deploying extension in runtime cluster",
 			Fn: func(ctx context.Context) error {
-				return r.deployExtensionInRuntime(ctx, log, extension)
+				var err error
+				reconcileResult, err = r.deployExtensionInRuntime(ctx, log, extension)
+				return err
 			},
 		})
 
@@ -122,20 +124,20 @@ func (r *Reconciler) reconcile(
 		Log: log,
 	}); err != nil {
 		conditions.installed = v1beta1helper.UpdatedConditionWithClock(r.Clock, conditions.installed, gardencorev1beta1.ConditionFalse, ReasonReconcileFailed, err.Error())
-		if err := r.updateExtensionStatus(ctx, log, extension, conditions); err != nil {
-			return reconcile.Result{}, fmt.Errorf("failed to update extension status: %w", err)
+		if updateErr := r.updateExtensionStatus(ctx, log, extension, conditions); updateErr != nil {
+			return reconcile.Result{}, errors.Join(err, fmt.Errorf("failed to update extension status: %w", updateErr))
 		}
 		if !reflect.DeepEqual(reconcileResult, reconcile.Result{}) {
 			return reconcileResult, nil
 		}
-		return reconcile.Result{}, errors.Join(err, r.updateExtensionStatus(ctx, log, extension, conditions))
+		return reconcile.Result{}, err
 	}
 
 	conditions.installed = v1beta1helper.UpdatedConditionWithClock(r.Clock, conditions.installed, gardencorev1beta1.ConditionTrue, ReasonReconcileSuccess, "Extension has been reconciled successfully")
 	return reconcileResult, r.updateExtensionStatus(ctx, log, extension, conditions)
 }
 
-func (r *Reconciler) deployExtensionInRuntime(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) error {
+func (r *Reconciler) deployExtensionInRuntime(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) (reconcile.Result, error) {
 	if !operator.IsExtensionInRuntimeRequired(extension) {
 		log.V(1).Info("Deployment in runtime cluster not required")
 		return r.runtime.Delete(ctx, log, extension)
diff --git a/pkg/operator/controller/extension/extension/runtime/runtime.go b/pkg/operator/controller/extension/extension/runtime/runtime.go
index 7b870f096ea..c1857094c77 100644
--- a/pkg/operator/controller/extension/extension/runtime/runtime.go
+++ b/pkg/operator/controller/extension/extension/runtime/runtime.go
@@ -12,10 +12,13 @@ import (
 
 	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
+	schedulingv1 "k8s.io/api/scheduling/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/record"
 	podsecurityadmissionapi "k8s.io/pod-security-admission/api"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
 	operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1"
@@ -29,12 +32,14 @@ import (
 	"github.com/gardener/gardener/pkg/utils/oci"
 )
 
+const priorityClassName = v1beta1constants.PriorityClassNameGardenSystem200
+
 // Interface contains functions for the extension deployer in the garden runtime cluster.
 type Interface interface {
 	// Reconcile creates or updates the extension deployment in the garden runtime cluster.
-	Reconcile(context.Context, logr.Logger, *operatorv1alpha1.Extension) error
+	Reconcile(context.Context, logr.Logger, *operatorv1alpha1.Extension) (reconcile.Result, error)
 	// Delete deletes the extension deployment in the garden runtime cluster.
-	Delete(context.Context, logr.Logger, *operatorv1alpha1.Extension) error
+	Delete(context.Context, logr.Logger, *operatorv1alpha1.Extension) (reconcile.Result, error)
 }
 
 type deployer struct {
@@ -46,27 +51,37 @@ type deployer struct {
 }
 
 // Reconcile creates or updates the extension deployment in the garden runtime cluster.
-func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) error {
+func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) (reconcile.Result, error) {
 	if !extensionDeploymentSpecified(extension) {
 		return d.Delete(ctx, log, extension)
 	}
 
+	if err := d.runtimeClientSet.Client().Get(ctx, client.ObjectKey{Name: priorityClassName}, &schedulingv1.PriorityClass{}); err != nil {
+		// Prevent exponential backoff of the controller in case the Garden controller is slow in creating the needed
+		// PriorityClass when creating a fresh Garden.
+		if apierrors.IsNotFound(err) {
+			log.Info("PriorityClass not found yet, requeuing", "priorityClassName", priorityClassName)
+			return reconcile.Result{RequeueAfter: 2 * time.Second}, fmt.Errorf("priority class %q not found yet", priorityClassName)
+		}
+		return reconcile.Result{}, fmt.Errorf("failed checking if priority class %q exists: %w", priorityClassName, err)
+	}
+
 	if err := d.createOrUpdateResources(ctx, extension); err != nil {
-		return err
+		return reconcile.Result{}, err
 	}
 	d.recorder.Event(extension, corev1.EventTypeNormal, "Reconciliation", "Extension applied successfully in runtime cluster")
-	return nil
+	return reconcile.Result{}, nil
 }
 
 // Delete deletes the extension deployment in the garden runtime cluster.
-func (d *deployer) Delete(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) error {
+func (d *deployer) Delete(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) (reconcile.Result, error) {
 	log.Info("Deleting extension resources in garden runtime cluster")
 	if err := d.deleteResources(ctx, log, extension); err != nil {
-		return err
+		return reconcile.Result{}, err
 	}
 
 	d.recorder.Event(extension, corev1.EventTypeNormal, "Deletion", "Extension deployment deleted successfully in runtime cluster")
-	return nil
+	return reconcile.Result{}, nil
 }
 
 func (d *deployer) createOrUpdateResources(ctx context.Context, extension *operatorv1alpha1.Extension) error {
@@ -79,7 +94,7 @@ func (d *deployer) createOrUpdateResources(ctx context.Context, extension *opera
 		"gardener": map[string]any{
 			"runtimeCluster": map[string]any{
 				"enabled":           "true",
-				"priorityClassName": v1beta1constants.PriorityClassNameGardenSystem200,
+				"priorityClassName": priorityClassName,
 			},
 		},
 	}
diff --git a/pkg/operator/controller/extension/extension/runtime/runtime_test.go b/pkg/operator/controller/extension/extension/runtime/runtime_test.go
index 8361b2ff0f1..d54a0f39ae1 100644
--- a/pkg/operator/controller/extension/extension/runtime/runtime_test.go
+++ b/pkg/operator/controller/extension/extension/runtime/runtime_test.go
@@ -14,6 +14,7 @@ import (
 	. "github.com/onsi/gomega"
 	"go.uber.org/mock/gomock"
 	corev1 "k8s.io/api/core/v1"
+	schedulingv1 "k8s.io/api/scheduling/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/record"
@@ -101,6 +102,9 @@ var _ = Describe("Deployment", func() {
 				},
 			},
 		}
+
+		priorityClass := &schedulingv1.PriorityClass{ObjectMeta: metav1.ObjectMeta{Name: "gardener-garden-system-200"}}
+		Expect(runtimeClient.Create(ctx, priorityClass)).To(Succeed())
 	})
 
 	AfterEach(func() {
@@ -110,7 +114,8 @@ var _ = Describe("Deployment", func() {
 
 	Describe("#Reconcile", func() {
 		It("should fail when OCI artifact is not found", func() {
-			Expect(runtime.Reconcile(ctx, log, extension)).To(MatchError(`failed pulling Helm chart from OCI repository "local-extension-runtime:v1.2.3": not found`))
+			_, err := runtime.Reconcile(ctx, log, extension)
+			Expect(err).To(MatchError(`failed pulling Helm chart from OCI repository "local-extension-runtime:v1.2.3": not found`))
 		})
 
 		It("should succeed reconciling the extension resources", func() {
@@ -136,7 +141,8 @@ var _ = Describe("Deployment", func() {
 				return nil
 			})()
 
-			Expect(runtime.Reconcile(ctx, log, extension)).To(Succeed())
+			_, err := runtime.Reconcile(ctx, log, extension)
+			Expect(err).NotTo(HaveOccurred())
 			Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: fmt.Sprintf("extension-%s-garden", extensionName), Namespace: "garden"}, &resourcesv1alpha1.ManagedResource{})).To(Succeed())
 			namespace := &corev1.Namespace{}
 			Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: "runtime-extension-test-extension"}, namespace)).To(Succeed())
@@ -150,7 +156,8 @@ var _ = Describe("Deployment", func() {
 		It("should succeed if extension deployment is not defined", func() {
 			extension.Spec.Deployment.ExtensionDeployment = nil
 
-			Expect(runtime.Reconcile(ctx, log, extension)).To(Succeed())
+			_, err := runtime.Reconcile(ctx, log, extension)
+			Expect(err).NotTo(HaveOccurred())
 
 			mrList := &resourcesv1alpha1.ManagedResourceList{}
 			Expect(runtimeClient.List(ctx, mrList)).To(Succeed())
@@ -160,7 +167,8 @@ var _ = Describe("Deployment", func() {
 
 	Describe("#Delete", func() {
 		It("should succeed if extension was not deployed before", func() {
-			Expect(runtime.Delete(ctx, log, extension)).To(Succeed())
+			_, err := runtime.Delete(ctx, log, extension)
+			Expect(err).NotTo(HaveOccurred())
 
 			mrList := &resourcesv1alpha1.ManagedResourceList{}
 			Expect(runtimeClient.List(ctx, mrList)).To(Succeed())
@@ -174,7 +182,8 @@ var _ = Describe("Deployment", func() {
 			Expect(runtimeClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("extension-%s-garden", extensionName), Namespace: "garden"}})).To(Succeed())
 			Expect(runtimeClient.Create(ctx, &resourcesv1alpha1.ManagedResource{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("extension-%s-garden", extensionName), Namespace: "garden"}, Spec: resourcesv1alpha1.ManagedResourceSpec{SecretRefs: []corev1.LocalObjectReference{{Name: fmt.Sprintf("extension-%s-garden", extensionName)}}}})).To(Succeed())
 
-			Expect(runtime.Delete(ctx, log, extension)).To(Succeed())
+			_, err := runtime.Delete(ctx, log, extension)
+			Expect(err).NotTo(HaveOccurred())
 
 			mrList := &resourcesv1alpha1.ManagedResourceList{}
 			Expect(runtimeClient.List(ctx, mrList)).To(Succeed())
diff --git a/pkg/operator/controller/garden/garden/components.go b/pkg/operator/controller/garden/garden/components.go
index b0145d77229..e8ab9cda7f4 100644
--- a/pkg/operator/controller/garden/garden/components.go
+++ b/pkg/operator/controller/garden/garden/components.go
@@ -85,6 +85,7 @@ import (
 	secretsutils "github.com/gardener/gardener/pkg/utils/secrets"
 	secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager"
 	"github.com/gardener/gardener/pkg/utils/timewindow"
+	versionutils "github.com/gardener/gardener/pkg/utils/version"
 )
 
 type components struct {
@@ -331,7 +332,7 @@ func (r *Reconciler) instantiateComponents(
 	return c, nil
 }
 
-func (r *Reconciler) enableSeedAuthorizer(ctx context.Context) (bool, error) {
+func (r *Reconciler) enableSeedAuthorizer(ctx context.Context, version *semver.Version) (bool, error) {
 	// The reconcile flow deploys the kube-apiserver of the virtual garden cluster before the gardener-apiserver and
 	// gardener-admission-controller (it has to be this way, otherwise the Gardener components cannot start). However,
 	// GAC serves an authorization webhook for the SeedAuthorizer feature. We can only configure kube-apiserver to
@@ -342,6 +343,10 @@ func (r *Reconciler) enableSeedAuthorizer(ctx context.Context) (bool, error) {
 	// TODO(rfranzke): Consider removing this two-step deployment once we only support Kubernetes 1.32+ (in this
 	//  version, the structured authorization feature has been promoted to GA). We already use structured authz for
 	//  1.30+ clusters. See https://github.com/gardener/gardener/pull/10682#discussion_r1816324389 for more information.
+	if versionutils.ConstraintK8sGreaterEqual132.Check(version) {
+		return true, nil
+	}
+
 	if err := r.RuntimeClientSet.Client().Get(ctx, client.ObjectKey{Name: gardenerapiserver.DeploymentName, Namespace: r.GardenNamespace}, &appsv1.Deployment{}); err != nil {
 		if !apierrors.IsNotFound(err) {
 			return false, err
diff --git a/pkg/operator/controller/garden/garden/reconciler_reconcile.go b/pkg/operator/controller/garden/garden/reconciler_reconcile.go
index 0a174499449..d884efd4501 100644
--- a/pkg/operator/controller/garden/garden/reconciler_reconcile.go
+++ b/pkg/operator/controller/garden/garden/reconciler_reconcile.go
@@ -125,7 +125,7 @@ func (r *Reconciler) reconcile(
 	}
 
 	log.Info("Instantiating component deployers")
-	enableSeedAuthorizer, err := r.enableSeedAuthorizer(ctx)
+	enableSeedAuthorizer, err := r.enableSeedAuthorizer(ctx, targetVersion)
 	if err != nil {
 		return reconcile.Result{}, err
 	}
@@ -198,17 +198,11 @@ func (r *Reconciler) reconcile(
 		})
 		deployEtcdDruid = g.Add(flow.Task{
 			Name: "Deploying ETCD Druid",
-			Fn:   component.OpWait(c.etcdDruid).Deploy,
+			Fn:   c.etcdDruid.Deploy,
 		})
 		deployIstio = g.Add(flow.Task{
 			Name: "Deploying Istio",
-			Fn:   component.OpWait(c.istio).Deploy,
-		})
-		_ = g.Add(flow.Task{
-			Name:         "Reconciling DNSRecords for virtual garden cluster and ingress controller",
-			Fn:           func(ctx context.Context) error { return r.reconcileDNSRecords(ctx, log, garden) },
-			SkipIf:       garden.Spec.DNS == nil,
-			Dependencies: flow.NewTaskIDs(deployIstio),
+			Fn:   c.istio.Deploy,
 		})
 		syncPointSystemComponents = flow.NewTaskIDs(
 			generateGenericTokenKubeconfig,
@@ -219,6 +213,23 @@ func (r *Reconciler) reconcile(
 			deployNginxIngressController,
 		)
 
+		waitUntilEtcdDruidReady = g.Add(flow.Task{
+			Name:         "Waiting for ETCD Druid to be ready",
+			Fn:           c.istio.Wait,
+			Dependencies: flow.NewTaskIDs(deployEtcdDruid),
+		})
+		_ = g.Add(flow.Task{
+			Name:         "Waiting for Istio to be ready",
+			Fn:           c.istio.Wait,
+			Dependencies: flow.NewTaskIDs(deployIstio),
+		})
+		_ = g.Add(flow.Task{
+			Name:         "Reconciling DNSRecords for virtual garden cluster and ingress controller",
+			Fn:           func(ctx context.Context) error { return r.reconcileDNSRecords(ctx, log, garden) },
+			SkipIf:       garden.Spec.DNS == nil,
+			Dependencies: flow.NewTaskIDs(syncPointSystemComponents),
+		})
+
 		backupBucket = etcdMainBackupBucket(garden)
 
 		deployEtcdBackupBucket = g.Add(flow.Task{
@@ -245,7 +256,7 @@ func (r *Reconciler) reconcile(
 		deployEtcds = g.Add(flow.Task{
 			Name:         "Deploying main and events ETCDs of virtual garden",
 			Fn:           r.deployEtcdsFunc(garden, c.etcdMain, c.etcdEvents, backupBucket),
-			Dependencies: flow.NewTaskIDs(syncPointSystemComponents, deployEtcdBackupBucket),
+			Dependencies: flow.NewTaskIDs(waitUntilEtcdDruidReady, deployEtcdBackupBucket),
 		})
 		waitUntilEtcdsReady = g.Add(flow.Task{
 			Name:         "Waiting until main and event ETCDs report readiness",
@@ -608,10 +619,6 @@ func (r *Reconciler) runRuntimeSetupFlow(ctx context.Context, log logr.Logger, g
 	var (
 		g = flow.NewGraph("Garden runtime setup")
 
-		_ = g.Add(flow.Task{
-			Name: "Deploying runtime system resources",
-			Fn:   c.runtimeSystem.Deploy,
-		})
 		_ = g.Add(flow.Task{
 			Name: "Deploying custom resource definitions for fluent-operator",
 			Fn:   component.OpWait(c.fluentCRD).Deploy,
@@ -638,16 +645,26 @@ func (r *Reconciler) runRuntimeSetupFlow(ctx context.Context, log logr.Logger, g
 			Fn:   component.OpWait(c.istioCRD).Deploy,
 		})
 		deployGardenerResourceManager = g.Add(flow.Task{
-			Name:         "Deploying and waiting for gardener-resource-manager to be healthy",
-			Fn:           component.OpWait(c.gardenerResourceManager).Deploy,
+			Name:         "Deploying gardener-resource-manager",
+			Fn:           c.gardenerResourceManager.Deploy,
 			Dependencies: flow.NewTaskIDs(deployEtcdCRD, deployVPACRD, deployIstioCRD),
 		})
+		_ = g.Add(flow.Task{
+			Name:         "Waiting for gardener-resource-manager to be healthy",
+			Fn:           c.gardenerResourceManager.Wait,
+			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager),
+		})
+		deploySystemResources = g.Add(flow.Task{
+			Name:         "Deploying runtime system resources",
+			Fn:           c.runtimeSystem.Deploy,
+			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager),
+		})
 		_ = g.Add(flow.Task{
 			Name: "Waiting for Extensions to get ready",
 			Fn: func(ctx context.Context) error {
 				return r.waitUntilRequiredExtensionsReady(ctx, log, garden)
 			},
-			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager),
+			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager, deploySystemResources),
 		})
 	)
 
diff --git a/pkg/provider-local/webhook/rolloutspeedup/add.go b/pkg/provider-local/webhook/rolloutspeedup/add.go
new file mode 100644
index 00000000000..90645d699d1
--- /dev/null
+++ b/pkg/provider-local/webhook/rolloutspeedup/add.go
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package rolloutspeedup
+
+import (
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
+	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
+	"github.com/gardener/gardener/pkg/provider-local/local"
+)
+
+// WebhookName is the name of the rollout-speedup webhook. It modifies the API server deployments (kube-apiserver and
+// garden-apiserver) to make sure that a rolling update happens faster.
+const WebhookName = "rollout-speedup"
+
+var (
+	logger = log.Log.WithName("local-rollout-speedup-webhook")
+
+	// DefaultAddOptions are the default AddOptions for AddToManager.
+	DefaultAddOptions = AddOptions{}
+)
+
+// AddOptions are options to apply when adding the local exposure webhook to the manager.
+type AddOptions struct{}
+
+// AddToManagerWithOptions creates a webhook with the given options and adds it to the manager.
+func AddToManagerWithOptions(mgr manager.Manager, _ AddOptions) (*extensionswebhook.Webhook, error) {
+	logger.Info("Adding webhook to manager")
+
+	var (
+		name     = "rollout-speedup"
+		provider = local.Type
+		types    = []extensionswebhook.Type{
+			{Obj: &appsv1.Deployment{}},
+		}
+	)
+
+	logger = logger.WithValues("provider", provider)
+
+	handler, err := extensionswebhook.NewBuilder(mgr, logger).WithMutator(&mutator{client: mgr.GetClient()}, types...).Build()
+	if err != nil {
+		return nil, err
+	}
+
+	logger.Info("Creating webhook", "name", name)
+
+	return &extensionswebhook.Webhook{
+		Name:              name,
+		Provider:          provider,
+		Types:             types,
+		Target:            extensionswebhook.TargetSeed,
+		Path:              name,
+		Webhook:           &admission.Webhook{Handler: handler, RecoverPanic: ptr.To(true)},
+		NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{corev1.LabelMetadataName: v1beta1constants.GardenNamespace}},
+		ObjectSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{
+			{Key: v1beta1constants.LabelRole, Operator: metav1.LabelSelectorOpIn, Values: []string{v1beta1constants.LabelAPIServer}},
+		}},
+	}, nil
+}
+
+// AddToManager creates a webhook with the default options and adds it to the manager.
+func AddToManager(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
+	return AddToManagerWithOptions(mgr, DefaultAddOptions)
+}
diff --git a/pkg/provider-local/webhook/rolloutspeedup/mutator.go b/pkg/provider-local/webhook/rolloutspeedup/mutator.go
new file mode 100644
index 00000000000..46f0f9cd465
--- /dev/null
+++ b/pkg/provider-local/webhook/rolloutspeedup/mutator.go
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package rolloutspeedup
+
+import (
+	"context"
+	"fmt"
+
+	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type mutator struct {
+	client client.Client
+}
+
+func (m *mutator) Mutate(_ context.Context, newObj, _ client.Object) error {
+	if newObj.GetDeletionTimestamp() != nil {
+		return nil
+	}
+
+	deployment, ok := newObj.(*appsv1.Deployment)
+	if !ok {
+		return fmt.Errorf("expected deployment, got %T", newObj)
+	}
+
+	// 3 and 5 are the magic numbers 🪄
+	if deployment.Spec.MinReadySeconds > 5 {
+		deployment.Spec.MinReadySeconds = 5
+	}
+	deployment.Spec.Template.Spec.TerminationGracePeriodSeconds = ptr.To[int64](3)
+
+	for i, container := range deployment.Spec.Template.Spec.Containers {
+		if container.ReadinessProbe != nil {
+			deployment.Spec.Template.Spec.Containers[i].ReadinessProbe.InitialDelaySeconds = 3
+			deployment.Spec.Template.Spec.Containers[i].ReadinessProbe.PeriodSeconds = 5
+		}
+	}
+
+	return nil
+}
diff --git a/skaffold-gardenadm.yaml b/skaffold-gardenadm.yaml
index b4b395e38da..c3b0df3b0fd 100644
--- a/skaffold-gardenadm.yaml
+++ b/skaffold-gardenadm.yaml
@@ -739,6 +739,7 @@ build:
             - pkg/provider-local/webhook/networkpolicy
             - pkg/provider-local/webhook/node
             - pkg/provider-local/webhook/prometheus
+            - pkg/provider-local/webhook/rolloutspeedup
             - pkg/provider-local/webhook/shoot
             - pkg/resourcemanager/controller/garbagecollector/references
             - pkg/utils
diff --git a/skaffold-operator-garden.yaml b/skaffold-operator-garden.yaml
index 0d500a39966..910de1ea683 100644
--- a/skaffold-operator-garden.yaml
+++ b/skaffold-operator-garden.yaml
@@ -13,7 +13,17 @@ deploy:
             command:
               - bash
               - -ec
-              - TIMEOUT=900 hack/usage/wait-for.sh garden local VirtualGardenAPIServerAvailable RuntimeComponentsHealthy VirtualComponentsHealthy
+              # We deliberately only wait for the last operation to be 'Reconcile Succeeded' in order to be able to
+              # faster deploy the gardenlet.
+              - TIMEOUT=900 hack/usage/wait-for.sh garden local
+        - host:
+            command:
+              - bash
+              - -ec
+              # Check that the admission component of provider-local extension is healthy - it may run webhooks for the
+              # resources that we are about to create in below 'garden-config' Skaffold config. This will fail in case
+              # the webhook server is down or not yet available.
+              - TIMEOUT=60 SKIP_LAST_OPERATION_CHECK=true ./hack/usage/wait-for.sh extop provider-local AdmissionHealthy
         - host:
             command:
               - bash
diff --git a/skaffold-operator.yaml b/skaffold-operator.yaml
index 6a0ac0f0b06..fa77eb7da6b 100644
--- a/skaffold-operator.yaml
+++ b/skaffold-operator.yaml
@@ -1139,6 +1139,7 @@ build:
             - pkg/provider-local/webhook/networkpolicy
             - pkg/provider-local/webhook/node
             - pkg/provider-local/webhook/prometheus
+            - pkg/provider-local/webhook/rolloutspeedup
             - pkg/provider-local/webhook/shoot
             - pkg/resourcemanager/controller/garbagecollector/references
             - pkg/utils
diff --git a/skaffold.yaml b/skaffold.yaml
index 6564303d1cd..108b3835340 100644
--- a/skaffold.yaml
+++ b/skaffold.yaml
@@ -781,6 +781,7 @@ build:
             - pkg/provider-local/webhook/networkpolicy
             - pkg/provider-local/webhook/node
             - pkg/provider-local/webhook/prometheus
+            - pkg/provider-local/webhook/rolloutspeedup
             - pkg/provider-local/webhook/shoot
             - pkg/resourcemanager/controller/garbagecollector/references
             - pkg/utils
diff --git a/test/integration/operator/extension/extension/extension_test.go b/test/integration/operator/extension/extension/extension_test.go
index 9bed705ce5b..e37d114cee5 100644
--- a/test/integration/operator/extension/extension/extension_test.go
+++ b/test/integration/operator/extension/extension/extension_test.go
@@ -10,6 +10,7 @@ import (
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	schedulingv1 "k8s.io/api/scheduling/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -193,6 +194,12 @@ var _ = Describe("Extension controller tests", func() {
 				Namespace: testNamespace.Name,
 			},
 		}
+
+		priorityClass := &schedulingv1.PriorityClass{ObjectMeta: metav1.ObjectMeta{Name: "gardener-garden-system-200"}}
+		Expect(testClient.Create(ctx, priorityClass)).To(Succeed())
+		DeferCleanup(func() {
+			Expect(testClient.Delete(ctx, priorityClass)).To(Succeed())
+		})
 	})
 
 	It("should reconcile all required cluster resources in the virtual and runtime garden cluster", func() {


Reviews:
user: login: ScheererJ
body: Thanks a lot for this impressive performance improvement 🏃 .
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:10:56Z
user: login: rfranzke
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:11:50Z
user: login: ScheererJ
body: /lgtm
/approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T14:58:30Z
user: login: timuthy
body: Nice improvements, thanks for tackling them 🚀 Please see the ~two~ comment I wasn't quite sure about.

Since the PR was lgtm-ed and approved at the same time, let's hold for a while until the questions are answered.

/hold
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T06:34:43Z
user: login: timuthy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T07:17:24Z
user: login: timuthy
body: /unhold
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T07:17:46Z

Review comments:
diff_hunk: @@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package rolloutspeedup
+
+import (
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
+	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
+	"github.com/gardener/gardener/pkg/provider-local/local"
+)
+
+// WebhookName is the name of the DNS config webhook.
path: pkg/provider-local/webhook/rolloutspeedup/add.go
user: login: ScheererJ
body: DNS is always the reason for any problems ;-) , but in this case it seems like a copy&paste bug.
Please adapt the comment.
Maybe we should also write somewhere a short summary what this does and why. I guess we want to roll `kube-apiserver` faster and ensure that the probes return results faster, but that is reading between the lines...
created_at: 2025-05-21T12:54:03Z
author_association: MEMBER
diff_hunk: @@ -638,16 +634,26 @@ func (r *Reconciler) runRuntimeSetupFlow(ctx context.Context, log logr.Logger, g
 			Fn:   component.OpWait(c.istioCRD).Deploy,
 		})
 		deployGardenerResourceManager = g.Add(flow.Task{
-			Name:         "Deploying and waiting for gardener-resource-manager to be healthy",
+			Name:         "Deploying gardener-resource-manager",
 			Fn:           component.OpWait(c.gardenerResourceManager).Deploy,
 			Dependencies: flow.NewTaskIDs(deployEtcdCRD, deployVPACRD, deployIstioCRD),
 		})
+		_ = g.Add(flow.Task{
+			Name:         "Waiting for gardener-resource-manager to be healthy",
+			Fn:           c.gardenerResourceManager.Wait,
+			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager),
+		})
path: pkg/operator/controller/garden/garden/reconciler_reconcile.go
user: login: ScheererJ
body: I do not understand why we split deployment of `gardener-resource-manager` and check of health, but still wait for it in the first step. It feels like `component.OpWait(...)` should have been removed in the first step. Otherwise the split seems useless.
created_at: 2025-05-21T13:07:10Z
author_association: MEMBER
diff_hunk: @@ -638,16 +634,26 @@ func (r *Reconciler) runRuntimeSetupFlow(ctx context.Context, log logr.Logger, g
 			Fn:   component.OpWait(c.istioCRD).Deploy,
 		})
 		deployGardenerResourceManager = g.Add(flow.Task{
-			Name:         "Deploying and waiting for gardener-resource-manager to be healthy",
+			Name:         "Deploying gardener-resource-manager",
 			Fn:           component.OpWait(c.gardenerResourceManager).Deploy,
 			Dependencies: flow.NewTaskIDs(deployEtcdCRD, deployVPACRD, deployIstioCRD),
 		})
+		_ = g.Add(flow.Task{
+			Name:         "Waiting for gardener-resource-manager to be healthy",
+			Fn:           c.gardenerResourceManager.Wait,
+			Dependencies: flow.NewTaskIDs(deployGardenerResourceManager),
+		})
path: pkg/operator/controller/garden/garden/reconciler_reconcile.go
user: login: rfranzke
body: Good catch, thank you!
created_at: 2025-05-21T13:11:50Z
author_association: MEMBER
diff_hunk: @@ -61,7 +66,13 @@ func (r *Reconciler) delete(
 		Log: log,
 	}); err != nil {
 		conditions.installed = v1beta1helper.UpdatedConditionWithClock(r.Clock, conditions.installed, gardencorev1beta1.ConditionFalse, ReasonDeleteFailed, err.Error())
-		return reconcile.Result{}, errors.Join(err, r.updateExtensionStatus(ctx, log, extension, conditions))
+		if updateErr := r.updateExtensionStatus(ctx, log, extension, conditions); updateErr != nil {
+			return reconcile.Result{}, errors.Join(err, fmt.Errorf("failed to update extension status: %w", updateErr))
+		}
+		if !reflect.DeepEqual(reconcileResult, reconcile.Result{}) {
+			return reconcileResult, nil
+		}
+		return reconcile.Result{}, err
path: pkg/operator/controller/extension/extension/reconciler_delete.go
user: login: timuthy
body: ~Can you explain this handling, i.e. why does the code ignore errors from all flow steps except "Handling Extension in runtime cluster"?~

Understood after having a second look - it's only prioritizing the `reconcileResult`.
created_at: 2025-05-22T06:23:04Z
author_association: MEMBER
diff_hunk: @@ -46,27 +51,37 @@ type deployer struct {
 }
 
 // Reconcile creates or updates the extension deployment in the garden runtime cluster.
-func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) error {
+func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) (reconcile.Result, error) {
 	if !extensionDeploymentSpecified(extension) {
 		return d.Delete(ctx, log, extension)
 	}
 
+	if err := d.runtimeClientSet.Client().Get(ctx, client.ObjectKey{Name: priorityClassName}, &schedulingv1.PriorityClass{}); err != nil {
path: pkg/operator/controller/extension/extension/runtime/runtime.go
user: login: timuthy
body: I assume this can also be true for essential CRDs like `ManagedResource`? Should it be checked here, as well?
created_at: 2025-05-22T06:25:53Z
author_association: MEMBER
diff_hunk: @@ -46,27 +51,37 @@ type deployer struct {
 }
 
 // Reconcile creates or updates the extension deployment in the garden runtime cluster.
-func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) error {
+func (d *deployer) Reconcile(ctx context.Context, log logr.Logger, extension *operatorv1alpha1.Extension) (reconcile.Result, error) {
 	if !extensionDeploymentSpecified(extension) {
 		return d.Delete(ctx, log, extension)
 	}
 
+	if err := d.runtimeClientSet.Client().Get(ctx, client.ObjectKey{Name: priorityClassName}, &schedulingv1.PriorityClass{}); err != nil {
path: pkg/operator/controller/extension/extension/runtime/runtime.go
user: login: timuthy
body: As discussed offline, waiting for the `ManagedResource` CRD happens implicitly by waiting for the `PriorityClass`.
created_at: 2025-05-22T07:17:24Z
author_association: MEMBER

PR comments:
user: login: ScheererJ
created_at: 2025-05-21T12:46:02Z
body: /assign
author_association: MEMBER
user: login: gardener-prow[bot]
created_at: 2025-05-21T14:58:34Z
body: LGTM label has been added.  <details>Git tree hash: a98cf8a84c3c3da5ae7df7260bbdd0d0ccbb39f3</details>
author_association: CONTRIBUTOR
user: login: gardener-prow[bot]
created_at: 2025-05-21T14:58:38Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/gardener/gardener/pull/12156#pullrequestreview-2858080366" title="Approved">ScheererJ</a>*

The full list of commands accepted by this bot can be found [here](https://prow.gardener.cloud/command-help?repo=gardener%2Fgardener).

The pull request process is described [here](https://gardener.cloud/docs/contribute/#pull-request-checklist)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/gardener/gardener/blob/master/OWNERS)~~ [ScheererJ]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: oliver-goetz
created_at: 2025-05-22T08:53:13Z
body: wicked fast ⚡ 
/lgtm
author_association: MEMBER