Repository: kubernetes-sigs/cluster-api
Pull request #8880 (2023-06-19 15:06:07)

🐛 Add kind mapper
**What this PR does / why we need it**:
CAPD doesn't have direct dependencies from Kind, however, it is using kindest/node images.

As a consequence CAPD must stay in sync with Kind with regards to how kindest/node images are run when creating a new DockerMachine.

kindest/node images are created using a specific Kind version, and thus it is required to keep into account the mapping between the Kubernetes version for a kindest/node and the Kind version that created that image, so we can run it properly.

This PR implements this mapping and use it to pick the right image to use and to start this image as expected.

**Which issue(s) this PR fixes**:
Fixes #


k8s-ci-robot: @fabriziopandini: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-informing-main | aa6e0b437769702b97aa8d8cb45c97a66930195b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-informing-main/1670810205123776512) | false | `/test pull-cluster-api-e2e-informing-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
killianmuldoon: Part of https://github.com/kubernetes-sigs/cluster-api/issues/8788
k8s-ci-robot: @fabriziopandini: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-informing-main | aa6e0b437769702b97aa8d8cb45c97a66930195b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-informing-main/1670810205123776512) | false | `/test pull-cluster-api-e2e-informing-main`
pull-cluster-api-e2e-main | fb82cdb6942a1595c74f129182622f41063363c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-main/1670839603113758720) | true | `/test pull-cluster-api-e2e-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
fabriziopandini: /retest
k8s-ci-robot: @fabriziopandini: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | fb82cdb6942a1595c74f129182622f41063363c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-main/1670839603113758720) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-e2e-informing-main | fb82cdb6942a1595c74f129182622f41063363c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-informing-main/1670839603164090368) | false | `/test pull-cluster-api-e2e-informing-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
fabriziopandini: /retest
k8s-ci-robot: @fabriziopandini: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-informing-main | fb82cdb6942a1595c74f129182622f41063363c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-informing-main/1671125787878625280) | false | `/test pull-cluster-api-e2e-informing-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @fabriziopandini: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-informing-main | fb82cdb6942a1595c74f129182622f41063363c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-informing-main/1671125787878625280) | false | `/test pull-cluster-api-e2e-informing-main`
pull-cluster-api-test-main | 29ae246e66ef1d42715a8e9bc401d94c4fc13bac | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-test-main/1671270498111066112) | true | `/test pull-cluster-api-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sbueringer: /test pull-cluster-api-e2e-full-main
k8s-ci-robot: @fabriziopandini: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-full-main | 9f5e993ceefad54b2dd75bd516c1f1bdf05f62e6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-full-main/1671380973096275968) | false | `/test pull-cluster-api-e2e-full-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @fabriziopandini: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-full-main | 9f5e993ceefad54b2dd75bd516c1f1bdf05f62e6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-full-main/1671380973096275968) | false | `/test pull-cluster-api-e2e-full-main`
pull-cluster-api-test-main | 1ba69ac79dc609b42051cf3c6f72ed5f5fde62c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-test-main/1671451499076521984) | true | `/test pull-cluster-api-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
fabriziopandini: /retest
/test pull-cluster-api-e2e-full-main
sbueringer: /lgtm 
on the changes. Let's see what CI thinks about it
k8s-ci-robot: LGTM label has been added.  <details>Git tree hash: 4f7268ebdbeae84082df83711991d309aee15008</details>
k8s-ci-robot: @fabriziopandini: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-full-main | 1ba69ac79dc609b42051cf3c6f72ed5f5fde62c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/8880/pull-cluster-api-e2e-full-main/1671453499746947072) | false | `/test pull-cluster-api-e2e-full-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=8880). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: New changes are detected. LGTM label has been removed.
fabriziopandini: /test pull-cluster-api-e2e-full-main
sbueringer: /lgtm
(+/- squash)

/assign @killianmuldoon 
k8s-ci-robot: LGTM label has been added.  <details>Git tree hash: 2d119ca115b23eef95cd6f1292d6957015c453aa</details>
killianmuldoon: /hold 
(for squash)

/lgtm
/approve
killianmuldoon: /cherry-pick release-1.4
k8s-infra-cherrypick-robot: @killianmuldoon: once the present PR merges, I will cherry-pick it on top of release-1.4 in a new PR and assign it to you.

<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api/pull/8880#issuecomment-1602346239):

>/cherry-pick release-1.4


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
killianmuldoon: /cherry-pick release-1.3
k8s-infra-cherrypick-robot: @killianmuldoon: once the present PR merges, I will cherry-pick it on top of release-1.3 in a new PR and assign it to you.

<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api/pull/8880#issuecomment-1602346466):

>/cherry-pick release-1.3


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api/pull/8880#issuecomment-1602346067" title="Approved">killianmuldoon</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/kubernetes-sigs/cluster-api/blob/main/OWNERS)~~ [killianmuldoon]

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":[]} -->

Patch:
diff --git a/.gitignore b/.gitignore
index 9258cfddc..95c74ae21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@ hack/tools/bin
 # E2E test templates
 test/e2e/data/infrastructure-docker/**/cluster-template*.yaml
 test/e2e/data/infrastructure-inmemory/**/cluster-template*.yaml
-
 # Output of Makefile targets using sed on MacOS systems
 *.yaml-e
 
diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
index a5cf5a130..be97c4d71 100644
--- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
+++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
@@ -158,7 +158,7 @@ func (r *KubeadmConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	}
 
 	// Look up the owner of this kubeadm config if there is one
-	configOwner, err := bsutil.GetConfigOwnerFromCache(ctx, r.Client, config)
+	configOwner, err := bsutil.GetConfigOwner(ctx, r.Client, config)
 	if apierrors.IsNotFound(err) {
 		// Could not find the owner yet, this is not an error and will rereconcile when the owner gets set.
 		return ctrl.Result{}, nil
diff --git a/bootstrap/util/configowner.go b/bootstrap/util/configowner.go
index 197cfbba2..d10fadd1b 100644
--- a/bootstrap/util/configowner.go
+++ b/bootstrap/util/configowner.go
@@ -24,9 +24,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -125,20 +123,8 @@ func (co ConfigOwner) KubernetesVersion() string {
 	return version
 }
 
-// GetConfigOwner returns the Unstructured object owning the current resource
-// using the uncached unstructured client. For performance-sensitive uses,
-// consider GetConfigOwnerCached.
+// GetConfigOwner returns the Unstructured object owning the current resource.
 func GetConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
-	return getConfigOwner(ctx, c, obj, GetOwnerByRef)
-}
-
-// GetConfigOwnerFromCache returns the Unstructured object owning the current
-// resource. The implementation ensures a typed client is used, so the objects are read from the cache.
-func GetConfigOwnerFromCache(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
-	return getConfigOwner(ctx, c, obj, GetOwnerByRefFromCache)
-}
-
-func getConfigOwner(ctx context.Context, c client.Client, obj metav1.Object, getFn func(context.Context, client.Client, *corev1.ObjectReference) (*ConfigOwner, error)) (*ConfigOwner, error) {
 	allowedGKs := []schema.GroupKind{
 		{
 			Group: clusterv1.GroupVersion.Group,
@@ -162,7 +148,7 @@ func getConfigOwner(ctx context.Context, c client.Client, obj metav1.Object, get
 
 		for _, gk := range allowedGKs {
 			if refGVK.Group == gk.Group && refGVK.Kind == gk.Kind {
-				return getFn(ctx, c, &corev1.ObjectReference{
+				return GetOwnerByRef(ctx, c, &corev1.ObjectReference{
 					APIVersion: ref.APIVersion,
 					Kind:       ref.Kind,
 					Name:       ref.Name,
@@ -182,33 +168,3 @@ func GetOwnerByRef(ctx context.Context, c client.Client, ref *corev1.ObjectRefer
 	}
 	return &ConfigOwner{obj}, nil
 }
-
-// GetOwnerByRefFromCache finds and returns the owner by looking at the object
-// reference. The implementation ensures a typed client is used, so the objects are read from the cache.
-func GetOwnerByRefFromCache(ctx context.Context, c client.Client, ref *corev1.ObjectReference) (*ConfigOwner, error) {
-	obj, err := c.Scheme().New(ref.GroupVersionKind())
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to construct object of type %s", ref.GroupVersionKind())
-	}
-	metaObj, ok := obj.(client.Object)
-	if !ok {
-		return nil, errors.Errorf("expected owner reference to refer to a client.Object, is actually %T", obj)
-	}
-	key := types.NamespacedName{
-		Namespace: ref.Namespace,
-		Name:      ref.Name,
-	}
-	err = c.Get(ctx, key, metaObj)
-	if err != nil {
-		return nil, err
-	}
-
-	content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(metaObj)
-	if err != nil {
-		return nil, err
-	}
-	u := unstructured.Unstructured{}
-	u.SetUnstructuredContent(content)
-
-	return &ConfigOwner{&u}, nil
-}
diff --git a/cmd/clusterctl/client/config/cert_manager_client.go b/cmd/clusterctl/client/config/cert_manager_client.go
index ea3858c27..429d23ae3 100644
--- a/cmd/clusterctl/client/config/cert_manager_client.go
+++ b/cmd/clusterctl/client/config/cert_manager_client.go
@@ -29,7 +29,7 @@ const (
 	CertManagerConfigKey = "cert-manager"
 
 	// CertManagerDefaultVersion defines the default cert-manager version to be used by clusterctl.
-	CertManagerDefaultVersion = "v1.12.2"
+	CertManagerDefaultVersion = "v1.12.1"
 
 	// CertManagerDefaultURL defines the default cert-manager repository url to be used by clusterctl.
 	// NOTE: At runtime CertManagerDefaultVersion may be replaced with the
diff --git a/controlplane/kubeadm/internal/cluster.go b/controlplane/kubeadm/internal/cluster.go
index 521e2df2a..60b71604c 100644
--- a/controlplane/kubeadm/internal/cluster.go
+++ b/controlplane/kubeadm/internal/cluster.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/client-go/rest"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -103,11 +102,10 @@ func (m *Management) GetMachinePoolsForCluster(ctx context.Context, cluster *clu
 func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error) {
 	// TODO(chuckha): Inject this dependency.
 	// TODO(chuckha): memoize this function. The workload client only exists as long as a reconciliation loop.
-	restConfig, err := m.Tracker.GetRESTConfig(ctx, clusterKey)
+	restConfig, err := remote.RESTConfig(ctx, KubeadmControlPlaneControllerName, m.Client, clusterKey)
 	if err != nil {
 		return nil, err
 	}
-	restConfig = rest.CopyConfig(restConfig)
 	restConfig.Timeout = 30 * time.Second
 
 	if m.Tracker == nil {
diff --git a/docs/book/src/clusterctl/commands/init.md b/docs/book/src/clusterctl/commands/init.md
index 73092f46c..032a8771b 100644
--- a/docs/book/src/clusterctl/commands/init.md
+++ b/docs/book/src/clusterctl/commands/init.md
@@ -190,7 +190,7 @@ If this happens, there are no guarantees about the proper functioning of `cluste
 Cluster API providers require a cert-manager version supporting the `cert-manager.io/v1` API to be installed in the cluster.
 
 While doing init, clusterctl checks if there is a version of cert-manager already installed. If not, clusterctl will
-install a default version (currently cert-manager v1.12.2). See [clusterctl configuration](../configuration.md) for
+install a default version (currently cert-manager v1.12.1). See [clusterctl configuration](../configuration.md) for
 available options to customize this operation.
 
 <aside class="note warning">
diff --git a/docs/book/src/developer/guide.md b/docs/book/src/developer/guide.md
index 3c055812b..a37527279 100644
--- a/docs/book/src/developer/guide.md
+++ b/docs/book/src/developer/guide.md
@@ -81,7 +81,7 @@ The generated binary can be found at ./hack/tools/bin/envsubst
 You'll need to deploy [cert-manager] components on your [management cluster][mcluster], using `kubectl`
 
 ```bash
-kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.2/cert-manager.yaml
+kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.1/cert-manager.yaml
 ```
 
 Ensure the cert-manager webhook service is ready before creating the Cluster API components.
diff --git a/docs/book/src/developer/providers/migrations/v1.4-to-v1.5.md b/docs/book/src/developer/providers/migrations/v1.4-to-v1.5.md
index 38764f8cf..4ba7d2f62 100644
--- a/docs/book/src/developer/providers/migrations/v1.4-to-v1.5.md
+++ b/docs/book/src/developer/providers/migrations/v1.4-to-v1.5.md
@@ -36,7 +36,6 @@ maintainers of providers and consumers of our Go API.
 - clusterctl move is adding the new annotation `clusterctl.cluster.x-k8s.io/delete-for-move` before object deletion.
 - Providers running CAPI release-0.3 clusterctl upgrade tests should set `WorkloadKubernetesVersion` field to the maximum workload cluster kubernetes version supported by the old providers in `ClusterctlUpgradeSpecInput`. For more information, please see: https://github.com/kubernetes-sigs/cluster-api/pull/8518#issuecomment-1508064859 
 - Introduced function `CollectInfrastructureLogs` at the `ClusterLogCollector` interface in `test/framework/cluster_proxy.go` to allow collecting infrastructure related logs during tests.
-- A `GetConfigOwnerFromCache` function has been added to the `sigs.k8s.io./cluster-api/bootstrap/util` package. It is equivalent to `GetConfigOwner` except that it uses the cached typed client instead of the uncached unstructured client, so `GetConfigOwnerFromCache` is expected to be more performant.
 
 ### Suggested changes for providers
 
diff --git a/go.mod b/go.mod
index 45579a397..99015b2b3 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
 	github.com/google/go-github/v48 v48.2.0
 	github.com/google/gofuzz v1.2.0
 	github.com/mattn/go-runewidth v0.0.14 // indirect
-	github.com/onsi/ginkgo/v2 v2.11.0
+	github.com/onsi/ginkgo/v2 v2.10.0
 	github.com/onsi/gomega v1.27.8
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.7.0
@@ -30,8 +30,8 @@ require (
 	github.com/valyala/fastjson v1.6.4
 	go.etcd.io/etcd/api/v3 v3.5.9
 	go.etcd.io/etcd/client/v3 v3.5.9
-	golang.org/x/net v0.11.0 // indirect
-	golang.org/x/oauth2 v0.9.0
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/oauth2 v0.8.0
 	google.golang.org/grpc v1.55.0
 	k8s.io/api v0.27.2
 	k8s.io/apiextensions-apiserver v0.27.2
@@ -103,10 +103,10 @@ require (
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
-	github.com/prometheus/client_golang v1.16.0
+	github.com/prometheus/client_golang v1.15.1
 	github.com/prometheus/client_model v0.4.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.10.1 // indirect
+	github.com/prometheus/procfs v0.9.0 // indirect
 	github.com/rivo/uniseg v0.4.2 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/spf13/afero v1.9.5 // indirect
@@ -122,9 +122,9 @@ require (
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.24.0 // indirect
 	go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
-	golang.org/x/crypto v0.10.0 // indirect
-	golang.org/x/sys v0.9.0 // indirect
-	golang.org/x/term v0.9.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/term v0.8.0 // indirect
 	golang.org/x/text v0.10.0
 	golang.org/x/time v0.3.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.3.0
diff --git a/go.sum b/go.sum
index a08904f42..b6ec0f094 100644
--- a/go.sum
+++ b/go.sum
@@ -394,8 +394,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
-github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
+github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
+github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
 github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
 github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -418,8 +418,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -431,8 +431,8 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
 github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
@@ -563,8 +563,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -642,8 +642,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
-golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -653,8 +653,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
-golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -717,13 +717,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
-golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/hack/observability/grafana/dashboards/cluster-api.json b/hack/observability/grafana/dashboards/cluster-api.json
index 94f1d5cec..6918a3085 100644
--- a/hack/observability/grafana/dashboards/cluster-api.json
+++ b/hack/observability/grafana/dashboards/cluster-api.json
@@ -18,7 +18,7 @@
   "editable": true,
   "fiscalYearStartMonth": 0,
   "graphTooltip": 0,
-  "id": 1,
+  "id": 2,
   "links": [],
   "liveNow": false,
   "panels": [
@@ -557,7 +557,7 @@
       "type": "stat"
     },
     {
-      "collapsed": true,
+      "collapsed": false,
       "gridPos": {
         "h": 1,
         "w": 24,
@@ -565,1186 +565,498 @@
         "y": 5
       },
       "id": 42,
-      "panels": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 6
-          },
-          "id": 49,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
+      "panels": [],
+      "title": "Performance",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 1,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
             }
           },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
               },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 6
+      },
+      "id": 43,
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "min",
+            "max",
+            "lastNotNull"
           ],
-          "title": "Cluster time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
+          "displayMode": "table",
+          "placement": "right",
+          "showLegend": true
         },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "asc"
+        }
+      },
+      "targets": [
         {
           "datasource": {
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 6
-          },
-          "id": 43,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
+          "editorMode": "code",
+          "expr": "avg((capi_cluster_status_condition_last_transition_time{status=\"True\"}>= time() - 60*5) - on(name) group_left () capi_cluster_created) by (type)",
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Cluster time until condition true since creationTimestamp",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 1,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
             }
           },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
               },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_cluster_created) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 14
+      },
+      "id": 45,
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "min",
+            "max",
+            "lastNotNull"
           ],
-          "title": "Cluster time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
+          "displayMode": "table",
+          "placement": "right",
+          "showLegend": true
         },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "asc"
+        }
+      },
+      "targets": [
         {
           "datasource": {
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 14
-          },
-          "id": 45,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "KubeadmControlPlane time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 14
-          },
-          "id": 50,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_kubeadmcontrolplane_created) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "KubeadmControlPlane time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 22
-          },
-          "id": 44,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Control Plane Machine time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 22
-          },
-          "id": 51,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Control Plane Machine time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 30
-          },
-          "id": 48,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Worker Machine time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 30
-          },
-          "id": 52,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Worker Machine time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 38
-          },
-          "id": 46,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Control Plane KubeadmConfig time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 38
-          },
-          "id": 53,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
+          "editorMode": "code",
+          "expr": "avg((capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}>= time() - 60*10) - on(name) group_left () capi_kubeadmcontrolplane_created) by (type)",
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "KubeadmControlPlane time until condition true since creationTimestamp",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 1,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
             }
           },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 22
+      },
+      "id": 44,
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "min",
+            "max",
+            "lastNotNull"
           ],
-          "title": "Control Plane KubeadmConfig time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
+          "displayMode": "table",
+          "placement": "right",
+          "showLegend": true
         },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "asc"
+        }
+      },
+      "targets": [
         {
           "datasource": {
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 0,
-            "y": 46
-          },
-          "id": 47,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
+          "editorMode": "code",
+          "expr": "avg((capi_machine_status_condition_last_transition_time{status=\"True\"}>= time() - 60*10) - on(name) group_left () capi_machine_created) by (type)",
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Machine time until condition true since creationTimestamp",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 1,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
             }
           },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
               },
-              "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 30
+      },
+      "id": 46,
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "min",
+            "max",
+            "lastNotNull"
           ],
-          "title": "Worker KubeadmConfig time until condition first true since creationTimestamp (all)",
-          "type": "timeseries"
+          "displayMode": "table",
+          "placement": "right",
+          "showLegend": true
         },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "asc"
+        }
+      },
+      "targets": [
         {
           "datasource": {
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 1,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 8,
-            "w": 12,
-            "x": 12,
-            "y": 46
-          },
-          "id": 54,
-          "options": {
-            "legend": {
-              "calcs": [
-                "mean",
-                "min",
-                "max",
-                "lastNotNull"
-              ],
-              "displayMode": "table",
-              "placement": "right",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "asc"
+          "editorMode": "code",
+          "expr": "(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}>= time() - 60*10) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\"))",
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Control Plane KubeadmConfig time until condition true since creationTimestamp",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 1,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
             }
           },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green"
               },
-              "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
-              "legendFormat": "{{type}}",
-              "range": true,
-              "refId": "A"
-            }
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 38
+      },
+      "id": 47,
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "min",
+            "max",
+            "lastNotNull"
           ],
-          "title": "Worker KubeadmConfig time until condition first true since creationTimestamp (last 5m)",
-          "type": "timeseries"
+          "displayMode": "table",
+          "placement": "right",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "asc"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "editorMode": "code",
+          "expr": "(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}>= time() - 60*10) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\"))",
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
         }
       ],
-      "title": "Performance",
-      "type": "row"
+      "title": "Worker KubeadmConfig time until condition true since creationTimestamp",
+      "type": "timeseries"
     },
     {
       "collapsed": true,
@@ -1752,7 +1064,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 6
+        "y": 46
       },
       "id": 22,
       "panels": [
@@ -2063,7 +1375,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 7
+        "y": 47
       },
       "id": 21,
       "panels": [
@@ -2422,7 +1734,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 8
+        "y": 48
       },
       "id": 20,
       "panels": [
@@ -2486,7 +1798,7 @@
             "h": 8,
             "w": 6,
             "x": 0,
-            "y": 9
+            "y": 8
           },
           "id": 15,
           "options": {
@@ -2601,7 +1913,7 @@
             "h": 8,
             "w": 6,
             "x": 6,
-            "y": 9
+            "y": 8
           },
           "id": 16,
           "options": {
@@ -2692,7 +2004,7 @@
             "h": 8,
             "w": 6,
             "x": 12,
-            "y": 9
+            "y": 8
           },
           "id": 17,
           "options": {
@@ -2843,7 +2155,7 @@
             "h": 8,
             "w": 6,
             "x": 18,
-            "y": 9
+            "y": 8
           },
           "id": 18,
           "options": {
@@ -2884,7 +2196,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 9
+        "y": 49
       },
       "id": 19,
       "panels": [
@@ -2948,7 +2260,7 @@
             "h": 8,
             "w": 6,
             "x": 0,
-            "y": 10
+            "y": 17
           },
           "id": 23,
           "options": {
@@ -3063,7 +2375,7 @@
             "h": 8,
             "w": 6,
             "x": 6,
-            "y": 10
+            "y": 17
           },
           "id": 24,
           "options": {
@@ -3154,7 +2466,7 @@
             "h": 8,
             "w": 6,
             "x": 12,
-            "y": 10
+            "y": 17
           },
           "id": 25,
           "options": {
@@ -3243,7 +2555,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 10
+        "y": 50
       },
       "id": 35,
       "panels": [
@@ -3307,7 +2619,7 @@
             "h": 8,
             "w": 6,
             "x": 0,
-            "y": 11
+            "y": 18
           },
           "id": 36,
           "options": {
@@ -3422,7 +2734,7 @@
             "h": 8,
             "w": 6,
             "x": 6,
-            "y": 11
+            "y": 18
           },
           "id": 37,
           "options": {
@@ -3513,7 +2825,7 @@
             "h": 8,
             "w": 6,
             "x": 12,
-            "y": 11
+            "y": 18
           },
           "id": 38,
           "options": {
@@ -3652,7 +2964,7 @@
             "h": 8,
             "w": 6,
             "x": 18,
-            "y": 11
+            "y": 18
           },
           "id": 39,
           "options": {
@@ -3693,7 +3005,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 11
+        "y": 51
       },
       "id": 27,
       "panels": [
@@ -3790,7 +3102,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name!=\"\"}) by (name) * on(name) group_right () (capi_machine_status_condition{status=\"True\"} == 1)) by (type)",
+              "expr": "sum(capi_machine_info{control_plane_name!=\"\"} * on(name) group_right () (capi_machine_status_condition{status=\"True\"} == 1)) by (type)",
               "hide": false,
               "legendFormat": "{{type}}",
               "range": true,
@@ -3802,7 +3114,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name!=\"\"}) by (name) * on(name) group_right () (capi_machine_status_condition{status=\"False\"} == 1)) by (type)",
+              "expr": "sum(capi_machine_info{control_plane_name!=\"\"} * on(name) group_right () (capi_machine_status_condition{status=\"False\"} == 1)) by (type)",
               "hide": false,
               "legendFormat": "{{type}} (False)",
               "range": true,
@@ -3985,7 +3297,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name!=\"\"}) by (name) * on (name) group_right () (capi_machine_status_phase == 1)) by (phase)",
+              "expr": "sum(capi_machine_info{control_plane_name!=\"\"} * on (name) group_right () (capi_machine_status_phase == 1)) by (phase)",
               "legendFormat": "{{phase}}",
               "range": true,
               "refId": "A"
@@ -4087,7 +3399,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name=\"\"}) by (name) * on(name) group_right () (capi_machine_status_condition{status=\"True\"} == 1)) by (type)",
+              "expr": "sum(capi_machine_info{control_plane_name=\"\"} * on(name) group_right () (capi_machine_status_condition{status=\"True\"} == 1)) by (type)",
               "hide": false,
               "legendFormat": "{{type}}",
               "range": true,
@@ -4099,7 +3411,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name=\"\"}) by (name) * on(name) group_right () (capi_machine_status_condition{status=\"False\"} == 1)) by (type)",
+              "expr": "sum(capi_machine_info{control_plane_name=\"\"} * on(name) group_right () (capi_machine_status_condition{status=\"False\"} == 1)) by (type)",
               "hide": false,
               "legendFormat": "{{type}} (False)",
               "range": true,
@@ -4282,7 +3594,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(avg(capi_machine_info{control_plane_name=\"\"}) by (name) * on (name) group_right () (capi_machine_status_phase == 1)) by (phase)",
+              "expr": "sum(capi_machine_info{control_plane_name=\"\"} * on (name) group_right () (capi_machine_status_phase == 1)) by (phase)",
               "legendFormat": "{{phase}}",
               "range": true,
               "refId": "A"
@@ -4301,7 +3613,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 12
+        "y": 52
       },
       "id": 34,
       "panels": [
@@ -4536,7 +3848,7 @@
     "list": []
   },
   "time": {
-    "from": "now-30m",
+    "from": "now-3h",
     "to": "now"
   },
   "timepicker": {},
diff --git a/hack/observability/grafana/dashboards/controller-runtime.json b/hack/observability/grafana/dashboards/controller-runtime.json
index f91d6f71d..d33f68941 100644
--- a/hack/observability/grafana/dashboards/controller-runtime.json
+++ b/hack/observability/grafana/dashboards/controller-runtime.json
@@ -19,7 +19,7 @@
   "editable": true,
   "fiscalYearStartMonth": 0,
   "graphTooltip": 0,
-  "id": 2,
+  "id": 1,
   "links": [],
   "liveNow": false,
   "panels": [
@@ -1815,7 +1815,7 @@
           },
           "gridPos": {
             "h": 8,
-            "w": 24,
+            "w": 12,
             "x": 0,
             "y": 11
           },
@@ -1839,7 +1839,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "sum(rate(controller_runtime_reconcile_time_seconds_sum{pod=~\"$Pod\"}[5m])) by (pod) / sum(rate(controller_runtime_reconcile_time_seconds_count{pod=~\"$Pod\"}[5m])) by (pod)",
+              "expr": "sum(rate(controller_runtime_reconcile_time_seconds_sum{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod) / sum(rate(controller_runtime_reconcile_time_seconds_count{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod)",
               "legendFormat": "{{pod}}",
               "range": true,
               "refId": "A"
@@ -1853,7 +1853,6 @@
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "description": "",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -1908,12 +1907,12 @@
             "overrides": []
           },
           "gridPos": {
-            "h": 9,
-            "w": 24,
-            "x": 0,
-            "y": 19
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 11
           },
-          "id": 65,
+          "id": 28,
           "options": {
             "legend": {
               "calcs": [],
@@ -1933,38 +1932,13 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.50, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\"}[5m])) by (pod, le))",
-              "hide": false,
-              "legendFormat": "P50 {{pod}}",
-              "range": true,
-              "refId": "B"
-            },
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "histogram_quantile(0.90, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\"}[5m])) by (pod, le))",
-              "hide": false,
-              "legendFormat": "P90 {{pod}}",
+              "expr": "sum(rate(controller_runtime_reconcile_time_seconds_sum{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod,controller) / sum(rate(controller_runtime_reconcile_time_seconds_count{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod,controller)",
+              "legendFormat": "{{pod}}: {{controller}}",
               "range": true,
               "refId": "A"
-            },
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\"}[5m])) by (pod, le))",
-              "hide": false,
-              "legendFormat": "P99 {{pod}}",
-              "range": true,
-              "refId": "C"
             }
           ],
-          "title": "Reconcile Duration Quantils",
+          "title": "Reconcile Duration by Controller",
           "type": "timeseries"
         },
         {
@@ -2029,7 +2003,7 @@
             "h": 8,
             "w": 24,
             "x": 0,
-            "y": 28
+            "y": 19
           },
           "id": 62,
           "options": {
@@ -2060,125 +2034,6 @@
           "title": "Reconcile Duration by Controller",
           "type": "timeseries"
         },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "description": "",
-          "fieldConfig": {
-            "defaults": {
-              "color": {
-                "mode": "palette-classic"
-              },
-              "custom": {
-                "axisCenteredZero": false,
-                "axisColorMode": "text",
-                "axisLabel": "",
-                "axisPlacement": "auto",
-                "barAlignment": 0,
-                "drawStyle": "line",
-                "fillOpacity": 0,
-                "gradientMode": "none",
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "lineInterpolation": "linear",
-                "lineWidth": 1,
-                "pointSize": 5,
-                "scaleDistribution": {
-                  "type": "linear"
-                },
-                "showPoints": "auto",
-                "spanNulls": false,
-                "stacking": {
-                  "group": "A",
-                  "mode": "none"
-                },
-                "thresholdsStyle": {
-                  "mode": "off"
-                }
-              },
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              },
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 9,
-            "w": 24,
-            "x": 0,
-            "y": 36
-          },
-          "id": 66,
-          "options": {
-            "legend": {
-              "calcs": [],
-              "displayMode": "list",
-              "placement": "bottom",
-              "showLegend": true
-            },
-            "tooltip": {
-              "mode": "multi",
-              "sort": "desc"
-            }
-          },
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "histogram_quantile(0.50, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod, controller, le))",
-              "hide": false,
-              "legendFormat": "P50 {{pod}} {{controller}} ",
-              "range": true,
-              "refId": "B"
-            },
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "histogram_quantile(0.90, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod, controller, le))",
-              "hide": false,
-              "legendFormat": "P90 {{pod}} {{controller}} ",
-              "range": true,
-              "refId": "A"
-            },
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "expr": "histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{pod=~\"$Pod\",controller=~\"$Controller\"}[5m])) by (pod, controller, le))",
-              "hide": false,
-              "legendFormat": "P99 {{pod}} {{controller}} ",
-              "range": true,
-              "refId": "C"
-            }
-          ],
-          "title": "Reconcile Duration Quantils by Controller",
-          "type": "timeseries"
-        },
         {
           "datasource": {
             "type": "prometheus",
@@ -2203,7 +2058,7 @@
             "h": 13,
             "w": 24,
             "x": 0,
-            "y": 45
+            "y": 27
           },
           "id": 26,
           "options": {
@@ -2464,7 +2319,7 @@
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "description": "Total number of retries handled by workqueue. In controller-runtime the retry metric is observed when an item is added to the queue because of RequeueAfter.",
+          "description": "Total number of retries handled by workqueue",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -2746,7 +2601,7 @@
             "type": "prometheus",
             "uid": "PBFA97CFB590B2093"
           },
-          "description": "How long in seconds processing an item from workqueue takes",
+          "description": "How long in seconds an item stays in workqueue before being requested",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -2806,7 +2661,7 @@
             "x": 0,
             "y": 36
           },
-          "id": 64,
+          "id": 63,
           "options": {
             "legend": {
               "calcs": [],
@@ -2826,7 +2681,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P50 {{pod}} {{name}} ",
               "range": true,
@@ -2838,7 +2693,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.90, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.90, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P90 {{pod}} {{name}} ",
               "range": true,
@@ -2850,14 +2705,14 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.99, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P99 {{pod}} {{name}} ",
               "range": true,
               "refId": "C"
             }
           ],
-          "title": "Workqueue Work Duration Quantils",
+          "title": "Workqueue Queue Duration Quantils",
           "type": "timeseries"
         },
         {
@@ -2866,6 +2721,87 @@
             "uid": "PBFA97CFB590B2093"
           },
           "description": "How long in seconds an item stays in workqueue before being requested",
+          "fieldConfig": {
+            "defaults": {
+              "custom": {
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "scaleDistribution": {
+                  "type": "linear"
+                }
+              }
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 13,
+            "w": 24,
+            "x": 0,
+            "y": 45
+          },
+          "id": 36,
+          "options": {
+            "calculate": false,
+            "cellGap": 2,
+            "color": {
+              "exponent": 0.5,
+              "fill": "dark-orange",
+              "mode": "scheme",
+              "reverse": true,
+              "scale": "exponential",
+              "scheme": "Oranges",
+              "steps": 128
+            },
+            "exemplars": {
+              "color": "rgba(255,0,255,0.7)"
+            },
+            "filterValues": {
+              "le": 1e-9
+            },
+            "legend": {
+              "show": true
+            },
+            "rowsFrame": {
+              "layout": "auto"
+            },
+            "tooltip": {
+              "show": true,
+              "yHistogram": false
+            },
+            "yAxis": {
+              "axisPlacement": "left",
+              "reverse": false,
+              "unit": "short"
+            }
+          },
+          "pluginVersion": "9.5.2",
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "PBFA97CFB590B2093"
+              },
+              "editorMode": "code",
+              "exemplar": true,
+              "expr": "rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\", name=~\"$Controller\"}[$__rate_interval])",
+              "format": "heatmap",
+              "legendFormat": "{{le}}",
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "Workqueue Queue Duration",
+          "type": "heatmap"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "description": "How long in seconds processing an item from workqueue takes",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -2923,9 +2859,9 @@
             "h": 9,
             "w": 24,
             "x": 0,
-            "y": 45
+            "y": 58
           },
-          "id": 63,
+          "id": 64,
           "options": {
             "legend": {
               "calcs": [],
@@ -2945,7 +2881,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P50 {{pod}} {{name}} ",
               "range": true,
@@ -2957,7 +2893,7 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.90, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.90, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P90 {{pod}} {{name}} ",
               "range": true,
@@ -2969,14 +2905,14 @@
                 "uid": "PBFA97CFB590B2093"
               },
               "editorMode": "code",
-              "expr": "histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
+              "expr": "histogram_quantile(0.99, sum(rate(workqueue_work_duration_seconds_bucket{pod=~\"$Pod\",name=~\"$Controller\"}[5m])) by (pod, name, le))",
               "hide": false,
               "legendFormat": "P99 {{pod}} {{name}} ",
               "range": true,
               "refId": "C"
             }
           ],
-          "title": "Workqueue Queue Duration Quantils",
+          "title": "Workqueue Work Duration Quantils",
           "type": "timeseries"
         },
         {
@@ -3004,7 +2940,7 @@
             "h": 13,
             "w": 24,
             "x": 0,
-            "y": 54
+            "y": 67
           },
           "id": 37,
           "options": {
@@ -3059,87 +2995,6 @@
           ],
           "title": "Workqueue Work Duration",
           "type": "heatmap"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "PBFA97CFB590B2093"
-          },
-          "description": "How long in seconds an item stays in workqueue before being requested",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {
-                "hideFrom": {
-                  "legend": false,
-                  "tooltip": false,
-                  "viz": false
-                },
-                "scaleDistribution": {
-                  "type": "linear"
-                }
-              }
-            },
-            "overrides": []
-          },
-          "gridPos": {
-            "h": 13,
-            "w": 24,
-            "x": 0,
-            "y": 67
-          },
-          "id": 36,
-          "options": {
-            "calculate": false,
-            "cellGap": 2,
-            "color": {
-              "exponent": 0.5,
-              "fill": "dark-orange",
-              "mode": "scheme",
-              "reverse": true,
-              "scale": "exponential",
-              "scheme": "Oranges",
-              "steps": 128
-            },
-            "exemplars": {
-              "color": "rgba(255,0,255,0.7)"
-            },
-            "filterValues": {
-              "le": 1e-9
-            },
-            "legend": {
-              "show": true
-            },
-            "rowsFrame": {
-              "layout": "auto"
-            },
-            "tooltip": {
-              "show": true,
-              "yHistogram": false
-            },
-            "yAxis": {
-              "axisPlacement": "left",
-              "reverse": false,
-              "unit": "short"
-            }
-          },
-          "pluginVersion": "9.5.2",
-          "targets": [
-            {
-              "datasource": {
-                "type": "prometheus",
-                "uid": "PBFA97CFB590B2093"
-              },
-              "editorMode": "code",
-              "exemplar": true,
-              "expr": "rate(workqueue_queue_duration_seconds_bucket{pod=~\"$Pod\", name=~\"$Controller\"}[$__rate_interval])",
-              "format": "heatmap",
-              "legendFormat": "{{le}}",
-              "range": true,
-              "refId": "A"
-            }
-          ],
-          "title": "Workqueue Queue Duration",
-          "type": "heatmap"
         }
       ],
       "title": "Workqueue",
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index e6adc69f1..3f2c3144a 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -115,10 +115,10 @@ require (
 	github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
-	github.com/prometheus/client_golang v1.16.0 // indirect
+	github.com/prometheus/client_golang v1.15.1 // indirect
 	github.com/prometheus/client_model v0.4.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.10.1 // indirect
+	github.com/prometheus/procfs v0.9.0 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/afero v1.9.5 // indirect
@@ -131,13 +131,13 @@ require (
 	github.com/xlab/treeprint v1.1.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
-	golang.org/x/crypto v0.10.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
 	golang.org/x/mod v0.10.0 // indirect
-	golang.org/x/net v0.11.0 // indirect
-	golang.org/x/oauth2 v0.9.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/oauth2 v0.8.0 // indirect
 	golang.org/x/sync v0.2.0 // indirect
-	golang.org/x/sys v0.9.0 // indirect
-	golang.org/x/term v0.9.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/term v0.8.0 // indirect
 	golang.org/x/text v0.10.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index 3fb823479..985ef09bf 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -419,7 +419,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
+github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
 github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
 github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -445,8 +445,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -462,8 +462,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -573,8 +573,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -653,8 +653,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
-golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -664,8 +664,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
-golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -732,13 +732,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
-golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/internal/controllers/machine/machine_controller.go b/internal/controllers/machine/machine_controller.go
index a38b22c82..5b62bbb9a 100644
--- a/internal/controllers/machine/machine_controller.go
+++ b/internal/controllers/machine/machine_controller.go
@@ -56,6 +56,11 @@ import (
 	"sigs.k8s.io/cluster-api/util/predicates"
 )
 
+const (
+	// controllerName defines the controller used when creating clients.
+	controllerName = "machine-controller"
+)
+
 var (
 	errNilNodeRef                 = errors.New("noderef is nil")
 	errLastControlPlaneNode       = errors.New("last control plane member")
@@ -574,7 +579,7 @@ func (r *Reconciler) isDeleteNodeAllowed(ctx context.Context, cluster *clusterv1
 func (r *Reconciler) drainNode(ctx context.Context, cluster *clusterv1.Cluster, nodeName string) (ctrl.Result, error) {
 	log := ctrl.LoggerFrom(ctx, "Node", klog.KRef("", nodeName))
 
-	restConfig, err := r.Tracker.GetRESTConfig(ctx, util.ObjectKey(cluster))
+	restConfig, err := remote.RESTConfig(ctx, controllerName, r.Client, util.ObjectKey(cluster))
 	if err != nil {
 		log.Error(err, "Error creating a remote client while deleting Machine, won't retry")
 		return ctrl.Result{}, nil


Test patch:
diff --git a/bootstrap/util/configowner_test.go b/bootstrap/util/configowner_test.go
index cbf30e44a..bdd9b767a 100644
--- a/bootstrap/util/configowner_test.go
+++ b/bootstrap/util/configowner_test.go
@@ -17,7 +17,6 @@ limitations under the License.
 package util
 
 import (
-	"context"
 	"testing"
 
 	. "github.com/onsi/gomega"
@@ -26,7 +25,6 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/utils/pointer"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -36,146 +34,136 @@ import (
 )
 
 func TestGetConfigOwner(t *testing.T) {
-	doTests := func(t *testing.T, getFn func(context.Context, client.Client, metav1.Object) (*ConfigOwner, error)) {
-		t.Helper()
-
-		t.Run("should get the owner when present (Machine)", func(t *testing.T) {
-			g := NewWithT(t)
-			myMachine := &clusterv1.Machine{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "my-machine",
-					Namespace: metav1.NamespaceDefault,
-					Labels: map[string]string{
-						clusterv1.MachineControlPlaneLabel: "",
-					},
-				},
-				Spec: clusterv1.MachineSpec{
-					ClusterName: "my-cluster",
-					Bootstrap: clusterv1.Bootstrap{
-						DataSecretName: pointer.String("my-data-secret"),
-					},
-					Version: pointer.String("v1.19.6"),
+	t.Run("should get the owner when present (Machine)", func(t *testing.T) {
+		g := NewWithT(t)
+		myMachine := &clusterv1.Machine{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "my-machine",
+				Namespace: metav1.NamespaceDefault,
+				Labels: map[string]string{
+					clusterv1.MachineControlPlaneLabel: "",
 				},
-				Status: clusterv1.MachineStatus{
-					InfrastructureReady: true,
+			},
+			Spec: clusterv1.MachineSpec{
+				ClusterName: "my-cluster",
+				Bootstrap: clusterv1.Bootstrap{
+					DataSecretName: pointer.String("my-data-secret"),
 				},
-			}
+				Version: pointer.String("v1.19.6"),
+			},
+			Status: clusterv1.MachineStatus{
+				InfrastructureReady: true,
+			},
+		}
 
-			c := fake.NewClientBuilder().WithObjects(myMachine).Build()
-			obj := &bootstrapv1.KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					OwnerReferences: []metav1.OwnerReference{
-						{
-							Kind:       "Machine",
-							APIVersion: clusterv1.GroupVersion.String(),
-							Name:       "my-machine",
-						},
+		c := fake.NewClientBuilder().WithObjects(myMachine).Build()
+		obj := &bootstrapv1.KubeadmConfig{
+			ObjectMeta: metav1.ObjectMeta{
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						Kind:       "Machine",
+						APIVersion: clusterv1.GroupVersion.String(),
+						Name:       "my-machine",
 					},
-					Namespace: metav1.NamespaceDefault,
-					Name:      "my-resource-owned-by-machine",
 				},
-			}
-			configOwner, err := getFn(ctx, c, obj)
-			g.Expect(err).NotTo(HaveOccurred())
-			g.Expect(configOwner).ToNot(BeNil())
-			g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
-			g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
-			g.Expect(configOwner.IsControlPlaneMachine()).To(BeTrue())
-			g.Expect(configOwner.IsMachinePool()).To(BeFalse())
-			g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
-			g.Expect(*configOwner.DataSecretName()).To(BeEquivalentTo("my-data-secret"))
-		})
+				Namespace: metav1.NamespaceDefault,
+				Name:      "my-resource-owned-by-machine",
+			},
+		}
+		configOwner, err := GetConfigOwner(ctx, c, obj)
+		g.Expect(err).NotTo(HaveOccurred())
+		g.Expect(configOwner).ToNot(BeNil())
+		g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
+		g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
+		g.Expect(configOwner.IsControlPlaneMachine()).To(BeTrue())
+		g.Expect(configOwner.IsMachinePool()).To(BeFalse())
+		g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
+		g.Expect(*configOwner.DataSecretName()).To(BeEquivalentTo("my-data-secret"))
+	})
 
-		t.Run("should get the owner when present (MachinePool)", func(t *testing.T) {
-			_ = feature.MutableGates.Set("MachinePool=true")
+	t.Run("should get the owner when present (MachinePool)", func(t *testing.T) {
+		_ = feature.MutableGates.Set("MachinePool=true")
 
-			g := NewWithT(t)
-			myPool := &expv1.MachinePool{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "my-machine-pool",
-					Namespace: metav1.NamespaceDefault,
-					Labels: map[string]string{
-						clusterv1.MachineControlPlaneLabel: "",
-					},
+		g := NewWithT(t)
+		myPool := &expv1.MachinePool{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "my-machine-pool",
+				Namespace: metav1.NamespaceDefault,
+				Labels: map[string]string{
+					clusterv1.MachineControlPlaneLabel: "",
 				},
-				Spec: expv1.MachinePoolSpec{
-					ClusterName: "my-cluster",
-					Template: clusterv1.MachineTemplateSpec{
-						Spec: clusterv1.MachineSpec{
-							Version: pointer.String("v1.19.6"),
-						},
+			},
+			Spec: expv1.MachinePoolSpec{
+				ClusterName: "my-cluster",
+				Template: clusterv1.MachineTemplateSpec{
+					Spec: clusterv1.MachineSpec{
+						Version: pointer.String("v1.19.6"),
 					},
 				},
-				Status: expv1.MachinePoolStatus{
-					InfrastructureReady: true,
-				},
-			}
+			},
+			Status: expv1.MachinePoolStatus{
+				InfrastructureReady: true,
+			},
+		}
 
-			c := fake.NewClientBuilder().WithObjects(myPool).Build()
-			obj := &bootstrapv1.KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					OwnerReferences: []metav1.OwnerReference{
-						{
-							Kind:       "MachinePool",
-							APIVersion: expv1.GroupVersion.String(),
-							Name:       "my-machine-pool",
-						},
+		c := fake.NewClientBuilder().WithObjects(myPool).Build()
+		obj := &bootstrapv1.KubeadmConfig{
+			ObjectMeta: metav1.ObjectMeta{
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						Kind:       "MachinePool",
+						APIVersion: expv1.GroupVersion.String(),
+						Name:       "my-machine-pool",
 					},
-					Namespace: metav1.NamespaceDefault,
-					Name:      "my-resource-owned-by-machine-pool",
 				},
-			}
-			configOwner, err := getFn(ctx, c, obj)
-			g.Expect(err).NotTo(HaveOccurred())
-			g.Expect(configOwner).ToNot(BeNil())
-			g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
-			g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
-			g.Expect(configOwner.IsControlPlaneMachine()).To(BeFalse())
-			g.Expect(configOwner.IsMachinePool()).To(BeTrue())
-			g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
-			g.Expect(configOwner.DataSecretName()).To(BeNil())
-		})
+				Namespace: metav1.NamespaceDefault,
+				Name:      "my-resource-owned-by-machine-pool",
+			},
+		}
+		configOwner, err := GetConfigOwner(ctx, c, obj)
+		g.Expect(err).NotTo(HaveOccurred())
+		g.Expect(configOwner).ToNot(BeNil())
+		g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
+		g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
+		g.Expect(configOwner.IsControlPlaneMachine()).To(BeFalse())
+		g.Expect(configOwner.IsMachinePool()).To(BeTrue())
+		g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
+		g.Expect(configOwner.DataSecretName()).To(BeNil())
+	})
 
-		t.Run("return an error when not found", func(t *testing.T) {
-			g := NewWithT(t)
-			c := fake.NewClientBuilder().Build()
-			obj := &bootstrapv1.KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					OwnerReferences: []metav1.OwnerReference{
-						{
-							Kind:       "Machine",
-							APIVersion: clusterv1.GroupVersion.String(),
-							Name:       "my-machine",
-						},
+	t.Run("return an error when not found", func(t *testing.T) {
+		g := NewWithT(t)
+		c := fake.NewClientBuilder().Build()
+		obj := &bootstrapv1.KubeadmConfig{
+			ObjectMeta: metav1.ObjectMeta{
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						Kind:       "Machine",
+						APIVersion: clusterv1.GroupVersion.String(),
+						Name:       "my-machine",
 					},
-					Namespace: metav1.NamespaceDefault,
-					Name:      "my-resource-owned-by-machine",
 				},
-			}
-			_, err := getFn(ctx, c, obj)
-			g.Expect(err).To(HaveOccurred())
-		})
-
-		t.Run("return nothing when there is no owner", func(t *testing.T) {
-			g := NewWithT(t)
-			c := fake.NewClientBuilder().Build()
-			obj := &bootstrapv1.KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					OwnerReferences: []metav1.OwnerReference{},
-					Namespace:       metav1.NamespaceDefault,
-					Name:            "my-resource-owned-by-machine",
-				},
-			}
-			configOwner, err := getFn(ctx, c, obj)
-			g.Expect(err).NotTo(HaveOccurred())
-			g.Expect(configOwner).To(BeNil())
-		})
-	}
-	t.Run("uncached", func(t *testing.T) {
-		doTests(t, GetConfigOwner)
+				Namespace: metav1.NamespaceDefault,
+				Name:      "my-resource-owned-by-machine",
+			},
+		}
+		_, err := GetConfigOwner(ctx, c, obj)
+		g.Expect(err).To(HaveOccurred())
 	})
-	t.Run("cached", func(t *testing.T) {
-		doTests(t, GetConfigOwnerFromCache)
+
+	t.Run("return nothing when there is no owner", func(t *testing.T) {
+		g := NewWithT(t)
+		c := fake.NewClientBuilder().Build()
+		obj := &bootstrapv1.KubeadmConfig{
+			ObjectMeta: metav1.ObjectMeta{
+				OwnerReferences: []metav1.OwnerReference{},
+				Namespace:       metav1.NamespaceDefault,
+				Name:            "my-resource-owned-by-machine",
+			},
+		}
+		configOwner, err := GetConfigOwner(ctx, c, obj)
+		g.Expect(err).NotTo(HaveOccurred())
+		g.Expect(configOwner).To(BeNil())
 	})
 }
 
diff --git a/controlplane/kubeadm/internal/cluster_test.go b/controlplane/kubeadm/internal/cluster_test.go
index 8d36f7ef4..efe89a367 100644
--- a/controlplane/kubeadm/internal/cluster_test.go
+++ b/controlplane/kubeadm/internal/cluster_test.go
@@ -105,6 +105,13 @@ func TestGetWorkloadCluster(t *testing.T) {
 	delete(emptyKeyEtcdSecret.Data, secret.TLSKeyDataName)
 	badCrtEtcdSecret := etcdSecret.DeepCopy()
 	badCrtEtcdSecret.Data[secret.TLSCrtDataName] = []byte("bad cert")
+	tracker, err := remote.NewClusterCacheTracker(
+		env.Manager,
+		remote.ClusterCacheTrackerOptions{
+			Log: &log.Log,
+		},
+	)
+	g.Expect(err).ToNot(HaveOccurred())
 
 	// Create kubeconfig secret
 	// Store the envtest config as the contents of the kubeconfig secret.
@@ -181,20 +188,10 @@ func TestGetWorkloadCluster(t *testing.T) {
 			for _, o := range tt.objs {
 				g.Expect(env.Client.Create(ctx, o)).To(Succeed())
 				defer func(do client.Object) {
-					g.Expect(env.CleanupAndWait(ctx, do)).To(Succeed())
+					g.Expect(env.Cleanup(ctx, do)).To(Succeed())
 				}(o)
 			}
 
-			// We have to create a new ClusterCacheTracker for every test case otherwise
-			// it could still have a rest config from a previous run cached.
-			tracker, err := remote.NewClusterCacheTracker(
-				env.Manager,
-				remote.ClusterCacheTrackerOptions{
-					Log: &log.Log,
-				},
-			)
-			g.Expect(err).ToNot(HaveOccurred())
-
 			// Note: The API reader is intentionally used instead of the regular (cached) client
 			// to avoid test failures when the local cache isn't able to catch up in time.
 			m := Management{
diff --git a/scripts/ci-e2e-lib.sh b/scripts/ci-e2e-lib.sh
index 3a5af5016..05944bf38 100644
--- a/scripts/ci-e2e-lib.sh
+++ b/scripts/ci-e2e-lib.sh
@@ -204,9 +204,9 @@ EOL
 # the actual test run less sensible to the network speed.
 kind:prepullAdditionalImages () {
   # Pulling cert manager images so we can pre-load in kind nodes
-  kind::prepullImage "quay.io/jetstack/cert-manager-cainjector:v1.12.2"
-  kind::prepullImage "quay.io/jetstack/cert-manager-webhook:v1.12.2"
-  kind::prepullImage "quay.io/jetstack/cert-manager-controller:v1.12.2"
+  kind::prepullImage "quay.io/jetstack/cert-manager-cainjector:v1.12.1"
+  kind::prepullImage "quay.io/jetstack/cert-manager-webhook:v1.12.1"
+  kind::prepullImage "quay.io/jetstack/cert-manager-controller:v1.12.1"
 }
 
 # kind:prepullImage pre-pull a docker image if no already present locally.
diff --git a/scripts/ci-test.sh b/scripts/ci-test.sh
index 2233611b0..c28560c64 100755
--- a/scripts/ci-test.sh
+++ b/scripts/ci-test.sh
@@ -31,10 +31,6 @@ echo -e "\n*** Testing Cluster API Provider Docker ***\n"
 # Docker provider
 make test-docker-infrastructure-junit
 
-echo -e "\n*** Testing Cluster API Provider In-Memory ***\n"
-# Docker provider
-make test-in-memory-infrastructure-junit
-
 echo -e "\n*** Testing Cluster API Runtime SDK test extension ***\n"
 # Test Extension
 make test-test-extension-junit
diff --git a/test/e2e/clusterclass_rollout.go b/test/e2e/clusterclass_rollout.go
index f40af1949..6c43f755e 100644
--- a/test/e2e/clusterclass_rollout.go
+++ b/test/e2e/clusterclass_rollout.go
@@ -279,7 +279,7 @@ func assertClusterObjects(ctx context.Context, clusterProxy framework.ClusterPro
 		assertMachineSetsMachines(g, clusterObjects, cluster)
 
 		By("All cluster objects have the right labels, annotations and selectors")
-	}, 30*time.Second, 1*time.Second).Should(Succeed())
+	}, 10*time.Second, 1*time.Second).Should(Succeed())
 }
 
 func assertInfrastructureCluster(g Gomega, clusterClassObjects clusterClassObjects, clusterObjects clusterObjects, cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) {
diff --git a/test/e2e/config/docker.yaml b/test/e2e/config/docker.yaml
index 60dbd2331..3f3f383a8 100644
--- a/test/e2e/config/docker.yaml
+++ b/test/e2e/config/docker.yaml
@@ -23,11 +23,11 @@ images:
   loadBehavior: tryLoad
 - name: gcr.io/k8s-staging-cluster-api/test-extension-{ARCH}:dev
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-cainjector:v1.12.2
+- name: quay.io/jetstack/cert-manager-cainjector:v1.12.1
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-webhook:v1.12.2
+- name: quay.io/jetstack/cert-manager-webhook:v1.12.1
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-controller:v1.12.2
+- name: quay.io/jetstack/cert-manager-controller:v1.12.1
   loadBehavior: tryLoad
 
 providers:
diff --git a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml
index 1626afac8..2db68ae61 100644
--- a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml
+++ b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml
@@ -220,34 +220,6 @@ spec:
             valueFrom:
               template: |
                 kindest/node:{{ .builtin.controlPlane.version | replace "+" "_" }}
-  - name: replaceImage-v1.23.17-machineDeployment
-    description: "Sets the container image for MD DockerMachineTemplates using Kubernetes v1.23.17."
-    enabledIf: '{{ semverCompare "v1.23.17" .builtin.machineDeployment.version }}'
-    definitions:
-      - selector:
-          apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-          kind: DockerMachineTemplate
-          matchResources:
-            machineDeploymentClass:
-              names:
-                - default-worker
-        jsonPatches:
-          - op: add
-            path: "/spec/template/spec/customImage"
-            value: "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff"
-  - name: replaceImage-v1.23.17-controlPlane
-    description: "Sets the container image for CP DockerMachineTemplates using Kubernetes v1.23.17."
-    enabledIf: '{{ semverCompare "v1.23.17" .builtin.controlPlane.version }}'
-    definitions:
-      - selector:
-          apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-          kind: DockerMachineTemplate
-          matchResources:
-            controlPlane: true
-        jsonPatches:
-          - op: add
-            path: "/spec/template/spec/customImage"
-            value: "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff"
   - name: preloadImages
     description: |
       Sets the container images to preload to the node that is used for running dockerMachines.
@@ -535,4 +507,3 @@ spec:
           kubeletExtraArgs:
             eviction-hard: 'nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%'
             fail-swap-on: "false"
-
diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go
index 7698ccbd1..d0f822fa8 100644
--- a/test/extension/handlers/topologymutation/handler.go
+++ b/test/extension/handlers/topologymutation/handler.go
@@ -24,7 +24,6 @@ package topologymutation
 import (
 	"context"
 	"fmt"
-	"strings"
 
 	"github.com/blang/semver"
 	"github.com/pkg/errors"
@@ -40,6 +39,7 @@ import (
 	runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
 	"sigs.k8s.io/cluster-api/exp/runtime/topologymutation"
 	infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 	"sigs.k8s.io/cluster-api/util/version"
 )
 
@@ -257,7 +257,9 @@ func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfi
 
 // patchDockerMachineTemplate patches the DockerMachineTemplate.
 // It sets the CustomImage to an image for the version in use by the controlPlane or by the MachineDeployment
-// the DockerMachineTemplate belongs to. This patch is required to pick up the kind image with the required Kubernetes version.
+// the DockerMachineTemplate belongs to.
+// NOTE: this patch is not required anymore after the introduction of the kind mapper in kind, however we keep it
+// as example of version aware patches.
 func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infrav1.DockerMachineTemplate, templateVariables map[string]apiextensionsv1.JSON) error {
 	log := ctrl.LoggerFrom(ctx)
 
@@ -270,13 +272,14 @@ func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infr
 		return errors.Wrap(err, "could not set customImage to control plane dockerMachineTemplate")
 	}
 	if found {
-		_, err := version.ParseMajorMinorPatchTolerant(cpVersion)
+		semVer, err := version.ParseMajorMinorPatchTolerant(cpVersion)
 		if err != nil {
 			return errors.Wrap(err, "could not parse control plane version")
 		}
-		customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(cpVersion, "+", "_"))
-		log.Info(fmt.Sprintf("Setting MachineDeployment custom image to %q", customImage))
-		dockerMachineTemplate.Spec.Template.Spec.CustomImage = customImage
+		kindMapping := kind.GetMapping(semVer, "")
+
+		log.Info(fmt.Sprintf("Setting MachineDeployment custom image to %q", kindMapping.Image))
+		dockerMachineTemplate.Spec.Template.Spec.CustomImage = kindMapping.Image
 		// return early if we have successfully patched a control plane dockerMachineTemplate
 		return nil
 	}
@@ -290,13 +293,14 @@ func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infr
 		return errors.Wrap(err, "could not set customImage to MachineDeployment DockerMachineTemplate")
 	}
 	if found {
-		_, err := version.ParseMajorMinorPatchTolerant(mdVersion)
+		semVer, err := version.ParseMajorMinorPatchTolerant(mdVersion)
 		if err != nil {
 			return errors.Wrap(err, "could not parse MachineDeployment version")
 		}
-		customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(mdVersion, "+", "_"))
-		log.Info(fmt.Sprintf("Setting MachineDeployment customImage to %q", customImage))
-		dockerMachineTemplate.Spec.Template.Spec.CustomImage = customImage
+		kindMapping := kind.GetMapping(semVer, "")
+
+		log.Info(fmt.Sprintf("Setting MachineDeployment customImage to %q", kindMapping.Image))
+		dockerMachineTemplate.Spec.Template.Spec.CustomImage = kindMapping.Image
 		return nil
 	}
 
diff --git a/test/go.mod b/test/go.mod
index 4b7cc86f4..fda556666 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -12,14 +12,14 @@ require (
 	github.com/evanphx/json-patch/v5 v5.6.0
 	github.com/flatcar/ignition v0.36.2
 	github.com/go-logr/logr v1.2.4
-	github.com/onsi/ginkgo/v2 v2.11.0
+	github.com/onsi/ginkgo/v2 v2.10.0
 	github.com/onsi/gomega v1.27.8
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/pflag v1.0.5
 	github.com/vincent-petithory/dataurl v1.0.0
 	go.etcd.io/etcd/api/v3 v3.5.9
 	go.etcd.io/etcd/client/v3 v3.5.9
-	golang.org/x/net v0.11.0
+	golang.org/x/net v0.10.0
 	google.golang.org/grpc v1.55.0
 	k8s.io/api v0.27.2
 	k8s.io/apiextensions-apiserver v0.27.2
@@ -99,10 +99,10 @@ require (
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
-	github.com/prometheus/client_golang v1.16.0 // indirect
+	github.com/prometheus/client_golang v1.15.1 // indirect
 	github.com/prometheus/client_model v0.4.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.10.1 // indirect
+	github.com/prometheus/procfs v0.9.0 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/afero v1.9.5 // indirect
@@ -117,10 +117,10 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.24.0 // indirect
-	golang.org/x/crypto v0.10.0 // indirect
-	golang.org/x/oauth2 v0.9.0 // indirect
-	golang.org/x/sys v0.9.0 // indirect
-	golang.org/x/term v0.9.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/oauth2 v0.8.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/term v0.8.0 // indirect
 	golang.org/x/text v0.10.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.9.3 // indirect
diff --git a/test/go.sum b/test/go.sum
index 1da5fa41b..b7703497a 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -375,8 +375,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
-github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
+github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
+github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
 github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
 github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -402,8 +402,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -415,8 +415,8 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
 github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -538,8 +538,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -617,8 +617,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
-golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -628,8 +628,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
-golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -694,13 +694,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
-golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/test/infrastructure/container/docker.go b/test/infrastructure/container/docker.go
index 4a6cdbcfe..c650fb016 100644
--- a/test/infrastructure/container/docker.go
+++ b/test/infrastructure/container/docker.go
@@ -40,6 +40,7 @@ import (
 	"k8s.io/utils/pointer"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 )
 
 const (
@@ -390,6 +391,8 @@ func (d *dockerRuntime) RunContainer(ctx context.Context, runConfig *RunContaine
 		restartMaximumRetryCount = 1
 	}
 
+	// TODO: check if we can simplify the following code for the CAPD load balancer, which now always has runConfig.KindMode == kind.ModeNone
+
 	hostConfig := dockercontainer.HostConfig{
 		// Running containers in a container requires privileges.
 		// NOTE: we could try to replicate this with --cap-add, and use less
@@ -406,6 +409,11 @@ func (d *dockerRuntime) RunContainer(ctx context.Context, runConfig *RunContaine
 	}
 	networkConfig := network.NetworkingConfig{}
 
+	// NOTE: starting from Kind 0.20 kind requires CgroupnsMode to be set to private.
+	if runConfig.KindMode != kind.ModeNone && runConfig.KindMode != kind.Mode0_19 {
+		hostConfig.CgroupnsMode = "private"
+	}
+
 	if runConfig.IPFamily == clusterv1.IPv6IPFamily || runConfig.IPFamily == clusterv1.DualStackIPFamily {
 		hostConfig.Sysctls = map[string]string{
 			"net.ipv6.conf.all.disable_ipv6": "0",
diff --git a/test/infrastructure/container/interface.go b/test/infrastructure/container/interface.go
index bbdfc121a..2517fc127 100644
--- a/test/infrastructure/container/interface.go
+++ b/test/infrastructure/container/interface.go
@@ -22,6 +22,7 @@ import (
 	"io"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 )
 
 // providerKey is the key type for accessing the runtime provider in passed contexts.
@@ -98,6 +99,8 @@ type RunContainerInput struct {
 	// RestartPolicy to use for the container.
 	// If not set, defaults to "unless-stopped".
 	RestartPolicy string
+	// Defines how the kindest/node image must be started.
+	KindMode kind.Mode
 }
 
 // ExecContainerInput contains values for running exec on a container.
diff --git a/test/infrastructure/docker/exp/internal/docker/nodepool.go b/test/infrastructure/docker/exp/internal/docker/nodepool.go
index ba99ac4e2..8956c3b34 100644
--- a/test/infrastructure/docker/exp/internal/docker/nodepool.go
+++ b/test/infrastructure/docker/exp/internal/docker/nodepool.go
@@ -25,6 +25,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/blang/semver"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/klog/v2"
@@ -37,8 +38,8 @@ import (
 	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
 	infraexpv1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/exp/api/v1beta1"
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/docker"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 	"sigs.k8s.io/cluster-api/util"
-	"sigs.k8s.io/cluster-api/util/container"
 )
 
 const (
@@ -168,15 +169,18 @@ func (np *NodePool) Delete(ctx context.Context) error {
 }
 
 func (np *NodePool) isMachineMatchingInfrastructureSpec(machine *docker.Machine) bool {
-	return imageVersion(machine) == container.SemverToOCIImageTag(*np.machinePool.Spec.Template.Spec.Version)
-}
+	// NOTE: With the current implementation we are checking if the machine is using a kindest/node image for the expected version,
+	// but not checking if the machine has the expected extra.mounts or pre.loaded images.
+
+	semVer, err := semver.Parse(strings.TrimPrefix(*np.machinePool.Spec.Template.Spec.Version, "v"))
+	if err != nil {
+		// TODO: consider if to return an error
+		panic(errors.Wrap(err, "failed to parse DockerMachine version").Error())
+	}
+
+	kindMapping := kind.GetMapping(semVer, np.dockerMachinePool.Spec.Template.CustomImage)
 
-// ImageVersion returns the version of the image used or nil if not specified
-// NOTE: Image version might be different from the Kubernetes version, because some characters
-// allowed by semver (e.g. +) can't be used for image tags, so they are replaced with "_".
-func imageVersion(m *docker.Machine) string {
-	containerImage := m.ContainerImage()
-	return containerImage[strings.LastIndex(containerImage, ":")+1:]
+	return machine.ContainerImage() == kindMapping.Image
 }
 
 // machinesMatchingInfrastructureSpec returns all of the docker.Machines which match the machine pool / docker machine pool spec.
diff --git a/test/infrastructure/docker/internal/docker/machine.go b/test/infrastructure/docker/internal/docker/machine.go
index e66e05a81..ccdcf3833 100644
--- a/test/infrastructure/docker/internal/docker/machine.go
+++ b/test/infrastructure/docker/internal/docker/machine.go
@@ -26,6 +26,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/blang/semver"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
@@ -45,22 +46,17 @@ import (
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/provisioning"
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/provisioning/cloudinit"
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/provisioning/ignition"
-	clusterapicontainer "sigs.k8s.io/cluster-api/util/container"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 	"sigs.k8s.io/cluster-api/util/patch"
 )
 
-const (
-	defaultImageName = "kindest/node"
-	defaultImageTag  = "v1.27.1"
-)
-
 var (
 	cloudProviderTaint = corev1.Taint{Key: "node.cloudprovider.kubernetes.io/uninitialized", Effect: corev1.TaintEffectNoSchedule}
 )
 
 type nodeCreator interface {
-	CreateControlPlaneNode(ctx context.Context, name, image, clusterName, listenAddress string, port int32, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily) (node *types.Node, err error)
-	CreateWorkerNode(ctx context.Context, name, image, clusterName string, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily) (node *types.Node, err error)
+	CreateControlPlaneNode(ctx context.Context, name, clusterName, listenAddress string, port int32, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily, kindMapping kind.Mapping) (node *types.Node, err error)
+	CreateWorkerNode(ctx context.Context, name, clusterName string, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily, kindMapping kind.Mapping) (node *types.Node, err error)
 }
 
 // Machine implement a service for managing the docker containers hosting a kubernetes nodes.
@@ -212,18 +208,26 @@ func (m *Machine) Create(ctx context.Context, image string, role string, version
 	if m.container == nil {
 		var err error
 
-		machineImage := m.machineImage(version)
-		if image != "" {
-			machineImage = image
+		// Get the KindMapping for the target K8s version.
+		// NOTE: The KindMapping allows to select the most recent kindest/node image available, if any, as well as
+		// provide info about the mode to be used when starting the kindest/node image itself.
+		if version == nil {
+			return errors.New("cannot create a DockerMachine for a nil version")
 		}
 
+		semVer, err := semver.Parse(strings.TrimPrefix(*version, "v"))
+		if err != nil {
+			return errors.Wrap(err, "failed to parse DockerMachine version")
+		}
+
+		kindMapping := kind.GetMapping(semVer, image)
+
 		switch role {
 		case constants.ControlPlaneNodeRoleValue:
-			log.Info(fmt.Sprintf("Creating control plane machine container with image %s", machineImage))
+			log.Info(fmt.Sprintf("Creating control plane machine container with image %s, mode %s", kindMapping.Image, kindMapping.Mode))
 			m.container, err = m.nodeCreator.CreateControlPlaneNode(
 				ctx,
 				m.ContainerName(),
-				machineImage,
 				m.cluster,
 				"127.0.0.1",
 				0,
@@ -231,21 +235,22 @@ func (m *Machine) Create(ctx context.Context, image string, role string, version
 				nil,
 				labels,
 				m.ipFamily,
+				kindMapping,
 			)
 			if err != nil {
 				return errors.WithStack(err)
 			}
 		case constants.WorkerNodeRoleValue:
-			log.Info(fmt.Sprintf("Creating worker machine container with image %s", machineImage))
+			log.Info(fmt.Sprintf("Creating worker machine container with image %s, mode %s", kindMapping.Image, kindMapping.Mode))
 			m.container, err = m.nodeCreator.CreateWorkerNode(
 				ctx,
 				m.ContainerName(),
-				machineImage,
 				m.cluster,
 				kindMounts(mounts),
 				nil,
 				labels,
 				m.ipFamily,
+				kindMapping,
 			)
 			if err != nil {
 				return errors.WithStack(err)
@@ -527,26 +532,6 @@ func (m *Machine) Delete(ctx context.Context) error {
 	return nil
 }
 
-// machineImage is the image of the container node with the machine.
-func (m *Machine) machineImage(version *string) string {
-	if version == nil {
-		defaultImage := fmt.Sprintf("%s:%s", defaultImageName, defaultImageTag)
-		return defaultImage
-	}
-
-	// TODO(fp) make this smarter
-	// - allows usage of custom docker repository & image names
-	// - add v only for semantic versions
-	versionString := *version
-	if !strings.HasPrefix(versionString, "v") {
-		versionString = fmt.Sprintf("v%s", versionString)
-	}
-
-	versionString = clusterapicontainer.SemverToOCIImageTag(versionString)
-
-	return fmt.Sprintf("%s:%s", defaultImageName, versionString)
-}
-
 func logContainerDebugInfo(ctx context.Context, log logr.Logger, name string) {
 	containerRuntime, err := container.RuntimeFrom(ctx)
 	if err != nil {
diff --git a/test/infrastructure/docker/internal/docker/kind_manager.go b/test/infrastructure/docker/internal/docker/manager.go
similarity index 87%
rename from test/infrastructure/docker/internal/docker/kind_manager.go
rename to test/infrastructure/docker/internal/docker/manager.go
index 813b4aabe..91ea59727 100644
--- a/test/infrastructure/docker/internal/docker/kind_manager.go
+++ b/test/infrastructure/docker/internal/docker/manager.go
@@ -27,6 +27,7 @@ import (
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/test/infrastructure/container"
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/docker/types"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 )
 
 // KubeadmContainerPort is the port that kubeadm listens on in the container.
@@ -46,7 +47,6 @@ type Manager struct{}
 
 type nodeCreateOpts struct {
 	Name         string
-	Image        string
 	ClusterName  string
 	Role         string
 	EntryPoint   []string
@@ -54,12 +54,13 @@ type nodeCreateOpts struct {
 	PortMappings []v1alpha4.PortMapping
 	Labels       map[string]string
 	IPFamily     clusterv1.ClusterIPFamily
+	KindMapping  kind.Mapping
 }
 
 // CreateControlPlaneNode will create a new control plane container.
 // NOTE: If port is 0 picking a host port for the control plane is delegated to the container runtime and is not stable across container restarts.
 // This means that connection to a control plane node may take some time to recover if the underlying container is restarted.
-func (m *Manager) CreateControlPlaneNode(ctx context.Context, name, image, clusterName, listenAddress string, port int32, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily) (*types.Node, error) {
+func (m *Manager) CreateControlPlaneNode(ctx context.Context, name, clusterName, listenAddress string, port int32, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily, kindMapping kind.Mapping) (*types.Node, error) {
 	// add api server port mapping
 	portMappingsWithAPIServer := append(portMappings, v1alpha4.PortMapping{
 		ListenAddress: listenAddress,
@@ -69,13 +70,13 @@ func (m *Manager) CreateControlPlaneNode(ctx context.Context, name, image, clust
 	})
 	createOpts := &nodeCreateOpts{
 		Name:         name,
-		Image:        image,
 		ClusterName:  clusterName,
 		Role:         constants.ControlPlaneNodeRoleValue,
 		PortMappings: portMappingsWithAPIServer,
 		Mounts:       mounts,
 		Labels:       labels,
 		IPFamily:     ipFamily,
+		KindMapping:  kindMapping,
 	}
 	node, err := createNode(ctx, createOpts)
 	if err != nil {
@@ -86,16 +87,16 @@ func (m *Manager) CreateControlPlaneNode(ctx context.Context, name, image, clust
 }
 
 // CreateWorkerNode will create a new worker container.
-func (m *Manager) CreateWorkerNode(ctx context.Context, name, image, clusterName string, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily) (*types.Node, error) {
+func (m *Manager) CreateWorkerNode(ctx context.Context, name, clusterName string, mounts []v1alpha4.Mount, portMappings []v1alpha4.PortMapping, labels map[string]string, ipFamily clusterv1.ClusterIPFamily, kindMapping kind.Mapping) (*types.Node, error) {
 	createOpts := &nodeCreateOpts{
 		Name:         name,
-		Image:        image,
 		ClusterName:  clusterName,
 		Role:         constants.WorkerNodeRoleValue,
 		PortMappings: portMappings,
 		Mounts:       mounts,
 		Labels:       labels,
 		IPFamily:     ipFamily,
+		KindMapping:  kindMapping,
 	}
 	return createNode(ctx, createOpts)
 }
@@ -113,11 +114,16 @@ func (m *Manager) CreateExternalLoadBalancerNode(ctx context.Context, name, imag
 	}}
 	createOpts := &nodeCreateOpts{
 		Name:         name,
-		Image:        image,
 		ClusterName:  clusterName,
 		Role:         constants.ExternalLoadBalancerNodeRoleValue,
 		PortMappings: portMappings,
 		EntryPoint:   haproxyEntrypoint,
+		// Load balancer doesn't have an equivalent in kind, but we use a kind.Mapping to
+		// forward the image name to create node.
+		KindMapping: kind.Mapping{
+			Image: image,
+			Mode:  kind.ModeNone,
+		},
 	}
 	node, err := createNode(ctx, createOpts)
 	if err != nil {
@@ -141,7 +147,7 @@ func createNode(ctx context.Context, opts *nodeCreateOpts) (*types.Node, error)
 
 	runOptions := &container.RunContainerInput{
 		Name:   opts.Name, // make hostname match container name
-		Image:  opts.Image,
+		Image:  opts.KindMapping.Image,
 		Labels: containerLabels,
 		// runtime persistent storage
 		// this ensures that E.G. pods, logs etc. are not on the container
@@ -158,6 +164,7 @@ func createNode(ctx context.Context, opts *nodeCreateOpts) (*types.Node, error)
 			"/run": "", // systemd wants a writable /run
 		},
 		IPFamily: opts.IPFamily,
+		KindMode: opts.KindMapping.Mode,
 	}
 	if opts.Role == constants.ControlPlaneNodeRoleValue {
 		runOptions.EnvironmentVars = map[string]string{
@@ -177,7 +184,7 @@ func createNode(ctx context.Context, opts *nodeCreateOpts) (*types.Node, error)
 		return nil, err
 	}
 
-	return types.NewNode(opts.Name, opts.Image, opts.Role), nil
+	return types.NewNode(opts.Name, opts.KindMapping.Image, opts.Role), nil
 }
 
 func generateMountInfo(mounts []v1alpha4.Mount) []container.Mount {
diff --git a/test/infrastructure/docker/internal/docker/kind_manager_test.go b/test/infrastructure/docker/internal/docker/manager_test.go
similarity index 88%
rename from test/infrastructure/docker/internal/docker/kind_manager_test.go
rename to test/infrastructure/docker/internal/docker/manager_test.go
index 58bf2d86e..0cc5b6a5f 100644
--- a/test/infrastructure/docker/internal/docker/kind_manager_test.go
+++ b/test/infrastructure/docker/internal/docker/manager_test.go
@@ -26,6 +26,7 @@ import (
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/test/infrastructure/container"
+	"sigs.k8s.io/cluster-api/test/infrastructure/kind"
 )
 
 func TestCreateNode(t *testing.T) {
@@ -44,12 +45,15 @@ func TestCreateNode(t *testing.T) {
 	}
 	createOpts := &nodeCreateOpts{
 		Name:         "TestName",
-		Image:        "TestImage",
 		ClusterName:  "TestClusterName",
 		Role:         constants.ControlPlaneNodeRoleValue,
 		PortMappings: portMappingsWithAPIServer,
 		Mounts:       []v1alpha4.Mount{},
 		IPFamily:     clusterv1.IPv4IPFamily,
+		KindMapping: kind.Mapping{
+			Image: "TestImage",
+			Mode:  kind.ModeNone, // no impact on the fake runtime.
+		},
 	}
 	_, err := createNode(ctx, createOpts)
 
@@ -76,7 +80,7 @@ func TestCreateControlPlaneNode(t *testing.T) {
 
 	containerRuntime.ResetRunContainerCallLogs()
 	m := Manager{}
-	node, err := m.CreateControlPlaneNode(ctx, "TestName", "TestImage", "TestCluster", "100.100.100.100", 80, []v1alpha4.Mount{}, []v1alpha4.PortMapping{}, make(map[string]string), clusterv1.IPv4IPFamily)
+	node, err := m.CreateControlPlaneNode(ctx, "TestName", "TestCluster", "100.100.100.100", 80, []v1alpha4.Mount{}, []v1alpha4.PortMapping{}, make(map[string]string), clusterv1.IPv4IPFamily, kind.Mapping{Image: "TestImage"})
 
 	g.Expect(err).ShouldNot(HaveOccurred())
 	g.Expect(node.Role()).Should(Equal(constants.ControlPlaneNodeRoleValue))
@@ -99,7 +103,7 @@ func TestCreateWorkerNode(t *testing.T) {
 
 	containerRuntime.ResetRunContainerCallLogs()
 	m := Manager{}
-	node, err := m.CreateWorkerNode(ctx, "TestName", "TestImage", "TestCluster", []v1alpha4.Mount{}, []v1alpha4.PortMapping{}, make(map[string]string), clusterv1.IPv4IPFamily)
+	node, err := m.CreateWorkerNode(ctx, "TestName", "TestCluster", []v1alpha4.Mount{}, []v1alpha4.PortMapping{}, make(map[string]string), clusterv1.IPv4IPFamily, kind.Mapping{Image: "TestImage"})
 
 	g.Expect(err).ShouldNot(HaveOccurred())
 	g.Expect(node.Role()).Should(Equal(constants.WorkerNodeRoleValue))
diff --git a/test/infrastructure/inmemory/internal/cloud/runtime/cache/cache.go b/test/infrastructure/inmemory/internal/cloud/runtime/cache/cache.go
index b78e08534..952d6f93f 100644
--- a/test/infrastructure/inmemory/internal/cloud/runtime/cache/cache.go
+++ b/test/infrastructure/inmemory/internal/cloud/runtime/cache/cache.go
@@ -56,7 +56,6 @@ type Cache interface {
 // Informer forwards events to event handlers.
 type Informer interface {
 	AddEventHandler(handler InformEventHandler) error
-	RemoveEventHandler(handler InformEventHandler) error
 }
 
 // InformEventHandler handle events originated by a source.
diff --git a/test/infrastructure/inmemory/internal/cloud/runtime/cache/client.go b/test/infrastructure/inmemory/internal/cloud/runtime/cache/client.go
index d83047cc1..45ab1e33f 100644
--- a/test/infrastructure/inmemory/internal/cloud/runtime/cache/client.go
+++ b/test/infrastructure/inmemory/internal/cloud/runtime/cache/client.go
@@ -409,8 +409,11 @@ func (c *cache) doTryDeleteLocked(resourceGroup string, tracker *resourceGroupTr
 		delete(tracker.ownedObjects, ownReference{gvk: objGVK, key: objKey})
 	}
 
-	// Set the deletion timestamp if not already set.
-	if obj.GetDeletionTimestamp().IsZero() {
+	// If the object still has finalizers, only set the deletion timestamp if not already set.
+	if len(obj.GetFinalizers()) > 0 {
+		if !obj.GetDeletionTimestamp().IsZero() {
+			return false, nil
+		}
 		if err := c.beforeDelete(resourceGroup, obj); err != nil {
 			return false, apierrors.NewBadRequest(err.Error())
 		}
@@ -421,13 +424,13 @@ func (c *cache) doTryDeleteLocked(resourceGroup string, tracker *resourceGroupTr
 		if err := c.beforeUpdate(resourceGroup, oldObj, obj); err != nil {
 			return false, apierrors.NewBadRequest(err.Error())
 		}
+		// Required to override default beforeUpdate behaviour
+		// that prevent changes to automatically managed fields.
+		obj.SetDeletionTimestamp(&now)
 
 		objects[objKey] = obj
 		c.afterUpdate(resourceGroup, oldObj, obj)
-	}
 
-	// If the object still has finalizers return early.
-	if len(obj.GetFinalizers()) > 0 {
 		return false, nil
 	}
 
diff --git a/test/infrastructure/inmemory/internal/cloud/runtime/cache/client_test.go b/test/infrastructure/inmemory/internal/cloud/runtime/cache/client_test.go
index 5508f644b..786e77526 100644
--- a/test/infrastructure/inmemory/internal/cloud/runtime/cache/client_test.go
+++ b/test/infrastructure/inmemory/internal/cloud/runtime/cache/client_test.go
@@ -629,7 +629,7 @@ func Test_cache_client(t *testing.T) {
 			g.Expect(c.resourceGroups["foo"].objects).To(HaveKey(cloudv1.GroupVersion.WithKind(cloudv1.CloudMachineKind)), "gvk must exist in object tracker for foo")
 			g.Expect(c.resourceGroups["foo"].objects[cloudv1.GroupVersion.WithKind(cloudv1.CloudMachineKind)]).ToNot(HaveKey(types.NamespacedName{Name: "bar"}), "Object bar must not exist in object tracker for foo")
 
-			g.Expect(h.Events()).To(ContainElement("foo, CloudMachine=bar, Deleted"))
+			g.Expect(h.Events()).ToNot(ContainElement("foo, CloudMachine=bar, Deleted"))
 		})
 
 		t.Run("delete with finalizers", func(t *testing.T) {
@@ -760,11 +760,6 @@ func (i *fakeInformer) AddEventHandler(handler InformEventHandler) error {
 	return nil
 }
 
-func (i *fakeInformer) RemoveEventHandler(_ InformEventHandler) error {
-	i.handler = nil
-	return nil
-}
-
 func (i *fakeInformer) InformCreate(resourceGroup string, obj client.Object) {
 	i.handler.OnCreate(resourceGroup, obj)
 }
diff --git a/test/infrastructure/inmemory/internal/cloud/runtime/cache/informer.go b/test/infrastructure/inmemory/internal/cloud/runtime/cache/informer.go
index 18fe4ddda..cfe88aca1 100644
--- a/test/infrastructure/inmemory/internal/cloud/runtime/cache/informer.go
+++ b/test/infrastructure/inmemory/internal/cloud/runtime/cache/informer.go
@@ -38,17 +38,6 @@ func (i *informer) AddEventHandler(handler InformEventHandler) error {
 	return nil
 }
 
-func (i *informer) RemoveEventHandler(handler InformEventHandler) error {
-	i.lock.Lock()
-	defer i.lock.Unlock()
-	for j, h := range i.handlers {
-		if h == handler {
-			i.handlers = append(i.handlers[:j], i.handlers[j+1:]...)
-		}
-	}
-	return nil
-}
-
 func (c *cache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
 	gvk, err := apiutil.GVKForObject(obj, c.scheme)
 	if err != nil {
diff --git a/test/infrastructure/inmemory/internal/cloud/runtime/controller/controller_test.go b/test/infrastructure/inmemory/internal/cloud/runtime/controller/controller_test.go
index 6d6068415..6951c2993 100644
--- a/test/infrastructure/inmemory/internal/cloud/runtime/controller/controller_test.go
+++ b/test/infrastructure/inmemory/internal/cloud/runtime/controller/controller_test.go
@@ -148,11 +148,6 @@ func (i *fakeInformer) AddEventHandler(handler ccache.InformEventHandler) error
 	return nil
 }
 
-func (i *fakeInformer) RemoveEventHandler(_ ccache.InformEventHandler) error {
-	i.handler = nil
-	return nil
-}
-
 func (i *fakeInformer) InformCreate(resourceGroup string, obj client.Object) {
 	i.handler.OnCreate(resourceGroup, obj)
 }
diff --git a/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller.go b/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller.go
index 5786c0459..4dd00edb9 100644
--- a/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller.go
+++ b/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller.go
@@ -32,7 +32,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/klog/v2"
-	"k8s.io/utils/pointer"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -264,14 +263,12 @@ func (r *InMemoryMachineReconciler) reconcileNormalCloudMachine(ctx context.Cont
 		x := inMemoryMachine.Spec.Behaviour.VM.Provisioning
 
 		provisioningDuration = x.StartupDuration.Duration
-		if x.StartupJitter != "" {
-			jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
-			if err != nil {
-				return ctrl.Result{}, errors.Wrapf(err, "failed to parse VM's StartupJitter")
-			}
-			if jitter > 0.0 {
-				provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
-			}
+		jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
+		if err != nil {
+			return ctrl.Result{}, errors.Wrapf(err, "failed to parse VM's StartupJitter")
+		}
+		if jitter > 0.0 {
+			provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
 		}
 	}
 
@@ -284,8 +281,6 @@ func (r *InMemoryMachineReconciler) reconcileNormalCloudMachine(ctx context.Cont
 
 	// TODO: consider if to surface VM provisioned also on the cloud machine (currently it surfaces only on the inMemoryMachine)
 
-	inMemoryMachine.Spec.ProviderID = pointer.String(calculateProviderID(inMemoryMachine))
-	inMemoryMachine.Status.Ready = true
 	conditions.MarkTrue(inMemoryMachine, infrav1.VMProvisionedCondition)
 	return ctrl.Result{}, nil
 }
@@ -302,14 +297,12 @@ func (r *InMemoryMachineReconciler) reconcileNormalNode(ctx context.Context, clu
 		x := inMemoryMachine.Spec.Behaviour.Node.Provisioning
 
 		provisioningDuration = x.StartupDuration.Duration
-		if x.StartupJitter != "" {
-			jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
-			if err != nil {
-				return ctrl.Result{}, errors.Wrapf(err, "failed to parse node's StartupJitter")
-			}
-			if jitter > 0.0 {
-				provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
-			}
+		jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
+		if err != nil {
+			return ctrl.Result{}, errors.Wrapf(err, "failed to parse node's StartupJitter")
+		}
+		if jitter > 0.0 {
+			provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
 		}
 	}
 
@@ -332,7 +325,7 @@ func (r *InMemoryMachineReconciler) reconcileNormalNode(ctx context.Context, clu
 			Name: inMemoryMachine.Name,
 		},
 		Spec: corev1.NodeSpec{
-			ProviderID: calculateProviderID(inMemoryMachine),
+			ProviderID: fmt.Sprintf("in-memory://%s", inMemoryMachine.Name),
 		},
 		Status: corev1.NodeStatus{
 			Conditions: []corev1.NodeCondition{
@@ -362,14 +355,13 @@ func (r *InMemoryMachineReconciler) reconcileNormalNode(ctx context.Context, clu
 		}
 	}
 
+	inMemoryMachine.Spec.ProviderID = &node.Spec.ProviderID
+	inMemoryMachine.Status.Ready = true
+
 	conditions.MarkTrue(inMemoryMachine, infrav1.NodeProvisionedCondition)
 	return ctrl.Result{}, nil
 }
 
-func calculateProviderID(inMemoryMachine *infrav1.InMemoryMachine) string {
-	return fmt.Sprintf("in-memory://%s", inMemoryMachine.Name)
-}
-
 func (r *InMemoryMachineReconciler) reconcileNormalETCD(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine, inMemoryMachine *infrav1.InMemoryMachine) (ctrl.Result, error) {
 	// No-op if the machine is not a control plane machine.
 	if !util.IsControlPlaneMachine(machine) {
@@ -387,14 +379,12 @@ func (r *InMemoryMachineReconciler) reconcileNormalETCD(ctx context.Context, clu
 		x := inMemoryMachine.Spec.Behaviour.Etcd.Provisioning
 
 		provisioningDuration = x.StartupDuration.Duration
-		if x.StartupJitter != "" {
-			jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
-			if err != nil {
-				return ctrl.Result{}, errors.Wrapf(err, "failed to parse etcd's StartupJitter")
-			}
-			if jitter > 0.0 {
-				provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
-			}
+		jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
+		if err != nil {
+			return ctrl.Result{}, errors.Wrapf(err, "failed to parse etcd's StartupJitter")
+		}
+		if jitter > 0.0 {
+			provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
 		}
 	}
 
@@ -504,14 +494,12 @@ func (r *InMemoryMachineReconciler) reconcileNormalAPIServer(ctx context.Context
 		x := inMemoryMachine.Spec.Behaviour.APIServer.Provisioning
 
 		provisioningDuration = x.StartupDuration.Duration
-		if x.StartupJitter != "" {
-			jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
-			if err != nil {
-				return ctrl.Result{}, errors.Wrapf(err, "failed to parse API server's StartupJitter")
-			}
-			if jitter > 0.0 {
-				provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
-			}
+		jitter, err := strconv.ParseFloat(x.StartupJitter, 64)
+		if err != nil {
+			return ctrl.Result{}, errors.Wrapf(err, "failed to parse API server's StartupJitter")
+		}
+		if jitter > 0.0 {
+			provisioningDuration += time.Duration(rand.Float64() * jitter * float64(provisioningDuration)) //nolint:gosec // Intentionally using a weak random number generator here.
 		}
 	}
 
diff --git a/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller_test.go b/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller_test.go
index 65d7c7e57..0b5344545 100644
--- a/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller_test.go
+++ b/test/infrastructure/inmemory/internal/controllers/inmemorymachine_controller_test.go
@@ -314,14 +314,8 @@ func TestReconcileNormalEtcd(t *testing.T) {
 		manager := cmanager.New(scheme)
 
 		host := "127.0.0.1"
-		wcmux, err := server.NewWorkloadClustersMux(manager, host, server.CustomPorts{
-			// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-			MinPort:   server.DefaultMinPort + 1000,
-			MaxPort:   server.DefaultMinPort + 1099,
-			DebugPort: server.DefaultDebugPort + 10,
-		})
-		g.Expect(err).ToNot(HaveOccurred())
-		_, err = wcmux.InitWorkloadClusterListener(klog.KObj(cluster).String())
+		wcmux := server.NewWorkloadClustersMux(manager, host)
+		_, err := wcmux.InitWorkloadClusterListener(klog.KObj(cluster).String())
 		g.Expect(err).ToNot(HaveOccurred())
 
 		r := InMemoryMachineReconciler{
@@ -442,14 +436,8 @@ func TestReconcileNormalApiServer(t *testing.T) {
 		manager := cmanager.New(scheme)
 
 		host := "127.0.0.1"
-		wcmux, err := server.NewWorkloadClustersMux(manager, host, server.CustomPorts{
-			// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-			MinPort:   server.DefaultMinPort + 1100,
-			MaxPort:   server.DefaultMinPort + 1199,
-			DebugPort: server.DefaultDebugPort + 11,
-		})
-		g.Expect(err).ToNot(HaveOccurred())
-		_, err = wcmux.InitWorkloadClusterListener(klog.KObj(cluster).String())
+		wcmux := server.NewWorkloadClustersMux(manager, host)
+		_, err := wcmux.InitWorkloadClusterListener(klog.KObj(cluster).String())
 		g.Expect(err).ToNot(HaveOccurred())
 
 		r := InMemoryMachineReconciler{
diff --git a/test/infrastructure/inmemory/internal/server/api/handler.go b/test/infrastructure/inmemory/internal/server/api/handler.go
index 0516ba7eb..8a6cf32b3 100644
--- a/test/infrastructure/inmemory/internal/server/api/handler.go
+++ b/test/infrastructure/inmemory/internal/server/api/handler.go
@@ -73,16 +73,14 @@ func NewAPIServerHandler(manager cmanager.Manager, log logr.Logger, resolver Res
 
 	// CRUD endpoints (global objects)
 	ws.Route(ws.POST("/api/v1/{resource}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Create))
-	ws.Route(ws.GET("/api/v1/{resource}").If(isList).To(apiServer.apiV1List))
-	ws.Route(ws.GET("/api/v1/{resource}").If(isWatch).To(apiServer.apiV1Watch))
+	ws.Route(ws.GET("/api/v1/{resource}").To(apiServer.apiV1List))
 	ws.Route(ws.GET("/api/v1/{resource}/{name}").To(apiServer.apiV1Get))
 	ws.Route(ws.PUT("/api/v1/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Update))
 	ws.Route(ws.PATCH("/api/v1/{resource}/{name}").Consumes(string(types.MergePatchType), string(types.StrategicMergePatchType)).To(apiServer.apiV1Patch))
 	ws.Route(ws.DELETE("/api/v1/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf, runtime.ContentTypeJSON).To(apiServer.apiV1Delete))
 
 	ws.Route(ws.POST("/apis/{group}/{version}/{resource}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Create))
-	ws.Route(ws.GET("/apis/{group}/{version}/{resource}").If(isList).To(apiServer.apiV1List))
-	ws.Route(ws.GET("/apis/{group}/{version}/{resource}").If(isWatch).To(apiServer.apiV1Watch))
+	ws.Route(ws.GET("/apis/{group}/{version}/{resource}").To(apiServer.apiV1List))
 	ws.Route(ws.GET("/apis/{group}/{version}/{resource}/{name}").To(apiServer.apiV1Get))
 	ws.Route(ws.PUT("/apis/{group}/{version}/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Update))
 	ws.Route(ws.PATCH("/apis/{group}/{version}/{resource}/{name}").Consumes(string(types.MergePatchType), string(types.StrategicMergePatchType)).To(apiServer.apiV1Patch))
@@ -90,16 +88,14 @@ func NewAPIServerHandler(manager cmanager.Manager, log logr.Logger, resolver Res
 
 	// CRUD endpoints (namespaced objects)
 	ws.Route(ws.POST("/api/v1/namespaces/{namespace}/{resource}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Create))
-	ws.Route(ws.GET("/api/v1/namespaces/{namespace}/{resource}").If(isList).To(apiServer.apiV1List))
-	ws.Route(ws.GET("/api/v1/namespaces/{namespace}/{resource}").If(isWatch).To(apiServer.apiV1Watch))
+	ws.Route(ws.GET("/api/v1/namespaces/{namespace}/{resource}").To(apiServer.apiV1List))
 	ws.Route(ws.GET("/api/v1/namespaces/{namespace}/{resource}/{name}").To(apiServer.apiV1Get))
 	ws.Route(ws.PUT("/api/v1/namespaces/{namespace}/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Update))
 	ws.Route(ws.PATCH("/api/v1/namespaces/{namespace}/{resource}/{name}").Consumes(string(types.MergePatchType), string(types.StrategicMergePatchType)).To(apiServer.apiV1Patch))
 	ws.Route(ws.DELETE("/api/v1/namespaces/{namespace}/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf, runtime.ContentTypeJSON).To(apiServer.apiV1Delete))
 
 	ws.Route(ws.POST("/apis/{group}/{version}/namespaces/{namespace}/{resource}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Create))
-	ws.Route(ws.GET("/apis/{group}/{version}/namespaces/{namespace}/{resource}").If(isList).To(apiServer.apiV1List))
-	ws.Route(ws.GET("/apis/{group}/{version}/namespaces/{namespace}/{resource}").If(isWatch).To(apiServer.apiV1Watch))
+	ws.Route(ws.GET("/apis/{group}/{version}/namespaces/{namespace}/{resource}").To(apiServer.apiV1List))
 	ws.Route(ws.GET("/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}").To(apiServer.apiV1Get))
 	ws.Route(ws.PUT("/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}").Consumes(runtime.ContentTypeProtobuf).To(apiServer.apiV1Update))
 	ws.Route(ws.PATCH("/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}").Consumes(string(types.MergePatchType), string(types.StrategicMergePatchType)).To(apiServer.apiV1Patch))
@@ -254,29 +250,6 @@ func (h *apiServerHandler) apiV1List(req *restful.Request, resp *restful.Respons
 	}
 }
 
-func (h *apiServerHandler) apiV1Watch(req *restful.Request, resp *restful.Response) {
-	// Gets the resource group the request targets (the resolver is aware of the mapping host<->resourceGroup)
-	resourceGroup, err := h.resourceGroupResolver(req.Request.Host)
-	if err != nil {
-		_ = resp.WriteErrorString(http.StatusInternalServerError, err.Error())
-		return
-	}
-
-	// Maps the requested resource to a gvk.
-	gvk, err := requestToGVK(req)
-	if err != nil {
-		_ = resp.WriteErrorString(http.StatusInternalServerError, err.Error())
-		return
-	}
-
-	// If the request is a Watch handle it using watchForResource.
-	err = h.watchForResource(req, resp, resourceGroup, *gvk)
-	if err != nil {
-		_ = resp.WriteErrorString(http.StatusInternalServerError, err.Error())
-		return
-	}
-}
-
 func (h *apiServerHandler) apiV1Get(req *restful.Request, resp *restful.Response) {
 	ctx := req.Request.Context()
 
@@ -556,13 +529,3 @@ func getAPIResourceList(req *restful.Request) *metav1.APIResourceList {
 	}
 	return corev1APIResourceList
 }
-
-// isWatch is true if the request contains `watch="true"` as a query parameter.
-func isWatch(req *http.Request) bool {
-	return req.URL.Query().Get("watch") == "true"
-}
-
-// isList is true if the request does not have `watch="true` as a query parameter.
-func isList(req *http.Request) bool {
-	return !isWatch(req)
-}
diff --git a/test/infrastructure/inmemory/internal/server/api/watch.go b/test/infrastructure/inmemory/internal/server/api/watch.go
deleted file mode 100644
index 136bd5ee5..000000000
--- a/test/infrastructure/inmemory/internal/server/api/watch.go
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package api
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"time"
-
-	"github.com/emicklei/go-restful/v3"
-	"github.com/pkg/errors"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/watch"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-// Event records a lifecycle event for a Kubernetes object.
-type Event struct {
-	Type   watch.EventType `json:"type,omitempty"`
-	Object runtime.Object  `json:"object,omitempty"`
-}
-
-// WatchEventDispatcher dispatches events for a single resourceGroup.
-type WatchEventDispatcher struct {
-	resourceGroup string
-	events        chan *Event
-}
-
-// OnCreate dispatches Create events.
-func (m *WatchEventDispatcher) OnCreate(resourceGroup string, o client.Object) {
-	if resourceGroup != m.resourceGroup {
-		return
-	}
-	m.events <- &Event{
-		Type:   watch.Added,
-		Object: o,
-	}
-}
-
-// OnUpdate dispatches Update events.
-func (m *WatchEventDispatcher) OnUpdate(resourceGroup string, _, o client.Object) {
-	if resourceGroup != m.resourceGroup {
-		return
-	}
-	m.events <- &Event{
-		Type:   watch.Modified,
-		Object: o,
-	}
-}
-
-// OnDelete dispatches Delete events.
-func (m *WatchEventDispatcher) OnDelete(resourceGroup string, o client.Object) {
-	if resourceGroup != m.resourceGroup {
-		return
-	}
-	m.events <- &Event{
-		Type:   watch.Deleted,
-		Object: o,
-	}
-}
-
-// OnGeneric dispatches Generic events.
-func (m *WatchEventDispatcher) OnGeneric(resourceGroup string, o client.Object) {
-	if resourceGroup != m.resourceGroup {
-		return
-	}
-	m.events <- &Event{
-		Type:   "GENERIC",
-		Object: o,
-	}
-}
-
-func (h *apiServerHandler) watchForResource(req *restful.Request, resp *restful.Response, resourceGroup string, gvk schema.GroupVersionKind) (reterr error) {
-	ctx := req.Request.Context()
-	queryTimeout := req.QueryParameter("timeoutSeconds")
-	c := h.manager.GetCache()
-	i, err := c.GetInformerForKind(ctx, gvk)
-	if err != nil {
-		return err
-	}
-	h.log.Info(fmt.Sprintf("Serving Watch for %v", req.Request.URL))
-	// With an unbuffered event channel RemoveEventHandler could be blocked because it requires a lock on the informer.
-	// When Run stops reading from the channel the informer could be blocked with an unbuffered chanel and then RemoveEventHandler never goes through.
-	events := make(chan *Event, 10)
-	watcher := &WatchEventDispatcher{
-		resourceGroup: resourceGroup,
-		events:        events,
-	}
-
-	if err := i.AddEventHandler(watcher); err != nil {
-		return err
-	}
-
-	// Defer cleanup which removes the event handler and ensures the channel is empty of events.
-	defer func() {
-		reterr = i.RemoveEventHandler(watcher)
-		// Doing this to ensure the channel is empty.
-	L:
-		for {
-			select {
-			case <-events:
-			default:
-				break L
-			}
-		}
-	}()
-
-	return watcher.Run(ctx, queryTimeout, resp)
-}
-
-// Run serves a series of encoded events via HTTP with Transfer-Encoding: chunked.
-func (m *WatchEventDispatcher) Run(ctx context.Context, timeout string, w http.ResponseWriter) error {
-	flusher, ok := w.(http.Flusher)
-	if !ok {
-		return errors.New("can't start Watch: can't get http.Flusher")
-	}
-	resp, ok := w.(*restful.Response)
-	if !ok {
-		return errors.New("can't start Watch: can't get restful.Response")
-	}
-	w.Header().Set("Transfer-Encoding", "chunked")
-	w.WriteHeader(http.StatusOK)
-	flusher.Flush()
-
-	timeoutTimer, seconds, err := setTimer(timeout)
-	if err != nil {
-		return errors.Wrapf(err, "can't start Watch: could not set timeout")
-	}
-
-	ctx, cancel := context.WithTimeout(ctx, seconds)
-	defer cancel()
-	defer timeoutTimer.Stop()
-	for {
-		select {
-		case <-ctx.Done():
-			return nil
-		case <-timeoutTimer.C:
-			return nil
-		case event, ok := <-m.events:
-			if !ok {
-				// End of results.
-				return nil
-			}
-			if err := resp.WriteEntity(event); err != nil {
-				_ = resp.WriteErrorString(http.StatusInternalServerError, err.Error())
-			}
-			if len(m.events) == 0 {
-				flusher.Flush()
-			}
-		}
-	}
-}
-
-// setTimer creates a time.Timer with the passed `timeout` or a default timeout of 120 seconds if `timeout` is empty.
-func setTimer(timeout string) (*time.Timer, time.Duration, error) {
-	var defaultTimeout = 120 * time.Second
-	if timeout == "" {
-		t := time.NewTimer(defaultTimeout)
-		return t, defaultTimeout, nil
-	}
-	seconds, err := time.ParseDuration(fmt.Sprintf("%ss", timeout))
-	if err != nil {
-		return nil, 0, errors.Wrapf(err, "Could not parse request timeout %s", timeout)
-	}
-	t := time.NewTimer(seconds)
-	return t, seconds, nil
-}
diff --git a/test/infrastructure/inmemory/internal/server/certs.go b/test/infrastructure/inmemory/internal/server/certs.go
index ab7f85301..64b3b3477 100644
--- a/test/infrastructure/inmemory/internal/server/certs.go
+++ b/test/infrastructure/inmemory/internal/server/certs.go
@@ -26,19 +26,13 @@ import (
 	"sigs.k8s.io/cluster-api/util/certs"
 )
 
-var key *rsa.PrivateKey
-
-func init() {
-	// Create a private key only once, since this is a slow operation and it is ok
-	// to reuse it for all the certificates in a test provider.
-	var err error
-	key, err = certs.NewPrivateKey()
+// newCertAndKey builds a new cert and key signed with the given CA and with the given config.
+func newCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *certs.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
+	key, err := certs.NewPrivateKey()
 	if err != nil {
-		panic(errors.Wrap(err, "unable to create private key").Error())
+		return nil, nil, errors.Wrap(err, "unable to create private key")
 	}
-}
 
-func newCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *certs.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
 	cert, err := config.NewSignedCert(key, caCert, caKey)
 	if err != nil {
 		return nil, nil, errors.Wrap(err, "unable to create certificate")
diff --git a/test/infrastructure/inmemory/internal/server/listener.go b/test/infrastructure/inmemory/internal/server/listener.go
index 8de7bf0d7..0bdfc16d5 100644
--- a/test/infrastructure/inmemory/internal/server/listener.go
+++ b/test/infrastructure/inmemory/internal/server/listener.go
@@ -114,7 +114,7 @@ func (s *WorkloadClusterListener) RESTConfig() (*rest.Config, error) {
 }
 
 // GetClient returns a client for a WorkloadClusterListener.
-func (s *WorkloadClusterListener) GetClient() (client.WithWatch, error) {
+func (s *WorkloadClusterListener) GetClient() (client.Client, error) {
 	restConfig, err := s.RESTConfig()
 	if err != nil {
 		return nil, err
@@ -130,7 +130,7 @@ func (s *WorkloadClusterListener) GetClient() (client.WithWatch, error) {
 		return nil, err
 	}
 
-	c, err := client.NewWithWatch(restConfig, client.Options{Scheme: s.scheme, Mapper: mapper})
+	c, err := client.New(restConfig, client.Options{Scheme: s.scheme, Mapper: mapper})
 	if err != nil {
 		return nil, err
 	}
diff --git a/test/infrastructure/inmemory/internal/server/mux.go b/test/infrastructure/inmemory/internal/server/mux.go
index 361872d82..c691db7e3 100644
--- a/test/infrastructure/inmemory/internal/server/mux.go
+++ b/test/infrastructure/inmemory/internal/server/mux.go
@@ -32,9 +32,7 @@ import (
 	"github.com/pkg/errors"
 	"golang.org/x/net/http2"
 	"golang.org/x/net/http2/h2c"
-	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/klog/v2"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
@@ -46,52 +44,15 @@ import (
 )
 
 const (
-	// DefaultDebugPort default debug port of the workload clusters mux.
-	DefaultDebugPort = 19000
+	debugPort = 19000
 
 	// This range allows for 4k clusters, which is 4 times the goal we have in mind for
 	// the first iteration of stress tests.
 
-	// DefaultMinPort default min port of the workload clusters mux.
-	DefaultMinPort = 20000
-	// DefaultMaxPort default max port of the workload clusters mux.
-	DefaultMaxPort = 24000
+	minPort = 20000
+	maxPort = 24000
 )
 
-// WorkloadClustersMuxOption define an option for the WorkloadClustersMux creation.
-type WorkloadClustersMuxOption interface {
-	Apply(*WorkloadClustersMuxOptions)
-}
-
-// WorkloadClustersMuxOptions are options for the workload clusters mux.
-type WorkloadClustersMuxOptions struct {
-	MinPort   int
-	MaxPort   int
-	DebugPort int
-}
-
-// ApplyOptions applies WorkloadClustersMuxOption to the current WorkloadClustersMuxOptions.
-func (o *WorkloadClustersMuxOptions) ApplyOptions(opts []WorkloadClustersMuxOption) *WorkloadClustersMuxOptions {
-	for _, opt := range opts {
-		opt.Apply(o)
-	}
-	return o
-}
-
-// CustomPorts allows to customize the ports used by the workload clusters mux.
-type CustomPorts struct {
-	MinPort   int
-	MaxPort   int
-	DebugPort int
-}
-
-// Apply applies this configuration to the given WorkloadClustersMuxOptions.
-func (c CustomPorts) Apply(options *WorkloadClustersMuxOptions) {
-	options.MinPort = c.MinPort
-	options.MaxPort = c.MaxPort
-	options.DebugPort = c.DebugPort
-}
-
 // WorkloadClustersMux implements a server that handles requests for multiple workload clusters.
 // Each workload clusters will get its own listener, serving on a dedicated port, eg.
 // wkl-cluster-1 >> :20000, wkl-cluster-2 >> :20001 etc.
@@ -116,19 +77,12 @@ type WorkloadClustersMux struct {
 }
 
 // NewWorkloadClustersMux returns a WorkloadClustersMux that handles requests for multiple workload clusters.
-func NewWorkloadClustersMux(manager cmanager.Manager, host string, opts ...WorkloadClustersMuxOption) (*WorkloadClustersMux, error) {
-	options := WorkloadClustersMuxOptions{
-		MinPort:   DefaultMinPort,
-		MaxPort:   DefaultMaxPort,
-		DebugPort: DefaultDebugPort,
-	}
-	options.ApplyOptions(opts)
-
+func NewWorkloadClustersMux(manager cmanager.Manager, host string) *WorkloadClustersMux {
 	m := &WorkloadClustersMux{
 		host:                      host,
-		minPort:                   options.MinPort,
-		maxPort:                   options.MaxPort,
-		portIndex:                 options.MinPort,
+		minPort:                   minPort,
+		maxPort:                   maxPort,
+		portIndex:                 minPort,
 		manager:                   manager,
 		workloadClusterListeners:  map[string]*WorkloadClusterListener{},
 		workloadClusterNameByHost: map[string]string{},
@@ -153,13 +107,10 @@ func NewWorkloadClustersMux(manager cmanager.Manager, host string, opts ...Workl
 	m.debugServer = http.Server{
 		Handler: api.NewDebugHandler(manager, m.log, m),
 	}
-	l, err := net.Listen("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", options.DebugPort)))
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to create listener for workload cluster mux")
-	}
+	l, _ := net.Listen("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", debugPort)))
 	go func() { _ = m.debugServer.Serve(l) }()
 
-	return m, nil
+	return m
 }
 
 // mixedHandler returns an handler that can serve either API server calls or etcd calls.
@@ -318,107 +269,81 @@ func (m *WorkloadClustersMux) initWorkloadClusterListenerWithPortLocked(wclName
 // When the first API server instance is added the serving certificates and the admin certificate
 // for tests are generated, and the listener is started.
 func (m *WorkloadClustersMux) AddAPIServer(wclName, podName string, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
-	// Start server
-	// Note: It is important that we unlock once the server is started. Because otherwise the server
-	// doesn't work yet as GetCertificate (which is required for the tls handshake) also requires the lock.
-	var startServerErr error
-	var wcl *WorkloadClusterListener
-	err := func() error {
-		m.lock.Lock()
-		defer m.lock.Unlock()
-
-		var ok bool
-		wcl, ok = m.workloadClusterListeners[wclName]
-		if !ok {
-			return errors.Errorf("workloadClusterListener with name %s must be initialized before adding an APIserver", wclName)
-		}
-		wcl.apiServers.Insert(podName)
-		m.log.Info("APIServer instance added to workloadClusterListener", "listenerName", wclName, "address", wcl.Address(), "podName", podName)
-
-		// TODO: check if cert/key are already set, they should match
-		wcl.apiServerCaCertificate = caCert
-		wcl.apiServerCaKey = caKey
-
-		// Generate Serving certificates for the API server instance
-		// NOTE: There is only one server certificate for all API server instances (kubeadm
-		// instead creates one for each API server pod). We don't need this because we are
-		// accessing all API servers via the same endpoint.
-		if wcl.apiServerServingCertificate == nil {
-			config := apiServerCertificateConfig(wcl.host)
-			cert, key, err := newCertAndKey(caCert, caKey, config)
-			if err != nil {
-				return errors.Wrapf(err, "failed to create serving certificate for API server %s", podName)
-			}
-
-			certificate, err := tls.X509KeyPair(certs.EncodeCertPEM(cert), certs.EncodePrivateKeyPEM(key))
-			if err != nil {
-				return errors.Wrapf(err, "failed to create X509KeyPair for API server %s", podName)
-			}
-			wcl.apiServerServingCertificate = &certificate
-		}
+	m.lock.Lock()
+	defer m.lock.Unlock()
 
-		// Generate admin certificates to be used for accessing the API server.
-		// NOTE: this is used for tests because CAPI creates its own.
-		if wcl.adminCertificate == nil {
-			config := adminClientCertificateConfig()
-			cert, key, err := newCertAndKey(caCert, caKey, config)
-			if err != nil {
-				return errors.Wrapf(err, "failed to create admin certificate for API server %s", podName)
-			}
-
-			wcl.adminCertificate = cert
-			wcl.adminKey = key
-		}
+	wcl, ok := m.workloadClusterListeners[wclName]
+	if !ok {
+		return errors.Errorf("workloadClusterListener with name %s must be initialized before adding an APIserver", wclName)
+	}
+	wcl.apiServers.Insert(podName)
+	m.log.Info("APIServer instance added to workloadClusterListener", "listenerName", wclName, "address", wcl.Address(), "podName", podName)
 
-		// Start the listener for the API server.
-		// NOTE: There is only one listener for all API server instances; the same listener will act
-		// as a port forward target too.
-		if wcl.listener != nil {
-			return nil
-		}
+	// TODO: check if cert/key are already set, they should match
+	wcl.apiServerCaCertificate = caCert
+	wcl.apiServerCaKey = caKey
 
-		l, err := net.Listen("tcp", wcl.HostPort())
+	// Generate Serving certificates for the API server instance
+	// NOTE: There is only one server certificate for all API server instances (kubeadm
+	// instead creates one for each API server pod). We don't need this because we are
+	// accessing all API servers via the same endpoint.
+	if wcl.apiServerServingCertificate == nil {
+		config := apiServerCertificateConfig(wcl.host)
+		cert, key, err := newCertAndKey(caCert, caKey, config)
 		if err != nil {
-			return errors.Wrapf(err, "failed to start WorkloadClusterListener %s, %s", wclName, wcl.HostPort())
+			return errors.Wrapf(err, "failed to create serving certificate for API server %s", podName)
 		}
-		wcl.listener = l
 
-		go func() {
-			if startServerErr = m.muxServer.ServeTLS(wcl.listener, "", ""); startServerErr != nil && !errors.Is(startServerErr, http.ErrServerClosed) {
-				m.log.Error(startServerErr, "Failed to start WorkloadClusterListener", "listenerName", wclName, "address", wcl.Address())
-			}
-		}()
-		return nil
-	}()
-	if err != nil {
-		return errors.Wrapf(err, "error starting server")
+		certificate, err := tls.X509KeyPair(certs.EncodeCertPEM(cert), certs.EncodePrivateKeyPEM(key))
+		if err != nil {
+			return errors.Wrapf(err, "failed to create X509KeyPair for API server %s", podName)
+		}
+		wcl.apiServerServingCertificate = &certificate
 	}
 
-	// Wait until the sever is working.
-	var pollErr error
-	err = wait.PollUntilContextTimeout(context.TODO(), 10*time.Millisecond, 1*time.Second, true, func(ctx context.Context) (done bool, err error) {
-		d := &net.Dialer{Timeout: 50 * time.Millisecond}
-		conn, err := tls.DialWithDialer(d, "tcp", wcl.HostPort(), &tls.Config{
-			InsecureSkipVerify: true, //nolint:gosec // config is used to connect to our own port.
-		})
+	// Generate admin certificates to be used for accessing the API server.
+	// NOTE: this is used for tests because CAPI creates its own.
+	if wcl.adminCertificate == nil {
+		config := adminClientCertificateConfig()
+		cert, key, err := newCertAndKey(caCert, caKey, config)
 		if err != nil {
-			pollErr = fmt.Errorf("server is not reachable: %w", err)
-			return false, nil
+			return errors.Wrapf(err, "failed to create admin certificate for API server %s", podName)
 		}
 
-		if err := conn.Close(); err != nil {
-			pollErr = fmt.Errorf("server is not reachable: closing connection: %w", err)
-			return false, nil
-		}
+		wcl.adminCertificate = cert
+		wcl.adminKey = key
+	}
 
-		return true, nil
-	})
+	// Start the listener for the API server.
+	// NOTE: There is only one listener for all API server instances; the same listener will act
+	// as a port forward target too.
+	if wcl.listener != nil {
+		return nil
+	}
+
+	l, err := net.Listen("tcp", wcl.HostPort())
 	if err != nil {
-		return kerrors.NewAggregate([]error{err, pollErr})
+		return errors.Wrapf(err, "failed to start WorkloadClusterListener %s, %s", wclName, wcl.HostPort())
 	}
+	wcl.listener = l
+
+	var startErr error
+	startCh := make(chan struct{})
+	go func() {
+		startCh <- struct{}{}
+		if err := m.muxServer.ServeTLS(wcl.listener, "", ""); err != nil && !errors.Is(err, http.ErrServerClosed) {
+			startErr = err
+			m.log.Error(startErr, "Failed to start WorkloadClusterListener", "listenerName", wclName, "address", wcl.Address())
+		}
+	}()
+
+	<-startCh
+	// TODO: Try to make this race condition free e.g. by checking the listener is answering.
+	// There is no guarantee ServeTLS was called after we received something on the startCh.
+	time.Sleep(100 * time.Millisecond)
 
-	if startServerErr != nil {
-		return startServerErr
+	if startErr != nil {
+		return startErr
 	}
 
 	m.log.Info("WorkloadClusterListener successfully started", "listenerName", wclName, "address", wcl.Address())
diff --git a/test/infrastructure/inmemory/internal/server/mux_test.go b/test/infrastructure/inmemory/internal/server/mux_test.go
index 1d45eda41..fab13f1a6 100644
--- a/test/infrastructure/inmemory/internal/server/mux_test.go
+++ b/test/infrastructure/inmemory/internal/server/mux_test.go
@@ -60,20 +60,13 @@ func init() {
 }
 
 func TestMux(t *testing.T) {
-	t.Parallel()
 	g := NewWithT(t)
 
 	manager := cmanager.New(scheme)
 
 	wcl := "workload-cluster"
 	host := "127.0.0.1" //nolint:goconst
-	wcmux, err := NewWorkloadClustersMux(manager, host, CustomPorts{
-		// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-		MinPort:   DefaultMinPort,
-		MaxPort:   DefaultMinPort + 99,
-		DebugPort: DefaultDebugPort,
-	})
-	g.Expect(err).ToNot(HaveOccurred())
+	wcmux := NewWorkloadClustersMux(manager, host)
 
 	listener, err := wcmux.InitWorkloadClusterListener(wcl)
 	g.Expect(err).ToNot(HaveOccurred())
@@ -122,15 +115,9 @@ func TestMux(t *testing.T) {
 }
 
 func TestAPI_corev1_CRUD(t *testing.T) {
-	t.Parallel()
 	g := NewWithT(t)
 
-	wcmux, c := setupWorkloadClusterListener(g, CustomPorts{
-		// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-		MinPort:   DefaultMinPort + 100,
-		MaxPort:   DefaultMinPort + 199,
-		DebugPort: DefaultDebugPort + 1,
-	})
+	wcmux, c := setupWorkloadClusterListener(g)
 
 	// create
 
@@ -184,15 +171,9 @@ func TestAPI_corev1_CRUD(t *testing.T) {
 }
 
 func TestAPI_rbacv1_CRUD(t *testing.T) {
-	t.Parallel()
 	g := NewWithT(t)
 
-	wcmux, c := setupWorkloadClusterListener(g, CustomPorts{
-		// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-		MinPort:   DefaultMinPort + 200,
-		MaxPort:   DefaultMinPort + 299,
-		DebugPort: DefaultDebugPort + 2,
-	})
+	wcmux, c := setupWorkloadClusterListener(g)
 
 	// create
 
@@ -233,19 +214,12 @@ func TestAPI_rbacv1_CRUD(t *testing.T) {
 }
 
 func TestAPI_PortForward(t *testing.T) {
-	t.Parallel()
 	g := NewWithT(t)
 	manager := cmanager.New(scheme)
 
 	// TODO: deduplicate this setup code with the test above
 	host := "127.0.0.1"
-	wcmux, err := NewWorkloadClustersMux(manager, host, CustomPorts{
-		// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-		MinPort:   DefaultMinPort + 300,
-		MaxPort:   DefaultMinPort + 399,
-		DebugPort: DefaultDebugPort + 3,
-	})
-	g.Expect(err).ToNot(HaveOccurred())
+	wcmux := NewWorkloadClustersMux(manager, host)
 
 	// InfraCluster controller >> when "creating the load balancer"
 	wcl1 := "workload-cluster1"
@@ -367,110 +341,11 @@ func TestAPI_PortForward(t *testing.T) {
 	g.Expect(err).ToNot(HaveOccurred())
 }
 
-func TestAPI_corev1_Watch(t *testing.T) {
-	g := NewWithT(t)
-
-	_, c := setupWorkloadClusterListener(g, CustomPorts{
-		// NOTE: make sure to use ports different than other tests, so we can run tests in parallel
-		MinPort:   DefaultMinPort + 400,
-		MaxPort:   DefaultMinPort + 499,
-		DebugPort: DefaultDebugPort + 4,
-	})
-
-	ctx := context.Background()
-
-	nodeWatcher, err := c.Watch(ctx, &corev1.NodeList{})
-	g.Expect(err).ToNot(HaveOccurred())
-
-	podWatcher, err := c.Watch(ctx, &corev1.PodList{})
-	g.Expect(err).ToNot(HaveOccurred())
-
-	expectedEvents := []string{"ADDED/foo", "MODIFIED/foo", "DELETED/foo", "ADDED/bar", "MODIFIED/bar", "DELETED/bar"}
-	receivedEvents := []string{}
-	done := make(chan bool)
-	go func() {
-		for {
-			select {
-			case event := <-nodeWatcher.ResultChan():
-				o, ok := event.Object.(client.Object)
-				if !ok {
-					return
-				}
-				receivedEvents = append(receivedEvents, fmt.Sprintf("%s/%s", event.Type, o.GetName()))
-			case event := <-podWatcher.ResultChan():
-				o, ok := event.Object.(client.Object)
-				if !ok {
-					return
-				}
-				receivedEvents = append(receivedEvents, fmt.Sprintf("%s/%s", event.Type, o.GetName()))
-			case <-done:
-				nodeWatcher.Stop()
-			}
-		}
-	}()
-
-	// Test watcher on Node resources.
-
-	node1 := &corev1.Node{}
-	node1.SetName("foo")
-
-	// create node
-	err = c.Create(ctx, node1)
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// get node
-	n := &corev1.Node{}
-	err = c.Get(ctx, client.ObjectKey{Name: "foo"}, n)
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// patch node
-	nodeWithAnnotations := n.DeepCopy()
-	nodeWithAnnotations.SetAnnotations(map[string]string{"foo": "bar"})
-	err = c.Patch(ctx, nodeWithAnnotations, client.MergeFrom(n))
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// delete node
-	err = c.Delete(ctx, n)
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// Test watcher on Pod resources.
-	pod1 := &corev1.Pod{}
-	pod1.SetName("bar")
-	pod1.SetNamespace("one")
-
-	// create pod
-	err = c.Create(ctx, pod1)
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// patch pod
-	p := &corev1.Pod{}
-	err = c.Get(ctx, client.ObjectKey{Name: "bar", Namespace: "one"}, p)
-	g.Expect(err).ToNot(HaveOccurred())
-	podWithAnnotations := p.DeepCopy()
-	podWithAnnotations.SetAnnotations(map[string]string{"foo": "bar"})
-	err = c.Patch(ctx, podWithAnnotations, client.MergeFrom(p))
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// delete pod
-	err = c.Delete(ctx, p)
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// Wait a second to ensure all events have been flushed.
-	time.Sleep(time.Second)
-
-	// Send a done signal to close the test goroutine.
-	done <- true
-
-	// Each event should be the same and in the same order.
-	g.Expect(receivedEvents).To(Equal(expectedEvents))
-}
-
-func setupWorkloadClusterListener(g Gomega, ports CustomPorts) (*WorkloadClustersMux, client.WithWatch) {
+func setupWorkloadClusterListener(g Gomega) (*WorkloadClustersMux, client.Client) {
 	manager := cmanager.New(scheme)
 
 	host := "127.0.0.1"
-	wcmux, err := NewWorkloadClustersMux(manager, host, ports)
-	g.Expect(err).ToNot(HaveOccurred())
+	wcmux := NewWorkloadClustersMux(manager, host)
 
 	// InfraCluster controller >> when "creating the load balancer"
 	wcl1 := "workload-cluster1"
diff --git a/test/infrastructure/inmemory/main.go b/test/infrastructure/inmemory/main.go
index dea5a98f9..2ddfd42b4 100644
--- a/test/infrastructure/inmemory/main.go
+++ b/test/infrastructure/inmemory/main.go
@@ -257,11 +257,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
 
 	// Start an http server
 	podIP := os.Getenv("POD_IP")
-	apiServerMux, err := server.NewWorkloadClustersMux(cloudMgr, podIP)
-	if err != nil {
-		setupLog.Error(err, "unable to create workload clusters mux")
-		os.Exit(1)
-	}
+	apiServerMux := server.NewWorkloadClustersMux(cloudMgr, podIP)
 
 	// Setup reconcilers
 	if err := (&controllers.InMemoryClusterReconciler{
diff --git a/test/infrastructure/inmemory/templates/clusterclass-in-memory-quick-start.yaml b/test/infrastructure/inmemory/templates/clusterclass-in-memory-quick-start.yaml
index 5449ebd14..ee9bcf4bd 100644
--- a/test/infrastructure/inmemory/templates/clusterclass-in-memory-quick-start.yaml
+++ b/test/infrastructure/inmemory/templates/clusterclass-in-memory-quick-start.yaml
@@ -4,13 +4,6 @@ metadata:
   name: in-memory-quick-start
 spec:
   controlPlane:
-    metadata:
-      annotations:
-        # The in-memory provider currently does not support looking up coredns
-        # and kube-proxy information and leads to reconcile errors in KCP.
-        # With these annotations KCP will skip processing those steps.
-        controlplane.cluster.x-k8s.io/skip-coredns: ""
-        controlplane.cluster.x-k8s.io/skip-kube-proxy: ""
     machineInfrastructure:
       ref:
         apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
@@ -104,4 +97,4 @@ spec:
         nodeRegistration:
           criSocket: unix:///var/run/containerd/containerd.sock
           kubeletExtraArgs:
-            eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
+            eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
\ No newline at end of file
diff --git a/test/infrastructure/kind/mapper.go b/test/infrastructure/kind/mapper.go
new file mode 100644
index 000000000..411a8daf8
--- /dev/null
+++ b/test/infrastructure/kind/mapper.go
@@ -0,0 +1,312 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+Package kind provide utilities for CAPD interoperability with kindest/node images.
+
+CAPD doesn't have direct dependencies from Kind, however, it is using kindest/node images.
+
+As a consequence CAPD must stay in sync with Kind with regards to how kindest/node images are
+run when creating a new DockerMachine.
+
+kindest/node images are created using a specific Kind version, and thus it is required
+to keep into account the mapping between the Kubernetes version for a kindest/node and
+the Kind version that created that image, so we can run it properly.
+
+NOTE: This is the same reason why in the Kind release documentation there is a specific list
+of kindest/node images - uniquely identified by a sha - to be used with a Kind release.
+
+For sake of simplification, we are grouping a set of Kind versions that runs kindest/node images
+in the same way into Kind mode.
+*/
+package kind
+
+import (
+	"fmt"
+
+	"github.com/blang/semver"
+
+	clusterapicontainer "sigs.k8s.io/cluster-api/util/container"
+)
+
+const (
+	kindestNodeImageName = "kindest/node"
+)
+
+// Mode defines a set of Kind versions that are running images in a consistent way.
+type Mode string
+
+const (
+	// ModeNone defined a Mode to be used when creating the CAPD load balancer, which doesn't
+	// use a kindest/node image.
+	ModeNone Mode = "None"
+
+	// Mode0_19 is a Mode that identifies kind v0.19 and older; all those versions
+	// are running images in a consistent way (or are too old at the time of this implementation).
+	Mode0_19 Mode = "kind 0.19"
+
+	// Mode0_20 is a Mode that identifies kind v0.20 and newer; all those versions
+	// are running images in a consistent way, that differs from Mode0_19 because
+	// of the adoption of CgroupnsMode = "private" when running images.
+	Mode0_20 Mode = "kind 0.20"
+
+	latestMode = Mode0_20
+)
+
+// Mapping defines a mapping between a Kubernetes version, a Mode, and a pre-built Image sha.
+type Mapping struct {
+	KubernetesVersion semver.Version
+	Mode              Mode
+	Image             string
+}
+
+// preBuiltMappings contains the list of kind images pre-built for a given Kind version.
+// IMPORTANT: new version should be added at the beginning of the list, so in case an image for
+// a given Kubernetes version is rebuilt with a newer kind version, we are using the latest image.
+var preBuiltMappings = []Mapping{
+
+	// TODO: Add pre-built images for newer Kind versions on top
+
+	// Pre-built images for Kind v1.20.
+	{
+		KubernetesVersion: semver.MustParse("1.27.3"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.26.6"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.25.11"),
+		Mode:              Mode0_20,
+		Image:             " kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.24.15"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.24.15@sha256:7db4f8bea3e14b82d12e044e25e34bd53754b7f2b0e9d56df21774e6f66a70ab",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.23.17"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.23.17@sha256:59c989ff8a517a93127d4a536e7014d28e235fb3529d9fba91b3951d461edfdb",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.22.17"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.22.17@sha256:f5b2e5698c6c9d6d0adc419c0deae21a425c07d81bbf3b6a6834042f25d4fba2",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.21.14"),
+		Mode:              Mode0_20,
+		Image:             "kindest/node:v1.21.14@sha256:8a4e9bb3f415d2bb81629ce33ef9c76ba514c14d707f9797a01e3216376ba093",
+	},
+
+	// Pre-built images for Kind v1.19.
+	{
+		KubernetesVersion: semver.MustParse("1.27.1"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.27.1@sha256:b7d12ed662b873bd8510879c1846e87c7e676a79fefc93e17b2a52989d3ff42b",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.26.4"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.26.4@sha256:f4c0d87be03d6bea69f5e5dc0adb678bb498a190ee5c38422bf751541cebe92e",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.25.9"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.25.9@sha256:c08d6c52820aa42e533b70bce0c2901183326d86dcdcbedecc9343681db45161",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.24.13"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.24.13@sha256:cea86276e698af043af20143f4bf0509e730ec34ed3b7fa790cc0bea091bc5dd",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.23.17"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.22.17"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.22.17@sha256:9af784f45a584f6b28bce2af84c494d947a05bd709151466489008f80a9ce9d5",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.21.14"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.21.14@sha256:220cfafdf6e3915fbce50e13d1655425558cb98872c53f802605aa2fb2d569cf",
+	},
+
+	// Pre-built and additional images for Kind v1.18.
+	// NOTE: This version predates the introduction of this change, but we are including it to expand
+	// the list of supported K8s versions; since they can be started with the same approach used up to kind 1.19,
+	// we are considering this version part of the Mode0_19 group.
+	{
+		KubernetesVersion: semver.MustParse("1.26.3"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.26.3@sha256:61b92f38dff6ccc29969e7aa154d34e38b89443af1a2c14e6cfbd2df6419c66f",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.25.8"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.25.8@sha256:00d3f5314cc35327706776e95b2f8e504198ce59ac545d0200a89e69fce10b7f",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.24.12"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.24.12@sha256:1e12918b8bc3d4253bc08f640a231bb0d3b2c5a9b28aa3f2ca1aee93e1e8db16",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.23.17"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.23.17@sha256:e5fd1d9cd7a9a50939f9c005684df5a6d145e8d695e78463637b79464292e66c",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.22.17"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.22.17@sha256:c8a828709a53c25cbdc0790c8afe12f25538617c7be879083248981945c38693",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.21.14"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.21.14@sha256:27ef72ea623ee879a25fe6f9982690a3e370c68286f4356bf643467c552a3888",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.27.1"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.27.1@sha256:9915f5629ef4d29f35b478e819249e89cfaffcbfeebda4324e5c01d53d937b09",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.27.0"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.27.0@sha256:c6b22e613523b1af67d4bc8a0c38a4c3ea3a2b8fbc5b367ae36345c9cb844518",
+	},
+
+	// Pre-built and additional images for Kind v1.17.
+	// NOTE: This version predates the introduction of this change, but we are including it to expand
+	// the list of supported K8s versions; since they can be started with the same approach used up to kind 1.19,
+	// we are considering this version part of the Mode0_19 group.
+
+	{
+		KubernetesVersion: semver.MustParse("1.25.3"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.25.3@sha256:f52781bc0d7a19fb6c405c2af83abfeb311f130707a0e219175677e366cc45d1",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.24.7"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.23.13"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.23.13@sha256:ef453bb7c79f0e3caba88d2067d4196f427794086a7d0df8df4f019d5e336b61",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.22.15"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.22.15@sha256:7d9708c4b0873f0fe2e171e2b1b7f45ae89482617778c1c875f1053d4cef2e41",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.21.14"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.21.14@sha256:9d9eb5fb26b4fbc0c6d95fa8c790414f9750dd583f5d7cee45d92e8c26670aa1",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.20.15"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.20.15@sha256:a32bf55309294120616886b5338f95dd98a2f7231519c7dedcec32ba29699394",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.19.16"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.19.16@sha256:476cb3269232888437b61deca013832fee41f9f074f9bed79f57e4280f7c48b7",
+	},
+	{
+		KubernetesVersion: semver.MustParse("1.26.0"),
+		Mode:              Mode0_19,
+		Image:             "kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352",
+	},
+}
+
+// GetMapping return the Mapping for a given Kubernetes version/custom image.
+// If a custom image is provided, return the corresponding Mapping if defined, otherwise return the mapping
+// for target K8sVersion if defined; if there is no exact match for a given Kubernetes version/custom image,
+// a best effort mapping is returned.
+// NOTE: returning a best guess mapping is a way to try to make things to work when new images are published and
+// either CAPD code in this file is not yet updated or users/CI are using older versions of CAPD.
+// Even if this can lead to CAPD failing in not obvious ways, we consider this an acceptable trade off given that this
+// is a provider to be used for development only and this approach allow us bit more of flexibility in using Kindest/node
+// images published after CAPD version is cut (without doing code changes in the list above and/or cherry-picks).
+func GetMapping(k8sVersion semver.Version, customImage string) Mapping {
+	bestGuess := Mapping{
+		KubernetesVersion: k8sVersion,
+		Mode:              latestMode,
+		Image:             pickFirstNotEmpty(customImage, fallbackImage(k8sVersion)),
+	}
+	for _, m := range preBuiltMappings {
+		// If a custom image is provided and it matches the mapping, return it.
+		if m.Image == customImage {
+			return m
+		}
+	}
+	for _, m := range preBuiltMappings {
+		// If the mapping isn't for the right Major/Minor, ignore it.
+		if !(k8sVersion.Major == m.KubernetesVersion.Major && k8sVersion.Minor == m.KubernetesVersion.Minor) {
+			continue
+		}
+
+		// If the mapping is for the same patch version, return it
+		if k8sVersion.Patch == m.KubernetesVersion.Patch {
+			return Mapping{
+				KubernetesVersion: m.KubernetesVersion,
+				Mode:              m.Mode,
+				Image:             pickFirstNotEmpty(customImage, m.Image),
+			}
+		}
+
+		// If the mapping is for an older patch version, then the K8s version is newer that any published image we are aware of,
+		// so we return out best guess.
+		if k8sVersion.Patch > m.KubernetesVersion.Patch {
+			return bestGuess
+		}
+
+		// Otherwise keep looping in the existing mapping but use the oldest kind mode as a best guess.
+		bestGuess.Mode = m.Mode
+	}
+	return bestGuess
+}
+
+// fallbackImage is a machine image for a given K8s version but without a sha.
+// NOTE: When using fallback images there is the risk of inconsistencies between
+// the kindest/node image that will be returned by the docker registry and the
+// mode that will be used to start the image.
+func fallbackImage(k8sVersion semver.Version) string {
+	versionString := clusterapicontainer.SemverToOCIImageTag(fmt.Sprintf("v%s", k8sVersion.String()))
+
+	return fmt.Sprintf("%s:%s", kindestNodeImageName, versionString)
+}
+
+func pickFirstNotEmpty(a, b string) string {
+	if a != "" {
+		return a
+	}
+	return b
+}
diff --git a/test/infrastructure/kind/mapper_test.go b/test/infrastructure/kind/mapper_test.go
new file mode 100644
index 000000000..42bf2556d
--- /dev/null
+++ b/test/infrastructure/kind/mapper_test.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kind
+
+import (
+	"testing"
+
+	"github.com/blang/semver"
+	. "github.com/onsi/gomega"
+)
+
+func TestGetMapping(t *testing.T) {
+	testCases := []struct {
+		name            string
+		k8sVersion      semver.Version
+		customImage     string
+		expectedMapping Mapping
+	}{
+		{
+			name:        "Exact match for custom image",
+			k8sVersion:  semver.MustParse("1.23.17"),
+			customImage: "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff",
+			expectedMapping: Mapping{
+				Mode:  Mode0_19,
+				Image: "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff",
+			},
+		},
+		{
+			name:        "No match for custom image fallback on K8s version match",
+			k8sVersion:  semver.MustParse("1.23.17"),
+			customImage: "foo",
+			expectedMapping: Mapping{
+				Mode:  Mode0_20,
+				Image: "foo",
+			},
+		},
+		{
+			name:       "Exact match for Kubernetes version, kind Mode0_20",
+			k8sVersion: semver.MustParse("1.27.3"),
+			expectedMapping: Mapping{
+				Mode:  Mode0_20,
+				Image: "kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72",
+			},
+		},
+		{
+			name:       "Exact match for Kubernetes version, kind Mode0_19",
+			k8sVersion: semver.MustParse("1.27.1"),
+			expectedMapping: Mapping{
+				Mode:  Mode0_19,
+				Image: "kindest/node:v1.27.1@sha256:b7d12ed662b873bd8510879c1846e87c7e676a79fefc93e17b2a52989d3ff42b",
+			},
+		},
+		{
+			name:       "In case of multiple matches for Kubernetes version, return the most recent kind mode",
+			k8sVersion: semver.MustParse("1.23.17"),
+			expectedMapping: Mapping{
+				Mode:  Mode0_20,
+				Image: "kindest/node:v1.23.17@sha256:59c989ff8a517a93127d4a536e7014d28e235fb3529d9fba91b3951d461edfdb",
+			},
+		},
+		{
+			name:       "No match Future version gets latest kind mode",
+			k8sVersion: semver.MustParse("1.27.99"),
+			expectedMapping: Mapping{
+				Mode:  latestMode,
+				Image: "kindest/node:v1.27.99",
+			},
+		},
+		{
+			name:       "No match - In case of patch version older than the last know matches return the oldest mode know for the major/minor",
+			k8sVersion: semver.MustParse("1.23.0"),
+			expectedMapping: Mapping{
+				Mode:  Mode0_19,
+				Image: "kindest/node:v1.23.0",
+			},
+		},
+		{
+			name:        "No Match custom image, No match Future version gets latest kind mode",
+			k8sVersion:  semver.MustParse("1.27.99"),
+			customImage: "foo",
+			expectedMapping: Mapping{
+				Mode:  latestMode,
+				Image: "foo",
+			},
+		},
+		{
+			name:        "No Match custom image, No match - In case of patch version older than the last know matches return the oldest mode know for the major/minor",
+			k8sVersion:  semver.MustParse("1.23.0"),
+			customImage: "foo",
+			expectedMapping: Mapping{
+				Mode:  Mode0_19,
+				Image: "foo",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			got := GetMapping(tc.k8sVersion, tc.customImage)
+
+			tc.expectedMapping.KubernetesVersion = tc.k8sVersion
+			g.Expect(got).To(Equal(tc.expectedMapping))
+		})
+	}
+}