Repository: kubernetes/minikube (go)

Issue:
Add support for kubeadm v1beta4
### What Happened?

Version
```
❯ minikube version
minikube version: v1.34.0
commit: 210b148df93a80eb872ecbeb7e35281b3c582c61
```

Start Command
```
minikube start --driver=docker --nodes=6 --ha --dns-domain cluster.dev.local
...
stderr:
W1010 21:32:44.961863    1982 common.go:101] your configuration file uses a deprecated API spec: "kubeadm.k8s.io/v1beta3" (kind: "ClusterConfiguration"). Please use 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.
W1010 21:32:44.962186    1982 common.go:101] your configuration file uses a deprecated API spec: "kubeadm.k8s.io/v1beta3" (kind: "InitConfiguration"). Please use 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.
	[WARNING Swap]: swap is supported for cgroup v2 only. The kubelet must be properly configured to use swap. Please refer to https://kubernetes.io/docs/concepts/architecture/nodes/#swap-memory, or disable swap on the node
	[WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "modprobe: FATAL: Module configs not found in directory /lib/modules/6.8.0-45-generic\n", err: exit status 1
	[WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'
	[WARNING DirAvailable--var-lib-minikube-etcd]: /var/lib/minikube/etcd is not empty
```

Happy to work on the support if not already developed.

### Attach the log file

n/a

### Operating System

None

### Driver

None

Pull request:
Adds support for kubeadm.k8s.io/v1beta4 available since k8s v1.31
fixes #19788

Adds support for kubeadm.k8s.io/v1beta4 available since k8s v1.31

https://kubernetes.io/blog/2024/08/23/kubernetes-1-31-kubeadm-v1beta4/


Patch:
diff --git a/hack/update/kubernetes_version/templates/v1beta3/containerd-api-port.yaml b/hack/update/kubernetes_version/templates/v1beta4/containerd-api-port.yaml
similarity index 75%
rename from hack/update/kubernetes_version/templates/v1beta3/containerd-api-port.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/containerd-api-port.yaml
index 14e9c7d25..ab59a9854 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/containerd-api-port.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/containerd-api-port.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:12345
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.23.0
 networking:
   dnsDomain: cluster.local
diff --git a/hack/update/kubernetes_version/templates/v1beta3/containerd-pod-network-cidr.yaml b/hack/update/kubernetes_version/templates/v1beta4/containerd-pod-network-cidr.yaml
similarity index 75%
rename from hack/update/kubernetes_version/templates/v1beta3/containerd-pod-network-cidr.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/containerd-pod-network-cidr.yaml
index 5cace184d..92d646b65 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/containerd-pod-network-cidr.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/containerd-pod-network-cidr.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.23.0
 networking:
   dnsDomain: cluster.local
diff --git a/hack/update/kubernetes_version/templates/v1beta3/containerd.yaml b/hack/update/kubernetes_version/templates/v1beta4/containerd.yaml
similarity index 84%
rename from hack/update/kubernetes_version/templates/v1beta3/containerd.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/containerd.yaml
index 5508409d2..ddf161f5c 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/containerd.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/containerd.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,15 +14,17 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
     allocate-node-cidrs: "true"
diff --git a/hack/update/kubernetes_version/templates/v1beta3/crio-options-gates.yaml b/hack/update/kubernetes_version/templates/v1beta4/crio-options-gates.yaml
similarity index 85%
rename from hack/update/kubernetes_version/templates/v1beta3/crio-options-gates.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/crio-options-gates.yaml
index 63c7a40db..c64545a51 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/crio-options-gates.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/crio-options-gates.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,15 +14,17 @@ nodeRegistration:
   criSocket: unix:///var/run/crio/crio.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
     fail-no-swap: "true"
     feature-gates: "a=b"
 controllerManager:
diff --git a/hack/update/kubernetes_version/templates/v1beta3/crio.yaml b/hack/update/kubernetes_version/templates/v1beta4/crio.yaml
similarity index 84%
rename from hack/update/kubernetes_version/templates/v1beta3/crio.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/crio.yaml
index d14509774..db7de1556 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/crio.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/crio.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,15 +14,17 @@ nodeRegistration:
   criSocket: unix:///var/run/crio/crio.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
     allocate-node-cidrs: "true"
diff --git a/hack/update/kubernetes_version/templates/v1beta3/default.yaml b/hack/update/kubernetes_version/templates/v1beta4/default.yaml
similarity index 84%
rename from hack/update/kubernetes_version/templates/v1beta3/default.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/default.yaml
index 6ab06fb8c..731e2f8b3 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/default.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/default.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,15 +14,17 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
     allocate-node-cidrs: "true"
diff --git a/hack/update/kubernetes_version/templates/v1beta3/dns.yaml b/hack/update/kubernetes_version/templates/v1beta4/dns.yaml
similarity index 84%
rename from hack/update/kubernetes_version/templates/v1beta3/dns.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/dns.yaml
index 5e2ab1275..a283e91b6 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/dns.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/dns.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,15 +14,17 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
     allocate-node-cidrs: "true"
diff --git a/hack/update/kubernetes_version/templates/v1beta3/image-repository.yaml b/hack/update/kubernetes_version/templates/v1beta4/image-repository.yaml
similarity index 84%
rename from hack/update/kubernetes_version/templates/v1beta3/image-repository.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/image-repository.yaml
index d674355f4..067665bf4 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/image-repository.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/image-repository.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,16 +14,18 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 imageRepository: test/repo
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
     allocate-node-cidrs: "true"
diff --git a/hack/update/kubernetes_version/templates/v1beta3/options.yaml b/hack/update/kubernetes_version/templates/v1beta4/options.yaml
similarity index 70%
rename from hack/update/kubernetes_version/templates/v1beta3/options.yaml
rename to hack/update/kubernetes_version/templates/v1beta4/options.yaml
index 7f9ba3f12..3a66badf2 100644
--- a/hack/update/kubernetes_version/templates/v1beta3/options.yaml
+++ b/hack/update/kubernetes_version/templates/v1beta4/options.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,25 +14,33 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
-    fail-no-swap: "true"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "fail-no-swap"
+      value: "true"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    kube-api-burst: "32"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "kube-api-burst"
+      value: "32"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
-    scheduler-name: "mini-scheduler"
+    - name: "leader-elect"
+      value: "false"
+    - name: "scheduler-name"
+      value: "mini-scheduler"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -40,7 +48,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.23.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/ktmpl/v1beta4.go b/pkg/minikube/bootstrapper/bsutil/ktmpl/v1beta4.go
new file mode 100644
index 000000000..650a18737
--- /dev/null
+++ b/pkg/minikube/bootstrapper/bsutil/ktmpl/v1beta4.go
@@ -0,0 +1,112 @@
+/*
+Copyright 2024 The Kubernetes Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ktmpl
+
+import "text/template"
+
+// V1Beta4 is kubeadm config template for Kubernetes v1.31.0+
+var V1Beta4 = template.Must(template.New("configTmpl-v1beta4").Funcs(template.FuncMap{
+	"printMapInOrder": printMapInOrder,
+}).Parse(`apiVersion: kubeadm.k8s.io/v1beta4
+kind: InitConfiguration
+localAPIEndpoint:
+  advertiseAddress: {{.AdvertiseAddress}}
+  bindPort: {{.APIServerPort}}
+bootstrapTokens:
+  - groups:
+      - system:bootstrappers:kubeadm:default-node-token
+    ttl: 24h0m0s
+    usages:
+      - signing
+      - authentication
+nodeRegistration:
+  criSocket: {{if .CRISocket}}{{if .PrependCriSocketUnix}}unix://{{end}}{{.CRISocket}}{{else}}{{if .PrependCriSocketUnix}}unix://{{end}}/var/run/dockershim.sock{{end}}
+  name: "{{.NodeName}}"
+  kubeletExtraArgs:
+    - name: "node-ip"
+      value: "{{.NodeIP}}"
+  taints: []
+---
+apiVersion: kubeadm.k8s.io/v1beta4
+kind: ClusterConfiguration
+{{ if .ImageRepository}}imageRepository: {{.ImageRepository}}
+{{end}}{{range .ComponentOptions}}{{.Component}}:
+{{- range $k, $v := .Pairs }}
+  {{$k}}: {{$v}}
+{{- end}}
+  extraArgs:
+{{- range $key, $val := .ExtraArgs }}
+    - name: "{{$key}}"
+      value: "{{$val}}"
+{{- end}}
+{{end -}}
+{{if .FeatureArgs}}featureGates:
+{{range $i, $val := .FeatureArgs}}{{$i}}: {{$val}}
+{{end -}}{{end -}}
+certificatesDir: {{.CertDir}}
+clusterName: mk
+controlPlaneEndpoint: {{.ControlPlaneAddress}}:{{.APIServerPort}}
+etcd:
+  local:
+    dataDir: {{.EtcdDataDir}}
+    extraArgs:
+      - name: "proxy-refresh-interval"
+        value: "70000"
+{{- range $key, $val := .EtcdExtraArgs }}
+      - name: "{{$key}}"
+        value: "{{$val}}"
+{{- end}}
+kubernetesVersion: {{.KubernetesVersion}}
+networking:
+  dnsDomain: {{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}
+  podSubnet: "{{.PodSubnet }}"
+  serviceSubnet: {{.ServiceCIDR}}
+---
+apiVersion: kubelet.config.k8s.io/v1beta1
+kind: KubeletConfiguration
+authentication:
+  x509:
+    clientCAFile: {{.ClientCAFile}}
+cgroupDriver: {{.CgroupDriver}}
+{{- range $key, $val := .KubeletConfigOpts}}
+{{$key}}: {{$val}}
+{{- end}}
+clusterDomain: "{{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}"
+# disable disk resource management by default
+imageGCHighThresholdPercent: 100
+evictionHard:
+  nodefs.available: "0%"
+  nodefs.inodesFree: "0%"
+  imagefs.available: "0%"
+failSwapOn: false
+staticPodPath: {{.StaticPodPath}}{{if .ResolvConfSearchRegression}}
+resolvConf: /etc/kubelet-resolv.conf{{end}}
+---
+apiVersion: kubeproxy.config.k8s.io/v1alpha1
+kind: KubeProxyConfiguration
+clusterCIDR: "{{.PodSubnet }}"
+metricsBindAddress: 0.0.0.0:10249
+conntrack:
+  maxPerCore: 0
+# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_established"
+  tcpEstablishedTimeout: 0s
+# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_close"
+  tcpCloseWaitTimeout: 0s
+{{- range $i, $val := printMapInOrder .KubeProxyOptions ": " }}
+{{$val}}
+{{- end}}
+`))
diff --git a/pkg/minikube/bootstrapper/bsutil/kubeadm.go b/pkg/minikube/bootstrapper/bsutil/kubeadm.go
index 5edf5ffae..a65efb33d 100644
--- a/pkg/minikube/bootstrapper/bsutil/kubeadm.go
+++ b/pkg/minikube/bootstrapper/bsutil/kubeadm.go
@@ -172,7 +172,15 @@ func GenerateKubeadmYAML(cc config.ClusterConfig, n config.Node, r cruntime.Mana
 	if version.GTE(semver.MustParse("1.23.0")) {
 		configTmpl = ktmpl.V1Beta3
 	}
-	// TODO: support v1beta4 kubeadm config when released - refs: https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta4/ and https://github.com/kubernetes/kubeadm/issues/2890
+	// v1beta4 isn't required until v1.31.
+	if version.GTE(semver.MustParse("1.31.0")) {
+		// Support v1beta4 kubeadm config
+		// refs:
+		// - https://kubernetes.io/blog/2024/08/23/kubernetes-1-31-kubeadm-v1beta4/
+		// - https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta4/
+		// - https://github.com/kubernetes/kubeadm/issues/2890
+		configTmpl = ktmpl.V1Beta4
+	}
 
 	if version.GTE(semver.MustParse("1.24.0-alpha.2")) {
 		opts.PrependCriSocketUnix = true


Test patch:
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-api-port.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-api-port.yaml
index a7260d1af..030c81ea5 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-api-port.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-api-port.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:12345
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-pod-network-cidr.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-pod-network-cidr.yaml
index 2db4da310..93e18e13b 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-pod-network-cidr.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd-pod-network-cidr.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd.yaml
index d5dccaccd..99a619530 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/containerd.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///run/containerd/containerd.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio-options-gates.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio-options-gates.yaml
index da42aff37..a10667065 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio-options-gates.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio-options-gates.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,28 +14,39 @@ nodeRegistration:
   criSocket: unix:///var/run/crio/crio.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
-    fail-no-swap: "true"
-    feature-gates: "a=b"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "fail-no-swap"
+      value: "true"
+    - name: "feature-gates"
+      value: "a=b"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    feature-gates: "a=b"
-    kube-api-burst: "32"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "feature-gates"
+      value: "a=b"
+    - name: "kube-api-burst"
+      value: "32"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    feature-gates: "a=b"
-    leader-elect: "false"
-    scheduler-name: "mini-scheduler"
+    - name: "feature-gates"
+      value: "a=b"
+    - name: "leader-elect"
+      value: "false"
+    - name: "scheduler-name"
+      value: "mini-scheduler"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -43,7 +54,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio.yaml
index 819f15ce0..66a9097ca 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/crio.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///var/run/crio/crio.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/default.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/default.yaml
index df50e2e23..44b3314f3 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/default.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/default.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/dns.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/dns.yaml
index e9872a102..c11958b07 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/dns.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/dns.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,22 +14,27 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -37,7 +42,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: minikube.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/image-repository.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/image-repository.yaml
index 2e2d09403..8e0313c6c 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/image-repository.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/image-repository.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,23 +14,28 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 imageRepository: test/repo
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
+    - name: "leader-elect"
+      value: "false"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -38,7 +43,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local
diff --git a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/options.yaml b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/options.yaml
index f712250ab..59e60f78c 100644
--- a/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/options.yaml
+++ b/pkg/minikube/bootstrapper/bsutil/testdata/v1.31/options.yaml
@@ -1,4 +1,4 @@
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: InitConfiguration
 localAPIEndpoint:
   advertiseAddress: 1.1.1.1
@@ -14,25 +14,33 @@ nodeRegistration:
   criSocket: unix:///var/run/dockershim.sock
   name: "mk"
   kubeletExtraArgs:
-    node-ip: 1.1.1.1
+    - name: "node-ip"
+      value: "1.1.1.1"
   taints: []
 ---
-apiVersion: kubeadm.k8s.io/v1beta3
+apiVersion: kubeadm.k8s.io/v1beta4
 kind: ClusterConfiguration
 apiServer:
   certSANs: ["127.0.0.1", "localhost", "1.1.1.1"]
   extraArgs:
-    enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
-    fail-no-swap: "true"
+    - name: "enable-admission-plugins"
+      value: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
+    - name: "fail-no-swap"
+      value: "true"
 controllerManager:
   extraArgs:
-    allocate-node-cidrs: "true"
-    kube-api-burst: "32"
-    leader-elect: "false"
+    - name: "allocate-node-cidrs"
+      value: "true"
+    - name: "kube-api-burst"
+      value: "32"
+    - name: "leader-elect"
+      value: "false"
 scheduler:
   extraArgs:
-    leader-elect: "false"
-    scheduler-name: "mini-scheduler"
+    - name: "leader-elect"
+      value: "false"
+    - name: "scheduler-name"
+      value: "mini-scheduler"
 certificatesDir: /var/lib/minikube/certs
 clusterName: mk
 controlPlaneEndpoint: control-plane.minikube.internal:8443
@@ -40,7 +48,8 @@ etcd:
   local:
     dataDir: /var/lib/minikube/etcd
     extraArgs:
-      proxy-refresh-interval: "70000"
+      - name: "proxy-refresh-interval"
+        value: "70000"
 kubernetesVersion: v1.31.0
 networking:
   dnsDomain: cluster.local