Skip to content

Commit 921b69b

Browse files
authored
Merge pull request kubernetes#125582 from chrischdi/pr-kubeadm-kep-4471
kubeadm: implement ControlPlaneKubeletLocalMode
2 parents 4805074 + 038a948 commit 921b69b

File tree

5 files changed

+135
-7
lines changed

5 files changed

+135
-7
lines changed

cmd/kubeadm/app/cmd/join.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ func newCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command {
219219
joinRunner.AppendPhase(phases.NewControlPlanePreparePhase())
220220
joinRunner.AppendPhase(phases.NewCheckEtcdPhase())
221221
joinRunner.AppendPhase(phases.NewKubeletStartPhase())
222+
joinRunner.AppendPhase(phases.NewEtcdJoinPhase())
223+
joinRunner.AppendPhase(phases.NewKubeletWaitBootstrapPhase())
222224
joinRunner.AppendPhase(phases.NewControlPlaneJoinPhase())
223225
joinRunner.AppendPhase(phases.NewWaitControlPlanePhase())
224226

cmd/kubeadm/app/cmd/phases/join/controlplanejoin.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
2626
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
2727
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
28+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
2829
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
2930
markcontrolplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markcontrolplane"
3031
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
@@ -35,6 +36,11 @@ var controlPlaneJoinExample = cmdutil.Examples(`
3536
kubeadm join phase control-plane-join all
3637
`)
3738

39+
var etcdJoinExample = cmdutil.Examples(`
40+
# Joins etcd for a control plane instance
41+
kubeadm join phase control-plane-join-etcd all
42+
`)
43+
3844
func getControlPlaneJoinPhaseFlags(name string) []string {
3945
flags := []string{
4046
options.CfgPath,
@@ -73,13 +79,37 @@ func NewControlPlaneJoinPhase() workflow.Phase {
7379
}
7480
}
7581

82+
// NewEtcdJoinPhase creates a kubeadm workflow phase that implements joining etcd
83+
func NewEtcdJoinPhase() workflow.Phase {
84+
return workflow.Phase{
85+
Name: "etcd-join",
86+
Short: fmt.Sprintf("[EXPERIMENTAL] Join etcd for control plane nodes (only used when feature gate %s is enabled)", features.ControlPlaneKubeletLocalMode),
87+
Run: runEtcdPhase,
88+
Example: etcdJoinExample,
89+
InheritFlags: getControlPlaneJoinPhaseFlags("etcd"),
90+
ArgsValidator: cobra.NoArgs,
91+
// TODO: unhide this phase once ControlPlaneKubeletLocalMode goes GA:
92+
// https://github.com/kubernetes/enhancements/issues/4471
93+
Hidden: true,
94+
// Only run this phase as if `ControlPlaneKubeletLocalMode` is activated.
95+
RunIf: func(c workflow.RunData) (bool, error) {
96+
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, true)
97+
},
98+
}
99+
}
100+
76101
func newEtcdLocalSubphase() workflow.Phase {
77102
return workflow.Phase{
78103
Name: "etcd",
79104
Short: "Add a new local etcd member",
80105
Run: runEtcdPhase,
81106
InheritFlags: getControlPlaneJoinPhaseFlags("etcd"),
82107
ArgsValidator: cobra.NoArgs,
108+
// Only run this phase as subphase of control-plane-join phase if
109+
// `ControlPlaneKubeletLocalMode` is deactivated.
110+
RunIf: func(c workflow.RunData) (bool, error) {
111+
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, false)
112+
},
83113
}
84114
}
85115

cmd/kubeadm/app/cmd/phases/join/data.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ package phases
2020
import (
2121
"io"
2222

23+
"github.com/pkg/errors"
2324
"k8s.io/apimachinery/pkg/util/sets"
2425
clientset "k8s.io/client-go/kubernetes"
2526
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2627

2728
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
29+
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
30+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
2831
)
2932

3033
// JoinData is the interface to use for join phases.
@@ -44,3 +47,17 @@ type JoinData interface {
4447
ManifestDir() string
4548
CertificateWriteDir() string
4649
}
50+
51+
func checkFeatureState(c workflow.RunData, featureGate string, state bool) (bool, error) {
52+
data, ok := c.(JoinData)
53+
if !ok {
54+
return false, errors.New("control-plane-join phase invoked with an invalid data struct")
55+
}
56+
57+
cfg, err := data.InitCfg()
58+
if err != nil {
59+
return false, err
60+
}
61+
62+
return state == features.Enabled(cfg.FeatureGates, featureGate), nil
63+
}

cmd/kubeadm/app/cmd/phases/join/kubelet.go

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ import (
4040
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
4141
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
4242
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
43+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
4344
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
4445
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
46+
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
4547
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
4648
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
4749
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
@@ -85,6 +87,27 @@ func NewKubeletStartPhase() workflow.Phase {
8587
}
8688
}
8789

90+
// NewKubeletWaitBootstrapPhase creates a kubeadm workflow phase that start kubelet on a node.
91+
func NewKubeletWaitBootstrapPhase() workflow.Phase {
92+
return workflow.Phase{
93+
Name: "kubelet-wait-bootstrap",
94+
Short: "[EXPERIMENTAL] Wait for the kubelet to bootstrap itself (only used when feature gate ControlPlaneKubeletLocalMode is enabled)",
95+
Run: runKubeletWaitBootstrapPhase,
96+
InheritFlags: []string{
97+
options.CfgPath,
98+
options.NodeCRISocket,
99+
options.DryRun,
100+
},
101+
// TODO: unhide this phase once ControlPlaneKubeletLocalMode goes GA:
102+
// https://github.com/kubernetes/enhancements/issues/4471
103+
Hidden: true,
104+
// Only run this phase as if `ControlPlaneKubeletLocalMode` is activated.
105+
RunIf: func(c workflow.RunData) (bool, error) {
106+
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, true)
107+
},
108+
}
109+
}
110+
88111
func getKubeletStartJoinData(c workflow.RunData) (*kubeadmapi.JoinConfiguration, *kubeadmapi.InitConfiguration, *clientcmdapi.Config, error) {
89112
data, ok := c.(JoinData)
90113
if !ok {
@@ -117,8 +140,36 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
117140
}
118141
bootstrapKubeConfigFile := filepath.Join(data.KubeConfigDir(), kubeadmconstants.KubeletBootstrapKubeConfigFileName)
119142

120-
// Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk
121-
defer os.Remove(bootstrapKubeConfigFile)
143+
// Do not delete the bootstrapKubeConfigFile at the end of this function when
144+
// using ControlPlaneKubeletLocalMode. The KubeletWaitBootstrapPhase will delete
145+
// it when the feature is enabled.
146+
if !features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
147+
// Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk
148+
defer func() {
149+
_ = os.Remove(bootstrapKubeConfigFile)
150+
}()
151+
}
152+
153+
// Create the bootstrap client before we possibly overwrite the server address
154+
// for ControlPlaneKubeletLocalMode.
155+
bootstrapClient, err := kubeconfigutil.ToClientSet(tlsBootstrapCfg)
156+
if err != nil {
157+
return errors.Errorf("could not create client from bootstrap kubeconfig")
158+
}
159+
160+
if features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
161+
// Set the server url to LocalAPIEndpoint if the feature gate is enabled so the config
162+
// which gets passed to the kubelet forces it to talk to the local kube-apiserver.
163+
if cfg.ControlPlane != nil {
164+
for c, conf := range tlsBootstrapCfg.Clusters {
165+
conf.Server, err = kubeadmutil.GetLocalAPIEndpoint(&cfg.ControlPlane.LocalAPIEndpoint)
166+
if err != nil {
167+
return errors.Wrapf(err, "could not get LocalAPIEndpoint when %s is enabled", features.ControlPlaneKubeletLocalMode)
168+
}
169+
tlsBootstrapCfg.Clusters[c] = conf
170+
}
171+
}
172+
}
122173

123174
// Write the bootstrap kubelet config file or the TLS-Bootstrapped kubelet config file down to disk
124175
klog.V(1).Infof("[kubelet-start] writing bootstrap kubelet config file at %s", bootstrapKubeConfigFile)
@@ -142,11 +193,6 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
142193
}
143194
}
144195

145-
bootstrapClient, err := kubeconfigutil.ClientSetFromFile(bootstrapKubeConfigFile)
146-
if err != nil {
147-
return errors.Errorf("couldn't create client from kubeconfig file %q", bootstrapKubeConfigFile)
148-
}
149-
150196
// Obtain the name of this Node.
151197
nodeName, _, err := kubeletphase.GetNodeNameAndHostname(&cfg.NodeRegistration)
152198
if err != nil {
@@ -205,6 +251,36 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
205251
fmt.Println("[kubelet-start] Starting the kubelet")
206252
kubeletphase.TryStartKubelet()
207253

254+
// Run the same code as KubeletWaitBootstrapPhase would do if the ControlPlaneKubeletLocalMode feature gate is disabled.
255+
if !features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
256+
if err := runKubeletWaitBootstrapPhase(c); err != nil {
257+
return err
258+
}
259+
}
260+
261+
return nil
262+
}
263+
264+
// runKubeletWaitBootstrapPhase waits for the kubelet to finish its TLS bootstrap process.
265+
// This process is executed by the kubelet and completes with the node joining the cluster
266+
// with a dedicates set of credentials as required by the node authorizer.
267+
func runKubeletWaitBootstrapPhase(c workflow.RunData) (returnErr error) {
268+
data, ok := c.(JoinData)
269+
if !ok {
270+
return errors.New("kubelet-start phase invoked with an invalid data struct")
271+
}
272+
cfg := data.Cfg()
273+
initCfg, err := data.InitCfg()
274+
if err != nil {
275+
return err
276+
}
277+
278+
bootstrapKubeConfigFile := filepath.Join(data.KubeConfigDir(), kubeadmconstants.KubeletBootstrapKubeConfigFileName)
279+
// Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk
280+
defer func() {
281+
_ = os.Remove(bootstrapKubeConfigFile)
282+
}()
283+
208284
// Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf
209285
// Wait for the kubelet to create the /etc/kubernetes/kubelet.conf kubeconfig file. If this process
210286
// times out, display a somewhat user-friendly message.

cmd/kubeadm/app/features/features.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const (
3838
EtcdLearnerMode = "EtcdLearnerMode"
3939
// WaitForAllControlPlaneComponents is expected to be alpha in v1.30
4040
WaitForAllControlPlaneComponents = "WaitForAllControlPlaneComponents"
41+
// ControlPlaneKubeletLocalMode is expected to be in alpha in v1.31, beta in v1.32
42+
ControlPlaneKubeletLocalMode = "ControlPlaneKubeletLocalMode"
4143
)
4244

4345
// InitFeatureGates are the default feature gates for the init command
@@ -53,6 +55,7 @@ var InitFeatureGates = FeatureList{
5355
},
5456
EtcdLearnerMode: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}},
5557
WaitForAllControlPlaneComponents: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
58+
ControlPlaneKubeletLocalMode: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
5659
}
5760

5861
// Feature represents a feature being gated

0 commit comments

Comments
 (0)