@@ -40,8 +40,10 @@ import (
40
40
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
41
41
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
42
42
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
43
+ "k8s.io/kubernetes/cmd/kubeadm/app/features"
43
44
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
44
45
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
46
+ kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
45
47
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
46
48
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
47
49
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
@@ -85,6 +87,27 @@ func NewKubeletStartPhase() workflow.Phase {
85
87
}
86
88
}
87
89
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
+
88
111
func getKubeletStartJoinData (c workflow.RunData ) (* kubeadmapi.JoinConfiguration , * kubeadmapi.InitConfiguration , * clientcmdapi.Config , error ) {
89
112
data , ok := c .(JoinData )
90
113
if ! ok {
@@ -117,8 +140,36 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
117
140
}
118
141
bootstrapKubeConfigFile := filepath .Join (data .KubeConfigDir (), kubeadmconstants .KubeletBootstrapKubeConfigFileName )
119
142
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
+ }
122
173
123
174
// Write the bootstrap kubelet config file or the TLS-Bootstrapped kubelet config file down to disk
124
175
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) {
142
193
}
143
194
}
144
195
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
-
150
196
// Obtain the name of this Node.
151
197
nodeName , _ , err := kubeletphase .GetNodeNameAndHostname (& cfg .NodeRegistration )
152
198
if err != nil {
@@ -205,6 +251,36 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
205
251
fmt .Println ("[kubelet-start] Starting the kubelet" )
206
252
kubeletphase .TryStartKubelet ()
207
253
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
+
208
284
// Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf
209
285
// Wait for the kubelet to create the /etc/kubernetes/kubelet.conf kubeconfig file. If this process
210
286
// times out, display a somewhat user-friendly message.
0 commit comments