Repository: kubernetes-sigs/kwok

Issue:
kube-scheduler crash: "clientConnection" already set in map
### How to use it?

- [ ] kwok
- [x] kwokctl --runtime=docker (default runtime)
- [ ] kwokctl --runtime=binary
- [ ] kwokctl --runtime=nerdctl
- [ ] kwokctl --runtime=kind

### What happened?

kwokctl-config.yaml:
```yaml
apiVersion: config.kwok.x-k8s.io/v1alpha1
kind: KwokctlConfiguration
metadata:
  creationTimestamp: null
componentsPatches:
- name: kube-apiserver
  extraArgs:
  - key: max-requests-inflight
    value: "10000"
  - key: max-mutating-requests-inflight
    value: "5000"
  - key: request-timeout
    value: 300s
  - key: min-request-timeout
    value: "180"
- name: kube-scheduler
  extraArgs:
  - key: bind-address
    value: 0.0.0.0
    override: true
  - key: secure-port
    value: "10259"
    override: true
  - key: authentication-always-allow-paths
    value: /metrics
    override: true
options:
  bindAddress: 0.0.0.0
  cacheDir: /root/.kwok/cache
  components:
  - etcd
  - kube-apiserver
  - kube-controller-manager
  - kube-scheduler
  - kwok-controller
  dashboardImage: docker.io/kubernetesui/dashboard:v2.7.0
  dashboardMetricsScraperImage: docker.io/kubernetesui/metrics-scraper:v1.0.9
  dashboardMetricsScraperVersion: v1.0.9
  dashboardVersion: v2.7.0
  disableKubeControllerManager: false
  disableKubeScheduler: false
  disableQPSLimits: false
  enableMetricsServer: false
  etcdBinary: https://github.com/etcd-io/etcd/releases/download/v3.5.21/etcd-v3.5.21-linux-amd64.tar.gz#etcd
  etcdImage: registry.k8s.io/etcd:3.5.21-0
  etcdPrefix: /registry
  etcdQuotaBackendSize: 8Gi
  etcdVersion: 3.5.21-0
  etcdctlBinary: https://github.com/etcd-io/etcd/releases/download/v3.5.21/etcd-v3.5.21-linux-amd64.tar.gz#etcdctl
  heartbeatFactor: 5
  jaegerBinary: https://github.com/jaegertracing/jaeger/releases/download/v1.69.0/jaeger-1.69.0-linux-amd64.tar.gz#jaeger-all-in-one
  jaegerImage: docker.io/jaegertracing/all-in-one:1.69.0
  jaegerVersion: v1.69.0
  kectlBinary: https://github.com/kwok-ci/kectl/releases/download/v0.0.8/kectl_linux_amd64
  kectlVersion: 0.0.8
  kindBinary: https://github.com/kubernetes-sigs/kind/releases/download/v0.27.0/kind-linux-amd64
  kindNodeImage: docker.io/kindest/node:v1.32.3
  kindVersion: v0.27.0
  kubeAdmission: true
  kubeApiserverBinary: https://dl.k8s.io/release/v1.32.3/bin/linux/amd64/kube-apiserver
  kubeApiserverImage: registry.k8s.io/kube-apiserver:v1.32.3
  kubeAuthorization: true
  kubeControllerManagerBinary: https://dl.k8s.io/release/v1.32.3/bin/linux/amd64/kube-controller-manager
  kubeControllerManagerImage: registry.k8s.io/kube-controller-manager:v1.32.3
  kubeControllerManagerNodeMonitorGracePeriodMilliseconds: 40000
  kubeControllerManagerNodeMonitorPeriodMilliseconds: 5000
  kubeSchedulerBinary: https://dl.k8s.io/release/v1.32.3/bin/linux/amd64/kube-scheduler
  kubeSchedulerImage: registry.k8s.io/kube-scheduler:v1.32.3
  kubeSchedulerConfig: ./scheduler-config.yaml
  kubeVersion: v1.32.3
  kubectlBinary: https://dl.k8s.io/release/v1.32.3/bin/linux/amd64/kubectl
  kubectlImage: registry.k8s.io/kubectl:v1.32.3
  kwokControllerBinary: https://github.com/kubernetes-sigs/kwok/releases/download/v0.7.0/kwok-linux-amd64
  kwokControllerImage: registry.k8s.io/kwok/kwok:v0.7.0
  kwokVersion: v0.7.0
  metricsServerBinary: https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/metrics-server-linux-amd64
  metricsServerImage: registry.k8s.io/metrics-server/metrics-server:v0.7.2
  metricsServerVersion: v0.7.2
  nodeLeaseDurationSeconds: 40
  nodeStatusUpdateFrequencyMilliseconds: 10000
  prometheusBinary: https://github.com/prometheus/prometheus/releases/download/v3.3.1/prometheus-3.3.1.linux-amd64.tar.gz#prometheus
  prometheusImage: docker.io/prom/prometheus:v3.3.1
  prometheusVersion: v3.3.1
  quietPull: false
  runtimes:
  - docker
  - podman
  - nerdctl
  - binary
  securePort: true
status: {}
```
scheduler-config.yaml:
```yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
  qps: 2000
  burst: 4000
percentageOfNodesToScore: 1   
parallelism: 128        

profiles:
- schedulerName: 2-scheduler
  plugins:
    score:
      disabled:
      - name: "*"
    filter:
      disabled:
      - name: "*"
      enabled:
      - name: NodeResourcesFit 
      - name: NodePorts        
      - name: TaintToleration  
  pluginConfig:
  - name: NodeResourcesFit
    args:
```
Setup cluster command:
```bash
$ ./kwokctl create cluster --config=./kwokctl-config.yaml --name=demo
$ docker ps
CONTAINER ID   IMAGE                                             COMMAND                   CREATED          STATUS                          PORTS                                         NAMES
dedb89e6cc7a   registry.k8s.io/kube-controller-manager:v1.32.3   "kube-controller-man…"   11 minutes ago   Up 11 minutes                                                                 kwok-demo-kube-controller-manager
356935657fb0   registry.k8s.io/kwok/kwok:v0.7.0                  "kwok --manage-all-n…"   11 minutes ago   Up 11 minutes                                                                 kwok-demo-kwok-controller
12cfefa67e0e   registry.k8s.io/kube-scheduler:v1.32.3            "kube-scheduler --co…"   11 minutes ago   Restarting (1) 23 seconds ago                                                 kwok-demo-kube-scheduler
c67e772194cc   registry.k8s.io/kube-apiserver:v1.32.3            "kube-apiserver --et…"   11 minutes ago   Up 11 minutes                   0.0.0.0:32766->6443/tcp, :::32766->6443/tcp   kwok-demo-kube-apiserver
42e8598fa95b   registry.k8s.io/etcd:3.5.21-0                     "etcd --name=node0 -…"   11 minutes ago   Up 11 minutes                   2379-2380/tcp, 4001/tcp, 7001/tcp             kwok-demo-etcd
```
kube-scheduler logs are as follows:
```bash
$ docker logs -f kwok-demo-kube-scheduler
I0812 02:40:56.775068       1 serving.go:386] Generated self-signed cert in-memory
E0812 02:40:56.775796       1 run.go:72] "command failed" err=<
        strict decoding error: yaml: unmarshal errors:
          line 28: key "clientConnection" already set in map
 >
I0812 02:40:57.778062       1 serving.go:386] Generated self-signed cert in-memory
E0812 02:40:57.778738       1 run.go:72] "command failed" err=<
        strict decoding error: yaml: unmarshal errors:
          line 28: key "clientConnection" already set in map

```

check local rendered scheduler.yaml:
```bash
$ cat /root/.kwok/clusters/demo/scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
  qps: 2000
  burst: 4000
percentageOfNodesToScore: 1 
parallelism: 128       

profiles:
- schedulerName: 2-scheduler
  plugins:
    score:
      disabled:
      - name: "*"
    filter:
      disabled:
      - name: "*"
      enabled:
      - name: NodeResourcesFit  
      - name: NodePorts     
      - name: TaintToleration  
  pluginConfig:
  - name: NodeResourcesFit
    args:


clientConnection:
  kubeconfig: "/root/.kube/config"

```

There are two `clientConnection` field. This is a bug?

I see these code:
```golang
// CopySchedulerConfig copies the scheduler configuration file to the given path.
func (c *Cluster) CopySchedulerConfig(oldpath, newpath, kubeconfig string) error {
	err := c.CopyFile(oldpath, newpath)
	if err != nil {
		return err
	}

	err = c.AppendToFile(newpath, []byte(fmt.Sprintf(`
clientConnection:
  kubeconfig: %q
`, kubeconfig)))
	if err != nil {
		return err
	}

	return nil
}
```

```

### What did you expect to happen?

Can render in kube-scheduler-config yaml normally

### How can we reproduce it (as minimally and precisely as possible)?

Reproduce steps are as above

### Anything else we need to know?

None

### Kwok version

<details>

```console
$ ./kwokctl --version
kwokctl version v0.7.0 go1.24.0 (linux/amd64)
```

</details>


### OS version

<details>
```console
# On Linux:
$ cat /etc/os-release
NAME="Alibaba Cloud Linux"
VERSION="3 (Soaring Falcon)"
ID="alinux"
ID_LIKE="rhel fedora centos anolis"
VERSION_ID="3"
UPDATE_ID="9"
PLATFORM_ID="platform:al8"
PRETTY_NAME="Alibaba Cloud Linux 3 (Soaring Falcon)"
ANSI_COLOR="0;31"
HOME_URL="https://www.aliyun.com/"
$ uname -a
Linux node01 5.10.134-16.3.al8.x86_64 #1 SMP Tue Mar 26 18:54:05 CST 2024 x86_64 x86_64 x86_64 GNU/Linux
```
</details>


Discussion:
@wzshiming: You can try using passing qps/burst in extraArgs as a workaround

/assign

Solution patch:
diff --git a/go.mod b/go.mod
index 988e6089..2dda1d64 100644
--- a/go.mod
+++ b/go.mod
@@ -41,6 +41,7 @@ require (
 	k8s.io/cri-api v0.33.0
 	k8s.io/cri-client v0.33.0
 	k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
+	k8s.io/kube-scheduler v0.33.0
 	k8s.io/kubelet v0.33.0
 	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
 	sigs.k8s.io/controller-runtime v0.19.0
diff --git a/go.sum b/go.sum
index 71c2f586..3e95b16c 100644
--- a/go.sum
+++ b/go.sum
@@ -357,6 +357,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
+k8s.io/kube-scheduler v0.33.0 h1:G5psn7ynB5Vjfo0ia8uz32Gv46TRa5RgTq4PTm+yjR0=
+k8s.io/kube-scheduler v0.33.0/go.mod h1:I+0aqwJ3G9f9pyfKfoN5b0uP9M6MinNpxXRlCXkM17E=
 k8s.io/kubelet v0.33.0 h1:4pJA2Ge6Rp0kDNV76KH7pTBiaV2T1a1874QHMcubuSU=
 k8s.io/kubelet v0.33.0/go.mod h1:iDnxbJQMy9DUNaML5L/WUlt3uJtNLWh7ZAe0JSp4Yi0=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
diff --git a/pkg/kwok/server/debugging_port_forword.go b/pkg/kwok/server/debugging_port_forword.go
index 73a37880..73ab5070 100644
--- a/pkg/kwok/server/debugging_port_forword.go
+++ b/pkg/kwok/server/debugging_port_forword.go
@@ -23,6 +23,7 @@ import (
 	"io"
 	"net"
 	"net/http"
+	"strconv"
 	"strings"
 
 	"github.com/emicklei/go-restful/v3"
@@ -57,10 +58,10 @@ func (s *Server) PortForward(ctx context.Context, name string, uid types.UID, po
 		return exec.Exec(exec.WithReadWriter(ctx, stream), forward.Command[0], forward.Command[1:]...)
 	}
 
-	if forward.Target != nil {
-		target := forward.Target
-		addr := fmt.Sprintf("%s:%d", target.Address, target.Port)
-		dial, err := net.Dial("tcp", addr)
+	if target := forward.Target; target != nil {
+		addr := net.JoinHostPort(target.Address, strconv.FormatInt(int64(target.Port), 10))
+		var dialer net.Dialer
+		dial, err := dialer.DialContext(ctx, "tcp", addr)
 		if err != nil {
 			return fmt.Errorf("failed to dial %s: %w", addr, err)
 		}
@@ -68,7 +69,6 @@ func (s *Server) PortForward(ctx context.Context, name string, uid types.UID, po
 			_ = dial.Close()
 		}()
 
-		// TODO: remove this when upgrade to go 1.21 upgrade takes place
 		buf1 := s.bufPool.Get()
 		buf2 := s.bufPool.Get()
 		defer func() {
diff --git a/pkg/kwokctl/runtime/scheduler.go b/pkg/kwokctl/runtime/scheduler.go
index f23701aa..ea0c55da 100644
--- a/pkg/kwokctl/runtime/scheduler.go
+++ b/pkg/kwokctl/runtime/scheduler.go
@@ -18,21 +18,49 @@ package runtime
 
 import (
 	"fmt"
+	"os"
+
+	v1 "k8s.io/kube-scheduler/config/v1"
+	"sigs.k8s.io/kwok/pkg/utils/yaml"
 )
 
 // CopySchedulerConfig copies the scheduler configuration file to the given path.
 func (c *Cluster) CopySchedulerConfig(oldpath, newpath, kubeconfig string) error {
-	err := c.CopyFile(oldpath, newpath)
+	var config v1.KubeSchedulerConfiguration
+
+	data, err := os.ReadFile(oldpath)
+	if err != nil {
+		return fmt.Errorf("failed to read file %s: %w", oldpath, err)
+	}
+
+	err = yaml.Unmarshal(data, &config)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshal YAML from %s: %w", oldpath, err)
+	}
+
+	expectedAPIVersion := v1.SchemeGroupVersion.String()
+	if config.APIVersion != expectedAPIVersion {
+		return fmt.Errorf("invalid apiVersion in scheduler configuration at %s: expected %s, got %s", oldpath, expectedAPIVersion, config.APIVersion)
+	}
+
+	expectedKind := "KubeSchedulerConfiguration"
+	if config.Kind != expectedKind {
+		return fmt.Errorf("invalid kind in scheduler configuration at %s: expected %s, got %s", oldpath, expectedKind, config.Kind)
+	}
+
+	if config.ClientConnection.Kubeconfig != "" {
+		return fmt.Errorf("kubeconfig already exists in scheduler configuration at %s", oldpath)
+	}
+
+	config.ClientConnection.Kubeconfig = kubeconfig
+	updatedData, err := yaml.Marshal(config)
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to marshal YAML: %w", err)
 	}
 
-	err = c.AppendToFile(newpath, []byte(fmt.Sprintf(`
-clientConnection:
-  kubeconfig: %q
-`, kubeconfig)))
+	err = c.WriteFile(newpath, updatedData)
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to write file %s: %w", newpath, err)
 	}
 
 	return nil



Test patch:
diff --git a/test/e2e/kwokctl/dryrun/testdata/binary/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/binary/create_cluster_with_verbosity.txt
index 6a96e984..699f6915 100644
--- a/test/e2e/kwokctl/dryrun/testdata/binary/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/binary/create_cluster_with_verbosity.txt
@@ -17,10 +17,23 @@ samplingRatePerMillion: 1000000
 EOF
 # Download https://dl.k8s.io/release/v1.33.0/bin/<OS>/<ARCH>/kube-controller-manager to <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/bin/kube-controller-manager
 # Download https://dl.k8s.io/release/v1.33.0/bin/<OS>/<ARCH>/kube-scheduler to <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/bin/kube-scheduler
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "<ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/kubeconfig"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/kubeconfig
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 # Download https://github.com/kubernetes-sigs/kwok/releases/download/v0.8.0/kwok-<OS>-<ARCH> to <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/bin/kwok-controller
 # Download https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/metrics-server-<OS>-<ARCH> to <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/bin/metrics-server
diff --git a/test/e2e/kwokctl/dryrun/testdata/docker/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/docker/create_cluster_with_verbosity.txt
index afae9b59..96708da7 100644
--- a/test/e2e/kwokctl/dryrun/testdata/docker/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/docker/create_cluster_with_verbosity.txt
@@ -16,10 +16,23 @@ samplingRatePerMillion: 1000000
 EOF
 docker pull registry.k8s.io/kubectl:v1.33.0
 docker pull registry.k8s.io/kube-controller-manager:v1.33.0
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "~/.kube/config"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: ~/.kube/config
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 docker pull registry.k8s.io/kube-scheduler:v1.33.0
 docker pull registry.k8s.io/kwok/kwok:v0.8.0
diff --git a/test/e2e/kwokctl/dryrun/testdata/kind-podman/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/kind-podman/create_cluster_with_verbosity.txt
index cd7c02af..40846f61 100644
--- a/test/e2e/kwokctl/dryrun/testdata/kind-podman/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/kind-podman/create_cluster_with_verbosity.txt
@@ -9,10 +9,23 @@ mkdir -p <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/manifests
 mkdir -p <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/logs
 touch <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/logs/audit.log
 cp <ROOT_DIR>/test/kwokctl/audit-policy.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/audit.yaml
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "/etc/kubernetes/scheduler.conf"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: /etc/kubernetes/scheduler.conf
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/apiserver-tracing-config.yaml
 apiVersion: apiserver.config.k8s.io/v1alpha1
diff --git a/test/e2e/kwokctl/dryrun/testdata/kind/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/kind/create_cluster_with_verbosity.txt
index 08068c02..3caf39d8 100644
--- a/test/e2e/kwokctl/dryrun/testdata/kind/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/kind/create_cluster_with_verbosity.txt
@@ -9,10 +9,23 @@ mkdir -p <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/manifests
 mkdir -p <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/logs
 touch <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/logs/audit.log
 cp <ROOT_DIR>/test/kwokctl/audit-policy.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/audit.yaml
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "/etc/kubernetes/scheduler.conf"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: /etc/kubernetes/scheduler.conf
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/apiserver-tracing-config.yaml
 apiVersion: apiserver.config.k8s.io/v1alpha1
diff --git a/test/e2e/kwokctl/dryrun/testdata/nerdctl/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/nerdctl/create_cluster_with_verbosity.txt
index 187743c4..f818c731 100644
--- a/test/e2e/kwokctl/dryrun/testdata/nerdctl/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/nerdctl/create_cluster_with_verbosity.txt
@@ -16,10 +16,23 @@ samplingRatePerMillion: 1000000
 EOF
 nerdctl pull registry.k8s.io/kubectl:v1.33.0
 nerdctl pull registry.k8s.io/kube-controller-manager:v1.33.0
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "~/.kube/config"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: ~/.kube/config
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 nerdctl pull registry.k8s.io/kube-scheduler:v1.33.0
 nerdctl pull registry.k8s.io/kwok/kwok:v0.8.0
diff --git a/test/e2e/kwokctl/dryrun/testdata/podman/create_cluster_with_verbosity.txt b/test/e2e/kwokctl/dryrun/testdata/podman/create_cluster_with_verbosity.txt
index 573a0ee1..2531062e 100644
--- a/test/e2e/kwokctl/dryrun/testdata/podman/create_cluster_with_verbosity.txt
+++ b/test/e2e/kwokctl/dryrun/testdata/podman/create_cluster_with_verbosity.txt
@@ -16,10 +16,23 @@ samplingRatePerMillion: 1000000
 EOF
 podman pull registry.k8s.io/kubectl:v1.33.0
 podman pull registry.k8s.io/kube-controller-manager:v1.33.0
-cp <ROOT_DIR>/test/kwokctl/scheduler-config.yaml <ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
-cat <<EOF >><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+cat <<EOF ><ROOT_DIR>/workdir/clusters/<CLUSTER_NAME>/scheduler.yaml
+apiVersion: kubescheduler.config.k8s.io/v1
 clientConnection:
-  kubeconfig: "~/.kube/config"
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  kubeconfig: ~/.kube/config
+  qps: 0
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s
 EOF
 podman pull registry.k8s.io/kube-scheduler:v1.33.0
 podman pull registry.k8s.io/kwok/kwok:v0.8.0
diff --git a/test/kwokctl/scheduler-config.yaml b/test/kwokctl/scheduler-config.yaml
new file mode 100644
index 00000000..18780e8d
--- /dev/null
+++ b/test/kwokctl/scheduler-config.yaml
@@ -0,0 +1,15 @@
+apiVersion: kubescheduler.config.k8s.io/v1
+kind: KubeSchedulerConfiguration
+clientConnection:
+  acceptContentTypes: ""
+  burst: 0
+  contentType: ""
+  qps: 0
+leaderElection:
+  leaderElect: null
+  leaseDuration: 0s
+  renewDeadline: 0s
+  resourceLock: ""
+  resourceName: ""
+  resourceNamespace: ""
+  retryPeriod: 0s