Repository: kumahq/kuma
Pull request #11365 (2024-09-10 09:39:10)

ci(e2e/framework): refactor debug functions
As Kuma now uses go1.23.1, we are able to get rid of functions related to copy directories in e2e framework debug functions. This commit also includes small changes around log messages.

### Checklist prior to review

<!--
Each of these sections need to be filled by the author when opening the PR.

If something doesn't apply please check the box and add a justification after the `--`
-->

- [x] [Link to relevant issue][1] as well as docs and UI issues
  - no relevant issues
- [x] This will not break child repos: it doesn't hardcode values (.e.g "kumahq" as a image registry) and it will work on Windows, system specific functions like `syscall.Mkfifo` have equivalent implementation on the other OS
  - it won't
- [x] Tests (Unit test, E2E tests, manual test on universal and k8s)
  - tested on local machine
  - Don't forget `ci/` labels to run additional/fewer tests
- [x] Do you need to update [`UPGRADE.md`](../blob/master/UPGRADE.md)?
  - there is no need
- [x] Does it need to be backported according to the [backporting policy](../blob/master/CONTRIBUTING.md#backporting)? ([this](https://github.com/kumahq/kuma/actions/workflows/auto-backport.yaml) GH action will add "backport" label based on these [file globs](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L6), if you want to prevent it from adding the "backport" label use [no-backport-autolabel](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L8) label)

> Changelog: skip

<!--
Uncomment the above section to explicitly set a [`> Changelog:` entry here](https://github.com/kumahq/kuma/blob/master/CONTRIBUTING.md#submitting-a-patch)?
-->

[1]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword



Patch:
diff --git a/deployments/charts/kuma/README.md b/deployments/charts/kuma/README.md
index ea740f30d..fb528c4af 100644
--- a/deployments/charts/kuma/README.md
+++ b/deployments/charts/kuma/README.md
@@ -96,6 +96,11 @@ A Helm chart for the Kuma Control Plane
 | controlPlane.podSecurityContext | object | `{"runAsNonRoot":true}` | Security context at the pod level for control plane. |
 | controlPlane.containerSecurityContext | object | `{"readOnlyRootFilesystem":true}` | Security context at the container level for control plane. |
 | controlPlane.supportGatewaySecretsInAllNamespaces | bool | `false` | If true, then control plane can support TLS secrets for builtin gateway outside of mesh system namespace. The downside is that control plane requires permission to read Secrets in all namespaces. |
+| controlPlane.dns | object | `{"config":{"nameservers":[],"searches":[]},"policy":""}` | DNS configuration for the control-plane pod. This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). |
+| controlPlane.dns.policy | string | `""` | Defines how DNS resolution is configured for that Pod. |
+| controlPlane.dns.config | object | `{"nameservers":[],"searches":[]}` | Optional dns configuration, required when policy is 'None' |
+| controlPlane.dns.config.nameservers | list | `[]` | A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified. |
+| controlPlane.dns.config.searches | list | `[]` | A list of DNS search domains for hostname lookup in the Pod. |
 | cni.enabled | bool | `false` | Install Kuma with CNI instead of proxy init container |
 | cni.chained | bool | `false` | Install CNI in chained mode |
 | cni.netDir | string | `"/etc/cni/multus/net.d"` | Set the CNI install directory |
@@ -156,6 +161,11 @@ A Helm chart for the Kuma Control Plane
 | ingress.containerSecurityContext | object | `{"readOnlyRootFilesystem":true}` | Security context at the container level for ingress |
 | ingress.serviceAccountAnnotations | object | `{}` | Annotations to add for Control Plane's Service Account |
 | ingress.automountServiceAccountToken | bool | `true` | Whether to automountServiceAccountToken for cp. Optionally set to false |
+| ingress.dns | object | `{"config":{"nameservers":[],"searches":[]},"policy":""}` | DNS configuration for the ingress pod. This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). |
+| ingress.dns.policy | string | `""` | Defines how DNS resolution is configured for that Pod. |
+| ingress.dns.config | object | `{"nameservers":[],"searches":[]}` | Optional dns configuration, required when policy is 'None' |
+| ingress.dns.config.nameservers | list | `[]` | A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified. |
+| ingress.dns.config.searches | list | `[]` | A list of DNS search domains for hostname lookup in the Pod. |
 | egress.enabled | bool | `false` | If true, it deploys Egress for cross cluster communication |
 | egress.extraLabels | object | `{}` | Labels to add to resources, in addition to the default labels. |
 | egress.drainTime | string | `"30s"` | Time for which old listener will still be active as draining |
@@ -188,6 +198,11 @@ A Helm chart for the Kuma Control Plane
 | egress.containerSecurityContext | object | `{"readOnlyRootFilesystem":true}` | Security context at the container level for egress |
 | egress.serviceAccountAnnotations | object | `{}` | Annotations to add for Control Plane's Service Account |
 | egress.automountServiceAccountToken | bool | `true` | Whether to automountServiceAccountToken for cp. Optionally set to false |
+| egress.dns | object | `{"config":{"nameservers":[],"searches":[]},"policy":""}` | DNS configuration for the egress pod. This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). |
+| egress.dns.policy | string | `""` | Defines how DNS resolution is configured for that Pod. |
+| egress.dns.config | object | `{"nameservers":[],"searches":[]}` | Optional dns configuration, required when policy is 'None' |
+| egress.dns.config.nameservers | list | `[]` | A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified. |
+| egress.dns.config.searches | list | `[]` | A list of DNS search domains for hostname lookup in the Pod. |
 | kumactl.image.repository | string | `"kumactl"` | The kumactl image repository |
 | kumactl.image.tag | string | `nil` | The kumactl image tag. When not specified, the value is copied from global.tag |
 | kubectl.image.registry | string | `"docker.io"` | The kubectl image registry |
diff --git a/deployments/charts/kuma/templates/_helpers.tpl b/deployments/charts/kuma/templates/_helpers.tpl
index e91a809c0..5ecc72c98 100644
--- a/deployments/charts/kuma/templates/_helpers.tpl
+++ b/deployments/charts/kuma/templates/_helpers.tpl
@@ -150,6 +150,32 @@ app: {{ include "kuma.name" . }}-cni
 {{ include "kuma.selectorLabels" . }}
 {{- end }}
 
+{{/*
+params: { dns: { policy?, config: {nameservers?, searches?}} }
+returns: formatted dnsConfig
+*/}}
+{{- define "kuma.dnsConfig" -}}
+{{- $dns := .dns }}
+{{- if $dns.policy }}
+dnsPolicy: {{ $dns.policy }}
+{{- end }}
+{{- if or (gt (len $dns.config.nameservers) 0) (gt (len $dns.config.searches) 0) }}
+dnsConfig:
+  {{- if gt (len $dns.config.nameservers) 0 }}
+  nameservers:
+    {{- range $nameserver := $dns.config.nameservers }}
+    - {{ $nameserver }}
+    {{- end }}
+  {{- end }}
+  {{- if gt (len $dns.config.searches) 0 }}
+  searches:
+    {{- range $search := $dns.config.searches }}
+    - {{ $search }}
+    {{- end }}
+  {{- end }}
+{{- end }}
+{{- end -}}
+
 {{/*
 params: { image: { registry?, repository, tag? }, root: $ }
 returns: formatted image string
diff --git a/deployments/charts/kuma/templates/cp-deployment.yaml b/deployments/charts/kuma/templates/cp-deployment.yaml
index a5d4a389d..1111b149b 100644
--- a/deployments/charts/kuma/templates/cp-deployment.yaml
+++ b/deployments/charts/kuma/templates/cp-deployment.yaml
@@ -111,6 +111,7 @@ spec:
       {{- end }}
       hostNetwork: {{ .Values.controlPlane.hostNetwork }}
       terminationGracePeriodSeconds: {{ .Values.controlPlane.terminationGracePeriodSeconds }}
+      {{ include "kuma.dnsConfig" (dict "dns" .Values.controlPlane.dns) | nindent 6 | trim }}
       {{- if (eq .Values.controlPlane.environment "universal") }}
       initContainers:
         - name: migration
diff --git a/deployments/charts/kuma/templates/egress-deployment.yaml b/deployments/charts/kuma/templates/egress-deployment.yaml
index 7655a3fa7..3b6617eee 100644
--- a/deployments/charts/kuma/templates/egress-deployment.yaml
+++ b/deployments/charts/kuma/templates/egress-deployment.yaml
@@ -45,6 +45,7 @@ spec:
       tolerations:
       {{ toYaml . | nindent 8 }}
       {{- end }}
+      {{ include "kuma.dnsConfig" (dict "dns" .Values.egress.dns) | nindent 6 | trim }}
       containers:
         - name: egress
           image: {{ include "kuma.formatImage" (dict "image" .Values.dataPlane.image "root" $) | quote }}
diff --git a/deployments/charts/kuma/templates/ingress-deployment.yaml b/deployments/charts/kuma/templates/ingress-deployment.yaml
index 65ab4e19a..fcefeaac6 100644
--- a/deployments/charts/kuma/templates/ingress-deployment.yaml
+++ b/deployments/charts/kuma/templates/ingress-deployment.yaml
@@ -46,6 +46,7 @@ spec:
       {{ toYaml . | nindent 8 }}
       {{- end }}
       terminationGracePeriodSeconds: {{ .Values.ingress.terminationGracePeriodSeconds }}
+      {{ include "kuma.dnsConfig" (dict "dns" .Values.ingress.dns) | nindent 6 | trim }}
       containers:
         - name: ingress
           image: {{ include "kuma.formatImage" (dict "image" .Values.dataPlane.image "root" $) | quote }}
diff --git a/deployments/charts/kuma/values.yaml b/deployments/charts/kuma/values.yaml
index eed03b4e4..8f2222e5f 100644
--- a/deployments/charts/kuma/values.yaml
+++ b/deployments/charts/kuma/values.yaml
@@ -331,6 +331,17 @@ controlPlane:
   # -- If true, then control plane can support TLS secrets for builtin gateway outside of mesh system namespace.
   # The downside is that control plane requires permission to read Secrets in all namespaces.
   supportGatewaySecretsInAllNamespaces: false
+  # -- DNS configuration for the control-plane pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 cni:
   # -- Install Kuma with CNI instead of proxy init container
@@ -535,6 +546,17 @@ ingress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the ingress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 egress:
   # -- If true, it deploys Egress for cross cluster communication
@@ -647,6 +669,17 @@ egress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the egress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 kumactl:
   image:
diff --git a/docs/generated/raw/helm-values.yaml b/docs/generated/raw/helm-values.yaml
index eed03b4e4..8f2222e5f 100644
--- a/docs/generated/raw/helm-values.yaml
+++ b/docs/generated/raw/helm-values.yaml
@@ -331,6 +331,17 @@ controlPlane:
   # -- If true, then control plane can support TLS secrets for builtin gateway outside of mesh system namespace.
   # The downside is that control plane requires permission to read Secrets in all namespaces.
   supportGatewaySecretsInAllNamespaces: false
+  # -- DNS configuration for the control-plane pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 cni:
   # -- Install Kuma with CNI instead of proxy init container
@@ -535,6 +546,17 @@ ingress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the ingress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 egress:
   # -- If true, it deploys Egress for cross cluster communication
@@ -647,6 +669,17 @@ egress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the egress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 kumactl:
   image:
diff --git a/pkg/hds/tracker/callbacks.go b/pkg/hds/tracker/callbacks.go
index 8d0cba34a..8b465fe86 100644
--- a/pkg/hds/tracker/callbacks.go
+++ b/pkg/hds/tracker/callbacks.go
@@ -116,12 +116,10 @@ func (t *tracker) OnHealthCheckRequest(streamID xds.StreamID, req *envoy_service
 	streams.activeStreams[streamID] = true
 
 	if streams.watchdogCancel == nil { // watchdog was not started yet
-		stopCh := make(chan struct{})
-		streams.watchdogCancel = func() {
-			close(stopCh)
-		}
+		ctx, cancel := context.WithCancel(context.Background())
+		streams.watchdogCancel = cancel
 		// kick off watchdog for that Dataplane
-		go t.newWatchdog(req.Node).Start(stopCh)
+		go t.newWatchdog(req.Node).Start(ctx)
 		t.log.V(1).Info("started Watchdog for a Dataplane", "streamid", streamID, "proxyId", proxyId, "dataplaneKey", dataplaneKey)
 	}
 	t.dpStreams[dataplaneKey] = streams
@@ -129,7 +127,7 @@ func (t *tracker) OnHealthCheckRequest(streamID xds.StreamID, req *envoy_service
 	return nil
 }
 
-func (t *tracker) newWatchdog(node *envoy_core.Node) watchdog.Watchdog {
+func (t *tracker) newWatchdog(node *envoy_core.Node) util_xds_v3.Watchdog {
 	return &watchdog.SimpleWatchdog{
 		NewTicker: func() *time.Ticker {
 			return time.NewTicker(t.config.RefreshInterval.Duration)
diff --git a/pkg/kds/v2/reconcile/interfaces.go b/pkg/kds/v2/reconcile/interfaces.go
index 833722cd4..dbee6a8e1 100644
--- a/pkg/kds/v2/reconcile/interfaces.go
+++ b/pkg/kds/v2/reconcile/interfaces.go
@@ -16,7 +16,8 @@ type Reconciler interface {
 	// Reconcile reconciles state of node given changed resource types.
 	// Returns error and bool which is true if any resource was changed.
 	Reconcile(context.Context, *envoy_core.Node, map[model.ResourceType]struct{}, logr.Logger) (error, bool)
-	Clear(context.Context, *envoy_core.Node) error
+	// Clear remove local state of node
+	Clear(*envoy_core.Node) error
 }
 
 // Generates a snapshot of xDS resources for a given node.
diff --git a/pkg/kds/v2/reconcile/reconciler.go b/pkg/kds/v2/reconcile/reconciler.go
index fc789bd75..936abbd18 100644
--- a/pkg/kds/v2/reconcile/reconciler.go
+++ b/pkg/kds/v2/reconcile/reconciler.go
@@ -40,7 +40,7 @@ type reconciler struct {
 	lock sync.Mutex
 }
 
-func (r *reconciler) Clear(ctx context.Context, node *envoy_core.Node) error {
+func (r *reconciler) Clear(node *envoy_core.Node) error {
 	id := r.hasher.ID(node)
 	r.lock.Lock()
 	defer r.lock.Unlock()
diff --git a/pkg/kds/v2/server/components.go b/pkg/kds/v2/server/components.go
index 271c9f85e..662f28d3e 100644
--- a/pkg/kds/v2/server/components.go
+++ b/pkg/kds/v2/server/components.go
@@ -89,12 +89,11 @@ func newSyncTracker(
 	for _, typ := range providedTypes {
 		changedTypes[typ] = struct{}{}
 	}
-	return util_xds_v3.NewWatchdogCallbacks(func(ctx context.Context, node *envoy_core.Node, streamID int64) (util_watchdog.Watchdog, error) {
+	return util_xds_v3.NewWatchdogCallbacks(func(ctx context.Context, node *envoy_core.Node, streamID int64) (util_xds_v3.Watchdog, error) {
 		log := log.WithValues("streamID", streamID, "nodeID", node.Id)
 		log = kuma_log.AddFieldsFromCtx(log, ctx, extensions)
 		if experimentalWatchdogCfg.Enabled {
 			return &EventBasedWatchdog{
-				Ctx:           ctx,
 				Node:          node,
 				EventBus:      eventBus,
 				Reconciler:    reconciler,
@@ -128,7 +127,7 @@ func newSyncTracker(
 			NewTicker: func() *time.Ticker {
 				return time.NewTicker(refresh)
 			},
-			OnTick: func(context.Context) error {
+			OnTick: func(ctx context.Context) error {
 				start := core.Now()
 				log.V(1).Info("on tick")
 				err, changed := reconciler.Reconcile(ctx, node, changedTypes, log)
@@ -147,7 +146,7 @@ func newSyncTracker(
 				log.Error(err, "OnTick() failed")
 			},
 			OnStop: func() {
-				if err := reconciler.Clear(ctx, node); err != nil {
+				if err := reconciler.Clear(node); err != nil {
 					log.Error(err, "OnStop() failed")
 				}
 			},
diff --git a/pkg/kds/v2/server/event_based_watchdog.go b/pkg/kds/v2/server/event_based_watchdog.go
index 483c6c0ad..328aafcd8 100644
--- a/pkg/kds/v2/server/event_based_watchdog.go
+++ b/pkg/kds/v2/server/event_based_watchdog.go
@@ -16,11 +16,9 @@ import (
 	"github.com/kumahq/kuma/pkg/kds/v2/reconcile"
 	"github.com/kumahq/kuma/pkg/multitenant"
 	util_maps "github.com/kumahq/kuma/pkg/util/maps"
-	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
 )
 
 type EventBasedWatchdog struct {
-	Ctx                 context.Context
 	Node                *envoy_core.Node
 	EventBus            events.EventBus
 	Reconciler          reconcile.Reconciler
@@ -31,10 +29,8 @@ type EventBasedWatchdog struct {
 	NewFullResyncTicker func() *time.Ticker
 }
 
-var _ util_watchdog.Watchdog = &EventBasedWatchdog{}
-
-func (e *EventBasedWatchdog) Start(stop <-chan struct{}) {
-	tenantID, _ := multitenant.TenantFromCtx(e.Ctx)
+func (e *EventBasedWatchdog) Start(ctx context.Context) {
+	tenantID, _ := multitenant.TenantFromCtx(ctx)
 	listener := e.EventBus.Subscribe(func(event events.Event) bool {
 		switch ev := event.(type) {
 		case events.ResourceChangedEvent:
@@ -58,8 +54,8 @@ func (e *EventBasedWatchdog) Start(stop <-chan struct{}) {
 
 	for {
 		select {
-		case <-stop:
-			if err := e.Reconciler.Clear(e.Ctx, e.Node); err != nil {
+		case <-ctx.Done():
+			if err := e.Reconciler.Clear(e.Node); err != nil {
 				e.Log.Error(err, "reconcile clear failed")
 			}
 			listener.Close()
@@ -71,7 +67,7 @@ func (e *EventBasedWatchdog) Start(stop <-chan struct{}) {
 			reason := strings.Join(util_maps.SortedKeys(reasons), "_and_")
 			e.Log.V(1).Info("reconcile", "changedTypes", changedTypes, "reason", reason)
 			start := core.Now()
-			err, changed := e.Reconciler.Reconcile(e.Ctx, e.Node, changedTypes, e.Log)
+			err, changed := e.Reconciler.Reconcile(ctx, e.Node, changedTypes, e.Log)
 			if err != nil && !errors.Is(err, context.Canceled) {
 				e.Log.Error(err, "reconcile failed", "changedTypes", changedTypes, "reason", reason)
 				e.Metrics.KdsGenerationErrors.Inc()
diff --git a/pkg/mads/v1/service/components.go b/pkg/mads/v1/service/components.go
index 692b68d5a..8ad132854 100644
--- a/pkg/mads/v1/service/components.go
+++ b/pkg/mads/v1/service/components.go
@@ -64,13 +64,13 @@ func NewReconcilerRestCallbacks(reconciler mads_reconcile.Reconciler) util_xds.R
 }
 
 func NewSyncTracker(reconciler mads_reconcile.Reconciler, refresh time.Duration, log logr.Logger) envoy_xds.Callbacks {
-	return util_xds_v3.NewWatchdogCallbacks(func(ctx context.Context, node *envoy_core.Node, streamID int64) (util_watchdog.Watchdog, error) {
+	return util_xds_v3.NewWatchdogCallbacks(func(_ context.Context, node *envoy_core.Node, streamID int64) (util_xds_v3.Watchdog, error) {
 		log := log.WithValues("streamID", streamID, "node", node)
 		return &util_watchdog.SimpleWatchdog{
 			NewTicker: func() *time.Ticker {
 				return time.NewTicker(refresh)
 			},
-			OnTick: func(context.Context) error {
+			OnTick: func(ctx context.Context) error {
 				log.V(1).Info("on tick")
 				return reconciler.Reconcile(ctx)
 			},
diff --git a/pkg/util/watchdog/watchdog.go b/pkg/util/watchdog/watchdog.go
index cfa9c9f48..6b0bf2a20 100644
--- a/pkg/util/watchdog/watchdog.go
+++ b/pkg/util/watchdog/watchdog.go
@@ -7,10 +7,6 @@ import (
 	"github.com/pkg/errors"
 )
 
-type Watchdog interface {
-	Start(stop <-chan struct{})
-}
-
 type SimpleWatchdog struct {
 	NewTicker func() *time.Ticker
 	OnTick    func(context.Context) error
@@ -18,55 +14,40 @@ type SimpleWatchdog struct {
 	OnStop    func()
 }
 
-func (w *SimpleWatchdog) Start(stop <-chan struct{}) {
+func (w *SimpleWatchdog) Start(ctx context.Context) {
 	ticker := w.NewTicker()
 	defer ticker.Stop()
 
 	for {
-		ctx, cancel := context.WithCancel(context.Background())
-		// cancel is called at the end of the loop
-		go func() {
-			select {
-			case <-stop:
-				cancel()
-			case <-ctx.Done():
-			}
-		}()
-
-		select {
-		case <-stop:
-		default:
-			if err := w.onTick(ctx); err != nil && !errors.Is(err, context.Canceled) {
+		if err := w.onTick(ctx); err != nil {
+			if !errors.Is(err, context.Canceled) && w.OnError != nil {
 				w.OnError(err)
 			}
 		}
-		cancel()
-
 		select {
+		case <-ctx.Done():
 		case <-ticker.C:
-			continue
-		case <-stop:
+		}
+		// cases are non prioritized so we first check is the context is done
+		select {
+		case <-ctx.Done():
 			if w.OnStop != nil {
 				w.OnStop()
 			}
-			// cancel will be called by the above goroutine
 			return
+		default:
 		}
 	}
 }
 
-func (w *SimpleWatchdog) onTick(ctx context.Context) error {
+func (w *SimpleWatchdog) onTick(ctx context.Context) (err error) {
 	defer func() {
 		if cause := recover(); cause != nil {
-			if w.OnError != nil {
-				var err error
-				switch typ := cause.(type) {
-				case error:
-					err = errors.WithStack(typ)
-				default:
-					err = errors.Errorf("%v", cause)
-				}
-				w.OnError(err)
+			switch typ := cause.(type) {
+			case error:
+				err = errors.WithStack(typ)
+			default:
+				err = errors.Errorf("%v", cause)
 			}
 		}
 	}()
diff --git a/pkg/util/xds/v3/watchdog_callbacks.go b/pkg/util/xds/v3/watchdog_callbacks.go
index fe20d58d3..748ddff38 100644
--- a/pkg/util/xds/v3/watchdog_callbacks.go
+++ b/pkg/util/xds/v3/watchdog_callbacks.go
@@ -7,11 +7,14 @@ import (
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	envoy_discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 	envoy_xds "github.com/envoyproxy/go-control-plane/pkg/server/v3"
-
-	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
 )
 
-type NewNodeWatchdogFunc func(ctx context.Context, node *envoy_core.Node, streamId int64) (util_watchdog.Watchdog, error)
+// Watchdog used to run in the background and update at a fixed rhythm
+type Watchdog interface {
+	Start(ctx context.Context)
+}
+
+type NewNodeWatchdogFunc func(ctx context.Context, node *envoy_core.Node, streamId int64) (Watchdog, error)
 
 func NewWatchdogCallbacks(newNodeWatchdog NewNodeWatchdogFunc) envoy_xds.Callbacks {
 	return &watchdogCallbacks{
@@ -56,6 +59,7 @@ func (cb *watchdogCallbacks) OnFetchRequest(ctx context.Context, req *envoy_disc
 	}
 	// TODO: could also register a TTL on the REST stream to clean it up if there is no activity over a certain period,
 	// 		 since it will currently never be closed once opened
+	//nolint:contextcheck // `OnStreamRequest` is a go-control-plane interface
 	return cb.OnStreamRequest(RestStreamID, req)
 }
 
@@ -98,21 +102,19 @@ func (cb *watchdogCallbacks) OnStreamRequest(streamID int64, req *envoy_discover
 	cb.mu.Lock() // write access to the map of all ADS streams
 	defer cb.mu.Unlock()
 
-	// create a stop channel even if there won't be an actual watchdog
-	stopCh := make(chan struct{})
-	watchdog.cancel = func() {
-		close(stopCh)
-	}
+	// The request context shouldn't influence watchdogs.
+	ctx, cancel := context.WithCancel(context.WithoutCancel(watchdog.context))
+	watchdog.cancel = cancel
 	cb.streams[streamID] = watchdog
 
-	runnable, err := cb.newNodeWatchdog(watchdog.context, req.Node, streamID)
+	runnable, err := cb.newNodeWatchdog(ctx, req.Node, streamID)
 	if err != nil {
 		return err
 	}
 
 	if runnable != nil {
 		// kick off watchdog for that stream
-		go runnable.Start(stopCh)
+		go runnable.Start(ctx)
 	}
 	return nil
 }
@@ -156,21 +158,18 @@ func (cb *watchdogCallbacks) OnStreamDeltaRequest(streamID int64, req *envoy_dis
 	cb.mu.Lock() // write access to the map of all ADS streams
 	defer cb.mu.Unlock()
 
-	// create a stop channel even if there won't be an actual watchdog
-	stopCh := make(chan struct{})
-	watchdog.cancel = func() {
-		close(stopCh)
-	}
+	ctx, cancel := context.WithCancel(context.WithoutCancel(watchdog.context))
+	watchdog.cancel = cancel
 	cb.streams[streamID] = watchdog
 
-	runnable, err := cb.newNodeWatchdog(watchdog.context, req.Node, streamID)
+	runnable, err := cb.newNodeWatchdog(ctx, req.Node, streamID)
 	if err != nil {
 		return err
 	}
 
 	if runnable != nil {
 		// kick off watchdog for that stream
-		go runnable.Start(stopCh)
+		go runnable.Start(ctx)
 	}
 	return nil
 }
diff --git a/pkg/xds/server/callbacks/dataplane_sync_tracker.go b/pkg/xds/server/callbacks/dataplane_sync_tracker.go
index 0ae322da8..5a885a85f 100644
--- a/pkg/xds/server/callbacks/dataplane_sync_tracker.go
+++ b/pkg/xds/server/callbacks/dataplane_sync_tracker.go
@@ -7,12 +7,12 @@ import (
 	"github.com/kumahq/kuma/pkg/core"
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
-	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
+	util_xds_v3 "github.com/kumahq/kuma/pkg/util/xds/v3"
 )
 
 var dataplaneSyncTrackerLog = core.Log.WithName("xds").WithName("dataplane-sync-tracker")
 
-type NewDataplaneWatchdogFunc func(key core_model.ResourceKey) util_watchdog.Watchdog
+type NewDataplaneWatchdogFunc func(key core_model.ResourceKey) util_xds_v3.Watchdog
 
 func NewDataplaneSyncTracker(factoryFunc NewDataplaneWatchdogFunc) DataplaneCallbacks {
 	return &dataplaneSyncTracker{
@@ -43,14 +43,14 @@ func (t *dataplaneSyncTracker) OnProxyConnected(streamID core_xds.StreamID, dpKe
 	t.Lock()
 	defer t.Unlock()
 
-	stopCh := make(chan struct{})
-
+	ctx, cancel := context.WithCancel(context.Background())
 	t.watchdogs[dpKey] = func() {
 		dataplaneSyncTrackerLog.V(1).Info("stopping Watchdog for a Dataplane", "dpKey", dpKey, "streamID", streamID)
-		close(stopCh)
+		cancel()
 	}
 	dataplaneSyncTrackerLog.V(1).Info("starting Watchdog for a Dataplane", "dpKey", dpKey, "streamID", streamID)
-	go t.newDataplaneWatchdog(dpKey).Start(stopCh)
+	//nolint:contextcheck // it's not clear how the parent go-control-plane context lives
+	go t.newDataplaneWatchdog(dpKey).Start(ctx)
 	return nil
 }
 
diff --git a/pkg/xds/sync/dataplane_watchdog_factory.go b/pkg/xds/sync/dataplane_watchdog_factory.go
index 4225f9181..5f5706cae 100644
--- a/pkg/xds/sync/dataplane_watchdog_factory.go
+++ b/pkg/xds/sync/dataplane_watchdog_factory.go
@@ -8,6 +8,7 @@ import (
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/core/user"
 	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
+	util_xds_v3 "github.com/kumahq/kuma/pkg/util/xds/v3"
 	xds_metrics "github.com/kumahq/kuma/pkg/xds/metrics"
 )
 
@@ -30,7 +31,7 @@ func NewDataplaneWatchdogFactory(
 	}, nil
 }
 
-func (d *dataplaneWatchdogFactory) New(dpKey model.ResourceKey) util_watchdog.Watchdog {
+func (d *dataplaneWatchdogFactory) New(dpKey model.ResourceKey) util_xds_v3.Watchdog {
 	log := xdsServerLog.WithName("dataplane-sync-watchdog").WithValues("dataplaneKey", dpKey)
 	dataplaneWatchdog := NewDataplaneWatchdog(d.deps, dpKey)
 	return &util_watchdog.SimpleWatchdog{
diff --git a/pkg/xds/sync/interfaces.go b/pkg/xds/sync/interfaces.go
index 4450864e6..28878ee60 100644
--- a/pkg/xds/sync/interfaces.go
+++ b/pkg/xds/sync/interfaces.go
@@ -5,7 +5,7 @@ import (
 
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
-	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
+	util_xds_v3 "github.com/kumahq/kuma/pkg/util/xds/v3"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
 )
 
@@ -25,5 +25,5 @@ type SnapshotReconciler interface {
 
 // DataplaneWatchdogFactory returns a Watchdog that creates a new XdsContext and Proxy and executes SnapshotReconciler if there is any change
 type DataplaneWatchdogFactory interface {
-	New(dpKey core_model.ResourceKey) util_watchdog.Watchdog
+	New(dpKey core_model.ResourceKey) util_xds_v3.Watchdog
 }


Test patch:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
index ed0e7f3d3..9d13decc1 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
@@ -631,6 +631,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
index 65d7af055..c99197246 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
@@ -9977,6 +9977,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
index eed03b4e4..8f2222e5f 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
@@ -331,6 +331,17 @@ controlPlane:
   # -- If true, then control plane can support TLS secrets for builtin gateway outside of mesh system namespace.
   # The downside is that control plane requires permission to read Secrets in all namespaces.
   supportGatewaySecretsInAllNamespaces: false
+  # -- DNS configuration for the control-plane pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 cni:
   # -- Install Kuma with CNI instead of proxy init container
@@ -535,6 +546,17 @@ ingress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the ingress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 egress:
   # -- If true, it deploys Egress for cross cluster communication
@@ -647,6 +669,17 @@ egress:
   serviceAccountAnnotations: { }
   # -- Whether to automountServiceAccountToken for cp. Optionally set to false
   automountServiceAccountToken: true
+  # -- DNS configuration for the egress pod.
+  # This is equivalent to the [Kubernetes DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).
+  dns:
+    # -- Defines how DNS resolution is configured for that Pod.
+    policy: ""
+    # -- Optional dns configuration, required when policy is 'None'
+    config:
+      # -- A list of IP addresses that will be used as DNS servers for the Pod. There can be at most 3 IP addresses specified.
+      nameservers: []
+      # -- A list of DNS search domains for hostname lookup in the Pod.
+      searches: []
 
 kumactl:
   image:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
index 222b418a3..b6d593c25 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
@@ -9977,6 +9977,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.global-universal-on-k8s.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.global-universal-on-k8s.golden.yaml
index abe8145a8..d6331daf7 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.global-universal-on-k8s.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.global-universal-on-k8s.golden.yaml
@@ -145,6 +145,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       initContainers:
         - name: migration
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml
index dfd312100..5e066347c 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml
@@ -433,6 +433,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml
index e28d03d85..88055d5da 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml
index 959367f64..55f85c037 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "kuma-ci/kuma-cp:greatest"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml
index 86497fe57..bdea50057 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "gcr.io/octo/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml
index 6a2f7f6c9..9aaa157f1 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml
index 5a1a7df97..27f33ad50 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml
@@ -448,6 +448,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -623,6 +624,7 @@ spec:
       nodeSelector:
       
         kubernetes.io/os: linux
+      
       containers:
         - name: egress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
index 2d3ab8b5e..78a29e9fc 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
@@ -10039,6 +10039,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -10218,6 +10219,7 @@ spec:
       nodeSelector:
       
         kubernetes.io/os: linux
+      
       containers:
         - name: egress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
@@ -10348,6 +10350,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml
index 9d70195c3..c39112f2f 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml
index b6da6a119..6fdceb538 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml
@@ -448,6 +448,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -628,6 +629,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.zone-universal-on-k8s.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.zone-universal-on-k8s.golden.yaml
index dca30e0cb..f3d45ee21 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.zone-universal-on-k8s.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.zone-universal-on-k8s.golden.yaml
@@ -129,6 +129,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       initContainers:
         - name: migration
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml
index 56bdc162d..9bbf0141f 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.golden.yaml
new file mode 100644
index 000000000..088d00696
--- /dev/null
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.golden.yaml
@@ -0,0 +1,1203 @@
+
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: kuma-system
+  labels:
+    kuma.io/system-namespace: "true"
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: kuma-control-plane
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: kuma-egress
+  namespace: kuma-system
+  labels: 
+    app: kuma-egress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: kuma-ingress
+  namespace: kuma-system
+  labels: 
+    app: kuma-ingress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: kuma-control-plane-config
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+data:
+  config.yaml: |
+    # use this file to override default configuration of `kuma-cp`
+    #
+    # see conf/kuma-cp.conf.yml for available settings
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: kuma-control-plane
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - namespaces
+      - pods
+      - configmaps
+      - nodes
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+    - ""
+    resources:
+      - secrets
+    verbs:
+    - list
+    - watch
+  - apiGroups:
+    - "discovery.k8s.io"
+    resources:
+      - endpointslices
+    verbs:
+    - get
+    - list
+    - watch
+  - apiGroups:
+      - "apps"
+    resources:
+      - deployments
+      - replicasets
+    verbs:
+      - create
+      - delete
+      - get
+      - list
+      - patch
+      - update
+      - watch
+  - apiGroups:
+      - "batch"
+    resources:
+      - jobs
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - gateway.networking.k8s.io
+    resources:
+      - gatewayclasses
+      - gateways
+      - referencegrants
+      - httproutes
+    verbs:
+      - create
+      - delete
+      - get
+      - list
+      - patch
+      - update
+      - watch
+  - apiGroups:
+      - gateway.networking.k8s.io
+    resources:
+      - gatewayclasses/status
+      - gateways/status
+      - httproutes/status
+    verbs:
+      - get
+      - patch
+      - update
+  - apiGroups:
+      - coordination.k8s.io
+    resources:
+      - leases
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - ""
+    resources:
+      - events
+    verbs:
+      - create
+      - patch
+  - apiGroups:
+      - ""
+    resources:
+      - services
+    verbs:
+      - get
+      - delete
+      - list
+      - watch
+      - create
+      - update
+      - patch
+  - apiGroups:
+    - "discovery.k8s.io"
+    resources:
+    - endpointslices
+    verbs:
+    - get
+    - list
+    - watch
+  - apiGroups:
+      - kuma.io
+    resources:
+      - dataplanes
+      - dataplaneinsights
+      - meshes
+      - zones
+      - zoneinsights
+      - zoneingresses
+      - zoneingressinsights
+      - zoneegresses
+      - zoneegressinsights
+      - meshinsights
+      - serviceinsights
+      - proxytemplates
+      - ratelimits
+      - trafficpermissions
+      - trafficroutes
+      - timeouts
+      - retries
+      - circuitbreakers
+      - virtualoutbounds
+      - containerpatches
+      - externalservices
+      - faultinjections
+      - healthchecks
+      - trafficlogs
+      - traffictraces
+      - meshgateways
+      - meshgatewayroutes
+      - meshgatewayinstances
+      - meshgatewayconfigs
+      - meshaccesslogs
+      - meshcircuitbreakers
+      - meshfaultinjections
+      - meshhealthchecks
+      - meshhttproutes
+      - meshloadbalancingstrategies
+      - meshmetrics
+      - meshpassthroughs
+      - meshproxypatches
+      - meshratelimits
+      - meshretries
+      - meshtcproutes
+      - meshtimeouts
+      - meshtlses
+      - meshtraces
+      - meshtrafficpermissions
+      - hostnamegenerators
+      - meshexternalservices
+      - meshmultizoneservices
+      - meshservices
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - kuma.io
+    resources:
+      - meshgatewayinstances/status
+      - meshgatewayinstances/finalizers
+      - meshes/finalizers
+      - dataplanes/finalizers
+    verbs:
+      - get
+      - patch
+      - update
+  - apiGroups:
+      - ""
+    resources:
+      - pods/finalizers
+    verbs:
+      - get
+      - patch
+      - update
+  # validate k8s token before issuing mTLS cert
+  - apiGroups:
+      - authentication.k8s.io
+    resources:
+      - tokenreviews
+    verbs:
+      - create
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: kuma-control-plane
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: kuma-control-plane
+subjects:
+  - kind: ServiceAccount
+    name: kuma-control-plane
+    namespace: kuma-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: kuma-control-plane
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - secrets
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  # leader-for-life election deletes Pods in some circumstances
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+    verbs:
+      - delete
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: kuma-control-plane
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: kuma-control-plane
+subjects:
+  - kind: ServiceAccount
+    name: kuma-control-plane
+    namespace: kuma-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: kuma-control-plane
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+  annotations:
+    prometheus.io/port: "5680"
+    prometheus.io/scrape: "true"
+spec:
+  type: ClusterIP
+  ports:
+    - port: 5680
+      name: diagnostics
+      appProtocol: http
+    - port: 5681
+      name: http-api-server
+      appProtocol: http
+    - port: 5682
+      name: https-api-server
+      appProtocol: https
+    - port: 443
+      name: https-admission-server
+      targetPort: 5443
+      appProtocol: https
+    - port: 5676
+      name: mads-server
+      appProtocol: https
+    - port: 5678
+      name: dp-server
+      appProtocol: https
+  selector:
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: kuma-egress
+  namespace: kuma-system
+  labels: 
+    app: kuma-egress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+  annotations:
+spec:
+  type: ClusterIP
+  ports:
+    - port: 10002
+      protocol: TCP
+      targetPort: 10002
+  selector:
+    app: kuma-egress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: kuma-ingress
+  namespace: kuma-system
+  labels: 
+    app: kuma-ingress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+  annotations:
+spec:
+  type: LoadBalancer
+  ports:
+    - port: 10001
+      protocol: TCP
+      targetPort: 10001
+  selector:
+    app: kuma-ingress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: kuma-control-plane
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+  annotations: 
+    
+spec:
+  replicas: 1
+  minReadySeconds: 0
+  strategy:
+    rollingUpdate:
+      maxSurge: 1
+      maxUnavailable: 0
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: kuma
+      app.kubernetes.io/instance: kuma
+      app: kuma-control-plane
+  template:
+    metadata:
+      annotations:
+        checksum/config: fd9d1d8386f97f2bd49e50f476520816168a1c9f60bbc43dec1347a64d239155
+        checksum/tls-secrets: cd5650b43d26157935d13a561eed2f7c85b6d0cf988a024a52e005527240a49d
+      labels: 
+        app: kuma-control-plane
+        app.kubernetes.io/name: kuma
+        app.kubernetes.io/instance: kuma
+    spec:
+      affinity: 
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app.kubernetes.io/name
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app.kubernetes.io/instance
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app
+                  operator: In
+                  values:
+                  - 'kuma-control-plane'
+              topologyKey: kubernetes.io/hostname
+            weight: 100
+      securityContext:
+        runAsNonRoot: true
+      serviceAccountName: kuma-control-plane
+      automountServiceAccountToken: true
+      nodeSelector:
+        
+        kubernetes.io/os: linux
+      hostNetwork: false
+      terminationGracePeriodSeconds: 30
+      dnsPolicy: None
+      dnsConfig:
+        nameservers:
+          - 8.8.8.8
+          - 1.1.1.1
+        searches:
+          - test.com
+          - new.test.com
+      containers:
+        - name: control-plane
+          image: "docker.io/kumahq/kuma-cp:0.0.1"
+          imagePullPolicy: IfNotPresent
+          securityContext:
+            readOnlyRootFilesystem: true
+          env:
+            - name: KUMA_API_SERVER_AUTHN_LOCALHOST_IS_ADMIN
+              value: "false"
+            - name: KUMA_API_SERVER_READ_ONLY
+              value: "true"
+            - name: KUMA_DEFAULTS_SKIP_MESH_CREATION
+              value: "false"
+            - name: KUMA_DP_SERVER_HDS_ENABLED
+              value: "false"
+            - name: KUMA_ENVIRONMENT
+              value: "kubernetes"
+            - name: KUMA_GENERAL_TLS_CERT_FILE
+              value: "/var/run/secrets/kuma.io/tls-cert/tls.crt"
+            - name: KUMA_GENERAL_TLS_KEY_FILE
+              value: "/var/run/secrets/kuma.io/tls-cert/tls.key"
+            - name: KUMA_INJECTOR_INIT_CONTAINER_IMAGE
+              value: "docker.io/kumahq/kuma-init:0.0.1"
+            - name: KUMA_MODE
+              value: "zone"
+            - name: KUMA_PLUGIN_POLICIES_ENABLED
+              value: "meshaccesslogs,meshcircuitbreakers,meshfaultinjections,meshhealthchecks,meshhttproutes,meshloadbalancingstrategies,meshmetrics,meshpassthroughs,meshproxypatches,meshratelimits,meshretries,meshtcproutes,meshtimeouts,meshtlses,meshtraces,meshtrafficpermissions"
+            - name: KUMA_RUNTIME_KUBERNETES_ADMISSION_SERVER_CERT_DIR
+              value: "/var/run/secrets/kuma.io/tls-cert"
+            - name: KUMA_RUNTIME_KUBERNETES_ADMISSION_SERVER_PORT
+              value: "5443"
+            - name: KUMA_RUNTIME_KUBERNETES_ALLOWED_USERS
+              value: "system:serviceaccount:kuma-system:kuma-control-plane"
+            - name: KUMA_RUNTIME_KUBERNETES_CONTROL_PLANE_SERVICE_NAME
+              value: "kuma-control-plane"
+            - name: KUMA_RUNTIME_KUBERNETES_INJECTOR_CA_CERT_FILE
+              value: "/var/run/secrets/kuma.io/tls-cert/ca.crt"
+            - name: KUMA_RUNTIME_KUBERNETES_INJECTOR_CNI_ENABLED
+              value: "false"
+            - name: KUMA_RUNTIME_KUBERNETES_INJECTOR_SIDECAR_CONTAINER_IMAGE
+              value: "docker.io/kumahq/kuma-dp:0.0.1"
+            - name: KUMA_STORE_KUBERNETES_SYSTEM_NAMESPACE
+              value: "kuma-system"
+            - name: KUMA_STORE_TYPE
+              value: "kubernetes"
+            - name: KUMA_INTER_CP_CATALOG_INSTANCE_ADDRESS
+              valueFrom:
+                fieldRef:
+                  fieldPath: status.podIP
+            - name: GOMEMLIMIT
+              valueFrom:
+                resourceFieldRef:
+                  containerName: control-plane
+                  resource: limits.memory
+            - name: GOMAXPROCS
+              valueFrom:
+                resourceFieldRef:
+                  containerName: control-plane
+                  resource: limits.cpu
+          args:
+            - run
+            - --log-level=info
+            - --log-output-path=
+            - --config-file=/etc/kuma.io/kuma-control-plane/config.yaml
+          ports:
+            - containerPort: 5680
+              name: diagnostics
+              protocol: TCP
+            - containerPort: 5681
+            - containerPort: 5682
+            - containerPort: 5443
+            - containerPort: 5678
+          livenessProbe:
+            timeoutSeconds: 10
+            httpGet:
+              path: /healthy
+              port: 5680
+          readinessProbe:
+            timeoutSeconds: 10
+            httpGet:
+              path: /ready
+              port: 5680
+          resources:
+            limits:
+              memory: 256Mi
+            requests:
+              cpu: 500m
+              memory: 256Mi
+          
+          volumeMounts:
+            - name: general-tls-cert
+              mountPath: /var/run/secrets/kuma.io/tls-cert/tls.crt
+              subPath: tls.crt
+              readOnly: true
+            - name: general-tls-cert
+              mountPath: /var/run/secrets/kuma.io/tls-cert/tls.key
+              subPath: tls.key
+              readOnly: true
+            - name: general-tls-cert
+              mountPath: /var/run/secrets/kuma.io/tls-cert/ca.crt
+              subPath: ca.crt
+              readOnly: true
+            - name: kuma-control-plane-config
+              mountPath: /etc/kuma.io/kuma-control-plane
+              readOnly: true
+            - name: tmp
+              mountPath: /tmp
+      volumes:
+        - name: general-tls-cert
+          secret:
+            secretName: general-tls-secret
+        - name: kuma-control-plane-config
+          configMap:
+            name: kuma-control-plane-config
+        - name: tmp
+          emptyDir: {}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: kuma-egress
+  namespace: kuma-system
+  labels: 
+    app: kuma-egress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+spec:
+  strategy:
+    rollingUpdate:
+      maxSurge: 1
+      maxUnavailable: 0
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: kuma
+      app.kubernetes.io/instance: kuma
+      app: kuma-egress
+  template:
+    metadata:
+      annotations:
+        kuma.io/egress: enabled
+      labels:
+        app: kuma-egress
+        app.kubernetes.io/name: kuma
+        app.kubernetes.io/instance: kuma
+    spec:
+      affinity: 
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app.kubernetes.io/name
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app.kubernetes.io/instance
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app
+                  operator: In
+                  values:
+                  - kuma-egress
+              topologyKey: kubernetes.io/hostname
+            weight: 100
+      securityContext:
+        runAsGroup: 5678
+        runAsNonRoot: true
+        runAsUser: 5678
+      serviceAccountName: kuma-egress
+      automountServiceAccountToken: true
+      nodeSelector:
+      
+        kubernetes.io/os: linux
+      dnsConfig:
+        searches:
+          - test.com
+          - new.test.com
+      containers:
+        - name: egress
+          image: "docker.io/kumahq/kuma-dp:0.0.1"
+          imagePullPolicy: IfNotPresent
+          securityContext:
+            readOnlyRootFilesystem: true
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: POD_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: KUMA_CONTROL_PLANE_URL
+              value: "https://kuma-control-plane.kuma-system:5678"
+            - name: KUMA_CONTROL_PLANE_CA_CERT_FILE
+              value: /var/run/secrets/kuma.io/cp-ca/ca.crt
+            - name: KUMA_DATAPLANE_DRAIN_TIME
+              value: 30s
+            - name: KUMA_DATAPLANE_RUNTIME_TOKEN_PATH
+              value: /var/run/secrets/kubernetes.io/serviceaccount/token
+            - name: KUMA_DATAPLANE_PROXY_TYPE
+              value: "egress"
+          args:
+            - run
+            - --log-level=info
+          ports:
+            - containerPort: 10002
+          livenessProbe:
+            httpGet:
+              path: "/ready"
+              port: 9901
+            failureThreshold: 12
+            initialDelaySeconds: 60
+            periodSeconds: 5
+            successThreshold: 1
+            timeoutSeconds: 3
+          readinessProbe:
+            httpGet:
+              path: "/ready"
+              port: 9901
+            failureThreshold: 12
+            initialDelaySeconds: 1
+            periodSeconds: 5
+            successThreshold: 1
+            timeoutSeconds: 3
+          resources: 
+            limits:
+              cpu: 1000m
+              memory: 512Mi
+            requests:
+              cpu: 50m
+              memory: 64Mi
+          volumeMounts:
+            - name: control-plane-ca
+              mountPath: /var/run/secrets/kuma.io/cp-ca
+              readOnly: true
+            - name: tmp
+              mountPath: /tmp
+      volumes:
+        - name: control-plane-ca
+          secret:
+            secretName: "general-tls-secret"
+            items:
+              - key: ca.crt
+                path: ca.crt
+        - name: tmp
+          emptyDir: {}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: kuma-ingress
+  namespace: kuma-system
+  labels: 
+    app: kuma-ingress
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+spec:
+  strategy:
+    rollingUpdate:
+      maxSurge: 1
+      maxUnavailable: 0
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: kuma
+      app.kubernetes.io/instance: kuma
+      app: kuma-ingress
+  template:
+    metadata:
+      annotations:
+        kuma.io/ingress: enabled
+      labels:
+        app: kuma-ingress
+        app.kubernetes.io/name: kuma
+        app.kubernetes.io/instance: kuma
+    spec:
+      affinity: 
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app.kubernetes.io/name
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app.kubernetes.io/instance
+                  operator: In
+                  values:
+                  - 'kuma'
+                - key: app
+                  operator: In
+                  values:
+                  - kuma-ingress
+              topologyKey: kubernetes.io/hostname
+            weight: 100
+      securityContext:
+        runAsGroup: 5678
+        runAsNonRoot: true
+        runAsUser: 5678
+      serviceAccountName: kuma-ingress
+      automountServiceAccountToken: true
+      nodeSelector:
+      
+        kubernetes.io/os: linux
+      terminationGracePeriodSeconds: 40
+      dnsConfig:
+        nameservers:
+          - 8.8.8.8
+          - 1.1.1.1
+      containers:
+        - name: ingress
+          image: "docker.io/kumahq/kuma-dp:0.0.1"
+          imagePullPolicy: IfNotPresent
+          securityContext:
+            readOnlyRootFilesystem: true
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: POD_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: KUMA_CONTROL_PLANE_URL
+              value: "https://kuma-control-plane.kuma-system:5678"
+            - name: KUMA_CONTROL_PLANE_CA_CERT_FILE
+              value: /var/run/secrets/kuma.io/cp-ca/ca.crt
+            - name: KUMA_DATAPLANE_DRAIN_TIME
+              value: 30s
+            - name: KUMA_DATAPLANE_RUNTIME_TOKEN_PATH
+              value: /var/run/secrets/kubernetes.io/serviceaccount/token
+            - name: KUMA_DATAPLANE_PROXY_TYPE
+              value: "ingress"
+          args:
+            - run
+            - --log-level=info
+          ports:
+            - containerPort: 10001
+          livenessProbe:
+            httpGet:
+              path: "/ready"
+              port: 9901
+            failureThreshold: 12
+            initialDelaySeconds: 60
+            periodSeconds: 5
+            successThreshold: 1
+            timeoutSeconds: 3
+          readinessProbe:
+            httpGet:
+              path: "/ready"
+              port: 9901
+            failureThreshold: 12
+            initialDelaySeconds: 1
+            periodSeconds: 5
+            successThreshold: 1
+            timeoutSeconds: 3
+          resources: 
+            limits:
+              cpu: 1000m
+              memory: 512Mi
+            requests:
+              cpu: 50m
+              memory: 64Mi
+          
+          volumeMounts:
+            - name: control-plane-ca
+              mountPath: /var/run/secrets/kuma.io/cp-ca
+              readOnly: true
+            - name: tmp
+              mountPath: /tmp
+      volumes:
+        - name: control-plane-ca
+          secret:
+            secretName: "general-tls-secret"
+            items:
+              - key: ca.crt
+                path: ca.crt
+        - name: tmp
+          emptyDir: {}
+---
+apiVersion: admissionregistration.k8s.io/v1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: kuma-admission-mutating-webhook-configuration
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+webhooks:
+  - name: mesh.defaulter.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Fail
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /default-kuma-io-v1alpha1-mesh
+    rules:
+      - apiGroups:
+          - kuma.io
+        apiVersions:
+          - v1alpha1
+        operations:
+          - CREATE
+          - UPDATE
+        resources:
+          - meshes
+          - dataplanes
+          - dataplaneinsights
+          - meshgateways
+          - zoneingresses
+          - zoneingressinsights
+          - zoneegresses
+          - zoneegressinsights
+          - serviceinsights
+          - zone
+          - zoneinsights
+          - meshaccesslogs
+          - meshcircuitbreakers
+          - meshfaultinjections
+          - meshhealthchecks
+          - meshhttproutes
+          - meshloadbalancingstrategies
+          - meshmetrics
+          - meshpassthroughs
+          - meshproxypatches
+          - meshratelimits
+          - meshretries
+          - meshtcproutes
+          - meshtimeouts
+          - meshtlses
+          - meshtraces
+          - meshtrafficpermissions
+          - hostnamegenerators
+          - meshexternalservices
+          - meshmultizoneservices
+          - meshservices
+    sideEffects: None
+  - name: owner-reference.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Fail
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /owner-reference-kuma-io-v1alpha1
+    rules:
+      - apiGroups:
+          - kuma.io
+        apiVersions:
+          - v1alpha1
+        operations:
+          - CREATE
+        resources:
+          - circuitbreakers
+          - externalservices
+          - faultinjections
+          - healthchecks
+          - meshgateways
+          - meshgatewayroutes
+          - proxytemplates
+          - ratelimits
+          - retries
+          - timeouts
+          - trafficlogs
+          - trafficpermissions
+          - trafficroutes
+          - traffictraces
+          - virtualoutbounds
+          - meshaccesslogs
+          - meshcircuitbreakers
+          - meshfaultinjections
+          - meshhealthchecks
+          - meshhttproutes
+          - meshloadbalancingstrategies
+          - meshmetrics
+          - meshpassthroughs
+          - meshproxypatches
+          - meshratelimits
+          - meshretries
+          - meshtcproutes
+          - meshtimeouts
+          - meshtlses
+          - meshtraces
+          - meshtrafficpermissions
+          - hostnamegenerators
+          - meshexternalservices
+          - meshmultizoneservices
+          - meshservices
+  
+      
+    sideEffects: None
+  - name: namespace-kuma-injector.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Fail
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+        - key: kuma.io/sidecar-injection
+          operator: In
+          values: ["enabled", "true"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /inject-sidecar
+    rules:
+      - apiGroups:
+          - ""
+        apiVersions:
+          - v1
+        operations:
+          - CREATE
+        resources:
+          - pods
+    sideEffects: None
+  - name: pods-kuma-injector.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Fail
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    objectSelector:
+      matchLabels:
+        kuma.io/sidecar-injection: enabled
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /inject-sidecar
+    rules:
+      - apiGroups:
+          - ""
+        apiVersions:
+          - v1
+        operations:
+          - CREATE
+        resources:
+          - pods
+    sideEffects: None
+---
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: kuma-validating-webhook-configuration
+  namespace: kuma-system
+  labels: 
+    app: kuma-control-plane
+    app.kubernetes.io/name: kuma
+    app.kubernetes.io/instance: kuma
+webhooks:
+  - name: validator.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Fail
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /validate-kuma-io-v1alpha1
+    rules:
+      - apiGroups:
+          - kuma.io
+        apiVersions:
+          - v1alpha1
+        operations:
+          - CREATE
+          - UPDATE
+          - DELETE
+        resources:
+          - circuitbreakers
+          - dataplanes
+          - externalservices
+          - faultinjections
+          - meshgatewayinstances
+          - healthchecks
+          - meshes
+          - meshgateways
+          - meshgatewayroutes
+          - proxytemplates
+          - ratelimits
+          - retries
+          - trafficlogs
+          - trafficpermissions
+          - trafficroutes
+          - traffictraces
+          - virtualoutbounds
+          - zones
+          - containerpatches
+          - meshaccesslogs
+          - meshcircuitbreakers
+          - meshfaultinjections
+          - meshhealthchecks
+          - meshhttproutes
+          - meshloadbalancingstrategies
+          - meshmetrics
+          - meshpassthroughs
+          - meshproxypatches
+          - meshratelimits
+          - meshretries
+          - meshtcproutes
+          - meshtimeouts
+          - meshtlses
+          - meshtraces
+          - meshtrafficpermissions
+          - hostnamegenerators
+          - meshexternalservices
+          - meshmultizoneservices
+          - meshservices
+    
+      
+    sideEffects: None
+  - name: service.validator.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Ignore
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /validate-v1-service
+    rules:
+      - apiGroups:
+          - ""
+        apiVersions:
+          - v1
+        operations:
+          - CREATE
+          - UPDATE
+        resources:
+          - services
+    sideEffects: None
+  - name: secret.validator.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    namespaceSelector:
+      matchLabels:
+        kuma.io/system-namespace: "true"
+    failurePolicy: Ignore
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /validate-v1-secret
+    rules:
+      - apiGroups:
+          - ""
+        apiVersions:
+          - v1
+        operations:
+          - CREATE
+          - UPDATE
+          - DELETE
+        resources:
+          - secrets
+    sideEffects: None
+  - name: gateway.validator.kuma-admission.kuma.io
+    admissionReviewVersions: ["v1"]
+    failurePolicy: Ignore
+    namespaceSelector:
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["kube-system"]
+    clientConfig:
+      caBundle: XYZ
+      service:
+        namespace: kuma-system
+        name: kuma-control-plane
+        path: /validate-gatewayclass
+    rules:
+      - apiGroups:
+          - "gateway.networking.k8s.io"
+        apiVersions:
+          - v1beta1
+        operations:
+          - CREATE
+        resources:
+          - gatewayclasses
+    sideEffects: None
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.values.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.values.yaml
new file mode 100644
index 000000000..bdf637a88
--- /dev/null
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/dnsConfigOption.values.yaml
@@ -0,0 +1,24 @@
+controlPlane:
+  dns:
+    policy: None
+    config:
+      nameservers:
+      - 8.8.8.8
+      - 1.1.1.1
+      searches:
+      - test.com
+      - new.test.com
+ingress:
+  enabled: true
+  dns:
+    config:
+      nameservers:
+      - 8.8.8.8
+      - 1.1.1.1
+egress:
+  enabled: true
+  dns:
+    config:
+      searches:
+      - test.com
+      - new.test.com
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml
index 5385141ce..0b4bd5e75 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml
@@ -416,6 +416,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml
index 1980c0ca4..cfd9433fa 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/envVarsDownwardApi.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/envVarsDownwardApi.golden.yaml
index 4a38fdd1e..15d1eb7d8 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/envVarsDownwardApi.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/envVarsDownwardApi.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml
index 86edcb6d3..aaf81c5b3 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml
@@ -440,6 +440,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml
index c0ec2d5cf..22058a7dd 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml
@@ -459,6 +459,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -641,6 +642,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml
index 399cf8823..f45700719 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml
@@ -703,6 +703,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -891,6 +892,7 @@ spec:
       nodeSelector:
       
         kubernetes.io/os: linux
+      
       containers:
         - name: egress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
@@ -1024,6 +1026,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml
index a3d461549..24e65b72d 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml
@@ -479,6 +479,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -661,6 +662,7 @@ spec:
       nodeSelector:
       
         kubernetes.io/os: linux
+      
       containers:
         - name: egress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
@@ -791,6 +793,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml
index b47f0ab69..76527d1f3 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml
@@ -420,6 +420,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml
index 1c6a971b3..cfef3b272 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml
@@ -494,6 +494,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
@@ -694,6 +695,7 @@ spec:
       nodeSelector:
       
         kubernetes.io/os: linux
+      
       containers:
         - name: egress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
@@ -845,6 +847,7 @@ spec:
       
         kubernetes.io/os: linux
       terminationGracePeriodSeconds: 40
+      
       containers:
         - name: ingress
           image: "docker.io/kumahq/kuma-dp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml
index b055a8844..213bc7838 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml
@@ -417,6 +417,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml
index dfd7eb93a..360337760 100644
--- a/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml
@@ -418,6 +418,7 @@ spec:
         kubernetes.io/os: linux
       hostNetwork: false
       terminationGracePeriodSeconds: 30
+      
       containers:
         - name: control-plane
           image: "docker.io/kumahq/kuma-cp:0.0.1"
diff --git a/pkg/kds/v2/server/event_based_watchdog_test.go b/pkg/kds/v2/server/event_based_watchdog_test.go
index 6e5312f77..58209d8cf 100644
--- a/pkg/kds/v2/server/event_based_watchdog_test.go
+++ b/pkg/kds/v2/server/event_based_watchdog_test.go
@@ -29,7 +29,7 @@ func (s staticReconciler) Reconcile(ctx context.Context, node *envoy_core.Node,
 	return nil, true
 }
 
-func (s staticReconciler) Clear(ctx context.Context, node *envoy_core.Node) error {
+func (s staticReconciler) Clear(node *envoy_core.Node) error {
 	return nil
 }
 
@@ -39,7 +39,7 @@ var _ = Describe("Event Based Watchdog", func() {
 	var eventBus events.EventBus
 	var metrics core_metrics.Metrics
 	var reconciler *staticReconciler
-	var stopCh chan struct{}
+	var cancel context.CancelFunc
 	var flushCh chan time.Time
 	var fullResyncCh chan time.Time
 	var watchdog *EventBasedWatchdog
@@ -56,14 +56,14 @@ var _ = Describe("Event Based Watchdog", func() {
 		eventBus, err = events.NewEventBus(10, metrics)
 		Expect(err).ToNot(HaveOccurred())
 
-		stopCh = make(chan struct{})
 		flushCh = make(chan time.Time)
 		fullResyncCh = make(chan time.Time)
 		reconciler = &staticReconciler{
 			changedResTypes: make(chan map[core_model.ResourceType]struct{}, 1),
 		}
+		ctx := context.Background()
+		ctx, cancel = context.WithCancel(ctx)
 		watchdog = &EventBasedWatchdog{
-			Ctx: context.Background(),
 			Node: &envoy_core.Node{
 				Id: "1",
 			},
@@ -84,12 +84,12 @@ var _ = Describe("Event Based Watchdog", func() {
 			},
 		}
 		go func() {
-			watchdog.Start(stopCh)
+			watchdog.Start(ctx)
 		}()
 	})
 
 	AfterAll(func() {
-		close(stopCh)
+		cancel()
 	})
 
 	It("should reconcile on the first flush", func() {
diff --git a/pkg/mads/v1/service_test.go b/pkg/mads/v1/service_test.go
index ba062c10d..97d67f481 100644
--- a/pkg/mads/v1/service_test.go
+++ b/pkg/mads/v1/service_test.go
@@ -95,7 +95,7 @@ var _ = Describe("MADS http service", func() {
 
 		// then
 		Expect(err).ToNot(HaveOccurred())
-		Expect(resp.StatusCode).To(Equal(http.StatusOK))
+		Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 		// when
 		respBody, err := io.ReadAll(resp.Body)
@@ -125,7 +125,7 @@ var _ = Describe("MADS http service", func() {
 
 		// then
 		Expect(err).ToNot(HaveOccurred())
-		Expect(resp.StatusCode).To(Equal(http.StatusNotModified))
+		Expect(resp).To(HaveHTTPStatus(http.StatusNotModified))
 
 		// when
 		respBody, err = io.ReadAll(resp.Body)
@@ -244,7 +244,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
@@ -287,7 +287,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusNotModified))
+			Expect(resp).To(HaveHTTPStatus(http.StatusNotModified))
 
 			// when
 			respBody, err = io.ReadAll(resp.Body)
@@ -319,12 +319,10 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
-
-			// then
 			Expect(err).ToNot(HaveOccurred())
 
 			// when
@@ -356,7 +354,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err = io.ReadAll(resp.Body)
@@ -396,7 +394,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
@@ -444,7 +442,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp2.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp2).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err = io.ReadAll(resp2.Body)
@@ -530,7 +528,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
@@ -562,7 +560,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusNotModified))
+			Expect(resp).To(HaveHTTPStatus(http.StatusNotModified))
 		})
 
 		It("should allow synchronous requests", func() {
@@ -603,7 +601,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusOK))
+			Expect(resp).To(HaveHTTPStatus(http.StatusOK))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
@@ -645,7 +643,7 @@ var _ = Describe("MADS http service", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
+			Expect(resp).To(HaveHTTPStatus(http.StatusBadRequest))
 
 			// when
 			respBody, err := io.ReadAll(resp.Body)
diff --git a/pkg/util/watchdog/watchdog_test.go b/pkg/util/watchdog/watchdog_test.go
index bbf422db9..512be33a7 100644
--- a/pkg/util/watchdog/watchdog_test.go
+++ b/pkg/util/watchdog/watchdog_test.go
@@ -16,18 +16,16 @@ var _ = Describe("SimpleWatchdog", func() {
 	var timeTicks chan time.Time
 	var onTickCalls chan struct{}
 	var onErrorCalls chan error
+	var doneCh chan struct{}
+	var cancel context.CancelFunc
+	var ctx context.Context
 
 	BeforeEach(func() {
 		timeTicks = make(chan time.Time)
 		onTickCalls = make(chan struct{})
 		onErrorCalls = make(chan error)
-	})
-
-	var stopCh, doneCh chan struct{}
-
-	BeforeEach(func() {
-		stopCh = make(chan struct{})
 		doneCh = make(chan struct{})
+		ctx, cancel = context.WithCancel(context.Background())
 	})
 
 	It("should call OnTick() on timer ticks", test.Within(5*time.Second, func() {
@@ -42,17 +40,28 @@ var _ = Describe("SimpleWatchdog", func() {
 				onTickCalls <- struct{}{}
 				return nil
 			},
+			OnError: func(err error) {
+				onErrorCalls <- err
+			},
 		}
 
 		// setup
 		go func() {
-			watchdog.Start(stopCh)
+			watchdog.Start(ctx)
 
 			close(doneCh)
 		}()
 
 		// then first tick happens "immediately"
-		<-onTickCalls
+		By("ticks on first call")
+		Eventually(onTickCalls).Should(Receive())
+
+		By("simulating 1st tick")
+		// when
+		timeTicks <- time.Time{}
+
+		// then
+		Eventually(onTickCalls).Should(Receive())
 
 		By("simulating 2nd tick")
 		// when
@@ -63,10 +72,11 @@ var _ = Describe("SimpleWatchdog", func() {
 
 		By("simulating Dataplane disconnect")
 		// when
-		close(stopCh)
+		cancel()
 
 		// then
-		<-doneCh
+		Eventually(doneCh).Should(BeClosed())
+		Consistently(onErrorCalls).ShouldNot(Receive())
 	}))
 
 	It("should call OnError() when OnTick() returns an error", test.Within(5*time.Second, func() {
@@ -89,21 +99,26 @@ var _ = Describe("SimpleWatchdog", func() {
 
 		// setup
 		go func() {
-			watchdog.Start(stopCh)
+			watchdog.Start(ctx)
 
 			close(doneCh)
 		}()
 
 		// then first tick happens "immediately"
-		actualErr := <-onErrorCalls
-		Expect(actualErr).To(MatchError(expectedErr))
+		By("tick on startup")
+		Eventually(onErrorCalls).Should(Receive(MatchError(expectedErr)))
+
+		By("simulating 1st tick")
+		// when
+		timeTicks <- time.Time{}
+		Eventually(onErrorCalls).Should(Receive(MatchError(expectedErr)))
 
 		By("simulating Dataplane disconnect")
 		// when
-		close(stopCh)
+		cancel()
 
 		// then
-		<-doneCh
+		Eventually(doneCh).Should(BeClosed())
 	}))
 
 	It("should not crash the whole application when watchdog crashes", test.Within(5*time.Second, func() {
@@ -124,13 +139,52 @@ var _ = Describe("SimpleWatchdog", func() {
 
 		// when
 		go func() {
-			watchdog.Start(stopCh)
+			watchdog.Start(ctx)
 			close(doneCh)
 		}()
 
 		// then watchdog returned an error
-		Expect(<-onErrorCalls).To(HaveOccurred())
-		close(stopCh)
-		<-doneCh
+		Eventually(onErrorCalls).Should(Receive())
+		cancel()
+		Eventually(doneCh).Should(BeClosed())
+	}))
+
+	It("should cancel in flight tick when stopping", test.Within(5*time.Second, func() {
+		// given
+		watchdog := SimpleWatchdog{
+			NewTicker: func() *time.Ticker {
+				return &time.Ticker{
+					C: timeTicks,
+				}
+			},
+			OnTick: func(ctx context.Context) error {
+				<-ctx.Done()
+				onTickCalls <- struct{}{}
+				return ctx.Err()
+			},
+			OnError: func(err error) {
+				onErrorCalls <- err
+			},
+		}
+
+		// setup
+		go func() {
+			watchdog.Start(ctx)
+
+			close(doneCh)
+		}()
+
+		// then first tick is delayed because context is not cancelled
+		By("ticks on first call")
+		Consistently(onTickCalls).ShouldNot(Receive())
+
+		By("simulating Dataplane disconnect")
+		// when
+		cancel()
+		Eventually(onTickCalls).Should(Receive())
+
+		// then
+		Eventually(doneCh).Should(BeClosed())
+		Consistently(onErrorCalls).ShouldNot(Receive())
 	}))
 })
diff --git a/pkg/xds/server/callbacks/dataplane_sync_tracker_test.go b/pkg/xds/server/callbacks/dataplane_sync_tracker_test.go
index 661b81fa0..fb456f232 100644
--- a/pkg/xds/server/callbacks/dataplane_sync_tracker_test.go
+++ b/pkg/xds/server/callbacks/dataplane_sync_tracker_test.go
@@ -12,7 +12,6 @@ import (
 
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/test"
-	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
 	util_xds_v3 "github.com/kumahq/kuma/pkg/util/xds/v3"
 	. "github.com/kumahq/kuma/pkg/xds/server/callbacks"
 )
@@ -77,10 +76,10 @@ var _ = Describe("Sync", func() {
 			watchdogCh := make(chan core_model.ResourceKey)
 
 			// setup
-			tracker := NewDataplaneSyncTracker(func(key core_model.ResourceKey) util_watchdog.Watchdog {
-				return WatchdogFunc(func(stop <-chan struct{}) {
+			tracker := NewDataplaneSyncTracker(func(key core_model.ResourceKey) util_xds_v3.Watchdog {
+				return WatchdogFunc(func(ctx context.Context) {
 					watchdogCh <- key
-					<-stop
+					<-ctx.Done()
 					close(watchdogCh)
 				})
 			})
@@ -131,10 +130,10 @@ var _ = Describe("Sync", func() {
 		It("should start only one watchdog per dataplane", func() {
 			// setup
 			var activeWatchdogs int32
-			tracker := NewDataplaneSyncTracker(func(key core_model.ResourceKey) util_watchdog.Watchdog {
-				return WatchdogFunc(func(stop <-chan struct{}) {
+			tracker := NewDataplaneSyncTracker(func(key core_model.ResourceKey) util_xds_v3.Watchdog {
+				return WatchdogFunc(func(ctx context.Context) {
 					atomic.AddInt32(&activeWatchdogs, 1)
-					<-stop
+					<-ctx.Done()
 					atomic.AddInt32(&activeWatchdogs, -1)
 				})
 			})
@@ -183,10 +182,8 @@ var _ = Describe("Sync", func() {
 	})
 })
 
-var _ util_watchdog.Watchdog = WatchdogFunc(nil)
+type WatchdogFunc func(ctx context.Context)
 
-type WatchdogFunc func(stop <-chan struct{})
-
-func (f WatchdogFunc) Start(stop <-chan struct{}) {
-	f(stop)
+func (f WatchdogFunc) Start(ctx context.Context) {
+	f(ctx)
 }
diff --git a/test/framework/debug.go b/test/framework/debug.go
index ed6572933..7b9bdfb56 100644
--- a/test/framework/debug.go
+++ b/test/framework/debug.go
@@ -2,12 +2,10 @@ package framework
 
 import (
 	"fmt"
-	"io"
 	"os"
 	"path/filepath"
 	"slices"
 	"strings"
-	"syscall"
 
 	"github.com/google/uuid"
 	"github.com/gruntwork-io/terratest/modules/k8s"
@@ -34,34 +32,31 @@ func DebugUniversal(cluster Cluster, mesh string) {
 	kumactlOpts := *cluster.GetKumactlOptions()
 	kumactlOpts.Verbose = false
 
-	seenErrors := []bool{
+	errs := slices.Concat(
 		debugUniversalCopyLogs(debugDir),
 		debugUniversalExport(cluster, mesh, debugDir, kumactlOpts),
 		debugUniversalInspectDPs(cluster, mesh, debugDir, kumactlOpts),
-	}
+	)
 
-	if slices.Contains(seenErrors, true) {
-		Logf("[WARNING]: some debug commands failed")
+	for _, err := range errs {
+		Logf("[WARNING]: %s", err)
 	}
 }
 
-func debugUniversalCopyLogs(debugPath string) bool {
-	currPath := universal_logs.GetLogsPath(
+func debugUniversalCopyLogs(debugPath string) []error {
+	srcPath := universal_logs.GetLogsPath(
 		ginkgo.CurrentSpecReport(),
 		Config.UniversalE2ELogsPath,
 	).Describe
-	copyPath := filepath.Join(debugPath, "logs")
+	destPath := filepath.Join(debugPath, "logs")
 
-	logMsg := fmt.Sprintf("copying logs from %q to %q", currPath, copyPath)
+	Logf("copying logs from %q to %q", srcPath, destPath)
 
-	Logf(logMsg)
-
-	if err := CopyDirectory(currPath, copyPath); err != nil {
-		Logf("%s failed with error: %s", logMsg, err)
-		return true
+	if err := os.CopyFS(destPath, os.DirFS(srcPath)); err != nil {
+		return []error{errors.Wrapf(err, "failed to copy logs from %q to %q", srcPath, destPath)}
 	}
 
-	return false
+	return nil
 }
 
 func debugUniversalExport(
@@ -69,33 +64,28 @@ func debugUniversalExport(
 	mesh string,
 	debugPath string,
 	kumactlOpts kumactl.KumactlOptions,
-) bool {
-	var errorSeen bool
+) []error {
+	var errs []error
 
 	filePath := filepath.Join(
 		debugPath,
 		fmt.Sprintf("%s-export.yaml", cluster.Name()),
 	)
 
-	logMsg := fmt.Sprintf("saving export of cluster %q for mesh %q to a file %q", cluster.Name(), mesh, filePath)
-
-	Logf(logMsg)
+	Logf("saving export for cluster %q and mesh %q to file %q", cluster.Name(), mesh, filePath)
 
 	out, err := kumactlOpts.RunKumactlAndGetOutput("export", "--profile", "all")
 	if err != nil {
-		// We don't want to fail in the middle.
-		errorSeen = true
-		msg := fmt.Sprintf("kumactl export --profile all failed with error: %s", err)
-		out = fmt.Sprintf("# %s", msg)
-		Logf(fmt.Sprintf("%s failed: %s", logMsg, msg))
+		wrappedErr := errors.Wrap(err, "failed to run 'kumactl export --profile all'")
+		errs = append(errs, wrappedErr)
+		out = fmt.Sprintf("# export failed: %s", wrappedErr)
 	}
 
 	if err := os.WriteFile(filePath, []byte(out), 0o600); err != nil {
-		errorSeen = true
-		Logf("%s failed with error: %s", logMsg, err)
+		errs = append(errs, errors.Wrapf(err, "failed to write export to file %q", filePath))
 	}
 
-	return errorSeen
+	return errs
 }
 
 func debugUniversalInspectDPs(
@@ -103,8 +93,8 @@ func debugUniversalInspectDPs(
 	mesh string,
 	debugPath string,
 	kumactlOpts kumactl.KumactlOptions,
-) bool {
-	var errorSeen bool
+) []error {
+	var errs []error
 
 	Logf("saving dataplane inspections from cluster %q for mesh %q", cluster.Name(), mesh)
 
@@ -125,10 +115,9 @@ func debugUniversalInspectDPs(
 				"--type", typ,
 			); err != nil {
 				// We don't want to fail in the middle.
-				errorSeen = true
-				msg := fmt.Sprintf("kumactl inspect dataplane %s --mesh %s --type %s failed with error: %s", dpName, mesh, typ, err)
-				out = fmt.Sprintf("%q", msg)
-				Logf(msg)
+				err := errors.Wrapf(err, "kumactl inspect dataplane %s --mesh %s --type %s failed", dpName, mesh, typ)
+				errs = append(errs, err)
+				out = fmt.Sprintf("%q", err)
 			}
 
 			filePath := filepath.Join(
@@ -137,13 +126,12 @@ func debugUniversalInspectDPs(
 			)
 
 			if err := os.WriteFile(filePath, []byte(out), 0o600); err != nil {
-				errorSeen = true
-				Logf("saving %q for dataplane %q to file %q failed with error: %s", typ, dpName, filePath, err)
+				errs = append(errs, errors.Wrapf(err, "failed to write file %q", filePath))
 			}
 		}
 	}
 
-	return errorSeen
+	return errs
 }
 
 func DebugKube(cluster Cluster, mesh string, namespaces ...string) {
@@ -239,114 +227,3 @@ func CpRestarted(cluster Cluster) bool {
 		return false
 	}
 }
-
-// When we'll update our package to Go 1.23, below helper functions are can be replaced with
-// err = os.CopyFS(destDir, os.DirFS(srcDir))
-// ref#1. https://stackoverflow.com/a/56314145
-// ref#2. https://github.com/golang/go/issues/62484
-
-func CopyDirectory(scrDir, dest string) error {
-	entries, err := os.ReadDir(scrDir)
-	if err != nil {
-		return err
-	}
-	for _, entry := range entries {
-		sourcePath := filepath.Join(scrDir, entry.Name())
-		destPath := filepath.Join(dest, entry.Name())
-
-		fileInfo, err := os.Stat(sourcePath)
-		if err != nil {
-			return err
-		}
-
-		stat, ok := fileInfo.Sys().(*syscall.Stat_t)
-		if !ok {
-			return errors.Errorf("failed to get raw syscall.Stat_t data for '%s'", sourcePath)
-		}
-
-		switch fileInfo.Mode() & os.ModeType {
-		case os.ModeDir:
-			if err := CreateIfNotExists(destPath, 0o755); err != nil {
-				return err
-			}
-			if err := CopyDirectory(sourcePath, destPath); err != nil {
-				return err
-			}
-		case os.ModeSymlink:
-			if err := CopySymLink(sourcePath, destPath); err != nil {
-				return err
-			}
-		default:
-			if err := Copy(sourcePath, destPath); err != nil {
-				return err
-			}
-		}
-
-		if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
-			return err
-		}
-
-		fInfo, err := entry.Info()
-		if err != nil {
-			return err
-		}
-
-		isSymlink := fInfo.Mode()&os.ModeSymlink != 0
-		if !isSymlink {
-			if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func Copy(srcFile, dstFile string) error {
-	out, err := os.Create(dstFile)
-	if err != nil {
-		return err
-	}
-
-	defer out.Close()
-
-	in, err := os.Open(srcFile)
-	if err != nil {
-		return err
-	}
-
-	defer in.Close()
-
-	if _, err = io.Copy(out, in); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func Exists(filePath string) bool {
-	if _, err := os.Stat(filePath); os.IsNotExist(err) {
-		return false
-	}
-
-	return true
-}
-
-func CreateIfNotExists(dir string, perm os.FileMode) error {
-	if Exists(dir) {
-		return nil
-	}
-
-	if err := os.MkdirAll(dir, perm); err != nil {
-		return errors.Wrapf(err, "failed to create directory: '%s'", dir)
-	}
-
-	return nil
-}
-
-func CopySymLink(source, dest string) error {
-	link, err := os.Readlink(source)
-	if err != nil {
-		return err
-	}
-	return os.Symlink(link, dest)
-}