Skip to content

Commit c09cafe

Browse files
authored
Merge pull request #23319 from bobsira/refactor/node-provisioner-linux
refactor: add NodeProvisioner interface with linuxNodeProvisioner
2 parents ae5ae57 + 0592619 commit c09cafe

4 files changed

Lines changed: 158 additions & 34 deletions

File tree

pkg/minikube/config/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ type Node struct {
155155
Worker bool
156156
}
157157

158+
// Role returns the node role string for logging and error messages.
159+
func (n *Node) Role() string {
160+
if n.ControlPlane {
161+
return "control-plane"
162+
}
163+
return "worker"
164+
}
165+
158166
// VersionedExtraOption holds information on flags to apply to a specific range
159167
// of versions
160168
type VersionedExtraOption struct {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package node
18+
19+
import (
20+
"fmt"
21+
"os/exec"
22+
"time"
23+
24+
"k8s.io/klog/v2"
25+
"k8s.io/minikube/pkg/minikube/bootstrapper"
26+
"k8s.io/minikube/pkg/minikube/bootstrapper/bsutil"
27+
"k8s.io/minikube/pkg/util/retry"
28+
)
29+
30+
type linuxProvisioner struct {
31+
// starter holds the node and cluster configuration for this provisioning workflow.
32+
starter Starter
33+
// controlplane is the bootstrapper for the control-plane node, used to generate join tokens.
34+
controlplane bootstrapper.Bootstrapper
35+
// worker is the bootstrapper for the target node being joined, used to execute join commands.
36+
worker bootstrapper.Bootstrapper
37+
}
38+
39+
// Compile-time assertion that linuxProvisioner implements the Provisioner interface.
40+
var _ Provisioner = (*linuxProvisioner)(nil)
41+
42+
func (p *linuxProvisioner) Join() error {
43+
joinCmd, err := p.controlplane.GenerateToken(*p.starter.Cfg)
44+
if err != nil {
45+
return fmt.Errorf("error generating join token: %w", err)
46+
}
47+
48+
join := func() error {
49+
klog.Infof("trying to join %s node %q to cluster: %+v", p.starter.Node.Role(), p.starter.Node.Name, p.starter.Node)
50+
if err := p.worker.JoinCluster(*p.starter.Cfg, *p.starter.Node, joinCmd); err != nil {
51+
klog.Errorf("%s node failed to join cluster, will retry: %v", p.starter.Node.Role(), err)
52+
53+
klog.Infof("resetting %s node %q before attempting to rejoin cluster...", p.starter.Node.Role(), p.starter.Node.Name)
54+
kubeadmBinary := bsutil.KubeadmCmdWithPath(p.starter.Cfg.KubernetesConfig.KubernetesVersion)
55+
cmd := exec.Command("sudo", "/bin/bash", "-c", fmt.Sprintf("%s reset --force", kubeadmBinary))
56+
if _, err := p.starter.Runner.RunCmd(cmd); err != nil {
57+
klog.Infof("kubeadm reset failed, continuing anyway: %v", err)
58+
} else {
59+
klog.Infof("successfully reset %s node %q", p.starter.Node.Role(), p.starter.Node.Name)
60+
}
61+
62+
return err
63+
}
64+
return nil
65+
}
66+
if err := retry.Expo(join, 10*time.Second, 3*time.Minute); err != nil {
67+
return fmt.Errorf("error joining %s node %q to cluster: %w", p.starter.Node.Role(), p.starter.Node.Name, err)
68+
}
69+
70+
return nil
71+
}
72+
73+
func (p *linuxProvisioner) LabelAndUntaint() error {
74+
if err := p.controlplane.LabelAndUntaintNode(*p.starter.Cfg, *p.starter.Node); err != nil {
75+
return fmt.Errorf("error applying %s node %q label: %w", p.starter.Node.Role(), p.starter.Node.Name, err)
76+
}
77+
return nil
78+
}

pkg/minikube/node/provisioner.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package node
18+
19+
// Provisioner encapsulates OS-specific node lifecycle operations using a strategy pattern.
20+
// Different OS implementations (Linux, Windows) use the same interface but with different
21+
// join mechanisms and readiness verification strategies.
22+
// This eliminates scattered OS conditionals from shared orchestration code.
23+
//
24+
// Note: This interface is minimal and grows incrementally as new provisioner implementations
25+
// are added. The complete lifecycle will eventually include PreBootstrap and PostJoin methods,
26+
// but they are added only when a second provisioner (Windows) needs them. This follows the
27+
// principle of not building speculative abstractions.
28+
type Provisioner interface {
29+
// Join generates the kubeadm join command and executes it to add the node to the cluster.
30+
// Handles retry logic with OS-specific failure recovery (e.g., kubeadm reset for Linux).
31+
// Preconditions: kubeadm binary present, network connectivity, cluster running
32+
// Side effects: joins node to cluster, writes kubeadm state to node
33+
//
34+
// Linux: Executes kubeadm join with exponential backoff retry. On failure, runs kubeadm reset
35+
// to clean up state before retrying. Recovery mechanism is synchronous and fast.
36+
//
37+
// Windows: Executes kubeadm join similarly but does not perform kubeadm reset on failure.
38+
// Windows nodes require additional API server registration time (handled by retry in LabelAndUntaint).
39+
Join() error
40+
41+
// LabelAndUntaint applies labels and removes taints after join is complete.
42+
// Preconditions: node registered with apiserver
43+
// Side effects: updates node labels and taints in etcd
44+
//
45+
// Linux: Single attempt to apply labels/taints. Node should be registered immediately
46+
// after kubeadm join returns.
47+
//
48+
// Windows: Retries labeling for up to 3 minutes as Windows nodes take extra time to register
49+
// with the API server after kubeadm join completes.
50+
LabelAndUntaint() error
51+
52+
// PostJoin performs OS-specific operations after node successfully joins the cluster.
53+
// Runs after LabelAndUntaint() completes. Used for CNI and network setup that must happen
54+
// after the node is labeled and integrated into the cluster.
55+
// Preconditions: node is labeled and registered with apiserver
56+
// Side effects: applies CNI manifests, configures network plugins
57+
//
58+
// Linux: No operation needed. CNI is configured externally or handled by control-plane addons.
59+
//
60+
// Windows: Applies Windows-specific CNI manifests (e.g., Flannel-Windows DaemonSet,
61+
// kube-proxy-windows DaemonSet) that are bundled in the minikube binary. These cannot
62+
// run on Linux nodes and must be applied only to Windows workers.
63+
// PostJoin() error
64+
}

pkg/minikube/node/start.go

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import (
4343
"k8s.io/minikube/pkg/libmachine"
4444
"k8s.io/minikube/pkg/libmachine/host"
4545
"k8s.io/minikube/pkg/minikube/bootstrapper"
46-
"k8s.io/minikube/pkg/minikube/bootstrapper/bsutil"
4746
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
4847
"k8s.io/minikube/pkg/minikube/bootstrapper/kubeadm"
4948
"k8s.io/minikube/pkg/minikube/cluster"
@@ -311,50 +310,25 @@ func joinCluster(starter Starter, cpBs bootstrapper.Bootstrapper, bs bootstrappe
311310
klog.Infof("duration metric: took %s to joinCluster", time.Since(start))
312311
}()
313312

314-
role := "worker"
315-
if starter.Node.ControlPlane {
316-
role = "control-plane"
317-
}
318-
319313
// avoid "error execution phase kubelet-start: a Node with name "<name>" and status "Ready" already exists in the cluster.
320314
// You must delete the existing Node or change the name of this new joining Node"
321315
if starter.PreExists {
322-
klog.Infof("removing existing %s node %q before attempting to rejoin cluster: %+v", role, starter.Node.Name, starter.Node)
316+
klog.Infof("removing existing %s node %q before attempting to rejoin cluster: %+v", starter.Node.Role(), starter.Node.Name, starter.Node)
323317
if _, err := teardown(*starter.Cfg, starter.Node.Name, options); err != nil {
324-
klog.Errorf("error removing existing %s node %q before rejoining cluster, will continue anyway: %v", role, starter.Node.Name, err)
318+
klog.Errorf("error removing existing %s node %q before rejoining cluster, will continue anyway: %v", starter.Node.Role(), starter.Node.Name, err)
325319
}
326-
klog.Infof("successfully removed existing %s node %q from cluster: %+v", role, starter.Node.Name, starter.Node)
320+
klog.Infof("successfully removed existing %s node %q from cluster: %+v", starter.Node.Role(), starter.Node.Name, starter.Node)
327321
}
328322

329-
joinCmd, err := cpBs.GenerateToken(*starter.Cfg)
330-
if err != nil {
331-
return fmt.Errorf("error generating join token: %w", err)
332-
}
333-
334-
join := func() error {
335-
klog.Infof("trying to join %s node %q to cluster: %+v", role, starter.Node.Name, starter.Node)
336-
if err := bs.JoinCluster(*starter.Cfg, *starter.Node, joinCmd); err != nil {
337-
klog.Errorf("%s node failed to join cluster, will retry: %v", role, err)
338-
339-
// reset node to revert any changes made by previous kubeadm init/join
340-
klog.Infof("resetting %s node %q before attempting to rejoin cluster...", role, starter.Node.Name)
341-
if _, err := starter.Runner.RunCmd(exec.Command("sudo", "/bin/bash", "-c", fmt.Sprintf("%s reset --force", bsutil.KubeadmCmdWithPath(starter.Cfg.KubernetesConfig.KubernetesVersion)))); err != nil {
342-
klog.Infof("kubeadm reset failed, continuing anyway: %v", err)
343-
} else {
344-
klog.Infof("successfully reset %s node %q", role, starter.Node.Name)
345-
}
323+
p := &linuxProvisioner{starter: starter, controlplane: cpBs, worker: bs}
346324

347-
return err
348-
}
349-
return nil
325+
if err := p.Join(); err != nil {
326+
return err
350327
}
351-
if err := retry.Expo(join, 10*time.Second, 3*time.Minute); err != nil {
352-
return fmt.Errorf("error joining %s node %q to cluster: %w", role, starter.Node.Name, err)
328+
if err := p.LabelAndUntaint(); err != nil {
329+
return err
353330
}
354331

355-
if err := cpBs.LabelAndUntaintNode(*starter.Cfg, *starter.Node); err != nil {
356-
return fmt.Errorf("error applying %s node %q label: %w", role, starter.Node.Name, err)
357-
}
358332
return nil
359333
}
360334

0 commit comments

Comments
 (0)