kubeadm: ensure the kubelet and kube-apiserver wait checks go first
The addition of the "super-admin.conf" functionality required init.go's Client() to create RBAC rules on its first creation. However this created a problem with the "wait-control-plane" phase of "kubeadm init" where a client is needed to connect to the API server Discovery API's "/healthz" endpoint. The logic that ensures the RBAC became the step where the API server wait was polled for. To avoid this, introduce a new InitData function ClientWithoutBootstrap. In "wait-control-plane" use this client, which has no permissions (anonymous), but is sufficient to connect to the "/healthz". Pending changes here would be: - Stop using the "/healthz", instead a regular REST client from the kubelet cert/key can be constructed. - Make the wait for kubelet / API server linear (not in go routines).
This commit is contained in:
		@@ -496,21 +496,28 @@ func (d *initData) OutputWriter() io.Writer {
 | 
				
			|||||||
	return d.outputWriter
 | 
						return d.outputWriter
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getDryRunClient creates a fake client that answers some GET calls in order to be able to do the full init flow in dry-run mode.
 | 
				
			||||||
 | 
					func getDryRunClient(d *initData) (clientset.Interface, error) {
 | 
				
			||||||
 | 
						svcSubnetCIDR, err := kubeadmconstants.GetKubernetesServiceCIDR(d.cfg.Networking.ServiceSubnet)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, errors.Wrapf(err, "unable to get internal Kubernetes Service IP from the given service CIDR (%s)", d.cfg.Networking.ServiceSubnet)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dryRunGetter := apiclient.NewInitDryRunGetter(d.cfg.NodeRegistration.Name, svcSubnetCIDR.String())
 | 
				
			||||||
 | 
						return apiclient.NewDryRunClient(dryRunGetter, os.Stdout), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Client returns a Kubernetes client to be used by kubeadm.
 | 
					// Client returns a Kubernetes client to be used by kubeadm.
 | 
				
			||||||
// This function is implemented as a singleton, thus avoiding to recreate the client when it is used by different phases.
 | 
					// This function is implemented as a singleton, thus avoiding to recreate the client when it is used by different phases.
 | 
				
			||||||
// Important. This function must be called after the admin.conf kubeconfig file is created.
 | 
					// Important. This function must be called after the admin.conf kubeconfig file is created.
 | 
				
			||||||
func (d *initData) Client() (clientset.Interface, error) {
 | 
					func (d *initData) Client() (clientset.Interface, error) {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
	if d.client == nil {
 | 
						if d.client == nil {
 | 
				
			||||||
		if d.dryRun {
 | 
							if d.dryRun {
 | 
				
			||||||
			svcSubnetCIDR, err := kubeadmconstants.GetKubernetesServiceCIDR(d.cfg.Networking.ServiceSubnet)
 | 
								d.client, err = getDryRunClient(d)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, errors.Wrapf(err, "unable to get internal Kubernetes Service IP from the given service CIDR (%s)", d.cfg.Networking.ServiceSubnet)
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// If we're dry-running, we should create a faked client that answers some GETs in order to be able to do the full init flow and just logs the rest of requests
 | 
					 | 
				
			||||||
			dryRunGetter := apiclient.NewInitDryRunGetter(d.cfg.NodeRegistration.Name, svcSubnetCIDR.String())
 | 
					 | 
				
			||||||
			d.client = apiclient.NewDryRunClient(dryRunGetter, os.Stdout)
 | 
					 | 
				
			||||||
		} else { // Use a real client
 | 
							} else { // Use a real client
 | 
				
			||||||
			var err error
 | 
					 | 
				
			||||||
			if !d.adminKubeConfigBootstrapped {
 | 
								if !d.adminKubeConfigBootstrapped {
 | 
				
			||||||
				// Call EnsureAdminClusterRoleBinding() to obtain a working client from admin.conf.
 | 
									// Call EnsureAdminClusterRoleBinding() to obtain a working client from admin.conf.
 | 
				
			||||||
				d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding(kubeadmconstants.KubernetesDir, nil)
 | 
									d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding(kubeadmconstants.KubernetesDir, nil)
 | 
				
			||||||
@@ -531,6 +538,28 @@ func (d *initData) Client() (clientset.Interface, error) {
 | 
				
			|||||||
	return d.client, nil
 | 
						return d.client, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClientWithoutBootstrap returns a dry-run client or a regular client from admin.conf.
 | 
				
			||||||
 | 
					// Unlike Client(), it does not call EnsureAdminClusterRoleBinding() or sets d.client.
 | 
				
			||||||
 | 
					// This means the client only has anonymous permissions and does not persist in initData.
 | 
				
			||||||
 | 
					func (d *initData) ClientWithoutBootstrap() (clientset.Interface, error) {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							client clientset.Interface
 | 
				
			||||||
 | 
							err    error
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if d.dryRun {
 | 
				
			||||||
 | 
							client, err = getDryRunClient(d)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else { // Use a real client
 | 
				
			||||||
 | 
							client, err = kubeconfigutil.ClientSetFromFile(d.KubeConfigPath())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return client, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tokens returns an array of token strings.
 | 
					// Tokens returns an array of token strings.
 | 
				
			||||||
func (d *initData) Tokens() []string {
 | 
					func (d *initData) Tokens() []string {
 | 
				
			||||||
	tokens := []string{}
 | 
						tokens := []string{}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,7 @@ type InitData interface {
 | 
				
			|||||||
	ExternalCA() bool
 | 
						ExternalCA() bool
 | 
				
			||||||
	OutputWriter() io.Writer
 | 
						OutputWriter() io.Writer
 | 
				
			||||||
	Client() (clientset.Interface, error)
 | 
						Client() (clientset.Interface, error)
 | 
				
			||||||
 | 
						ClientWithoutBootstrap() (clientset.Interface, error)
 | 
				
			||||||
	Tokens() []string
 | 
						Tokens() []string
 | 
				
			||||||
	PatchesDir() string
 | 
						PatchesDir() string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,22 +31,23 @@ type testInitData struct{}
 | 
				
			|||||||
// testInitData must satisfy InitData.
 | 
					// testInitData must satisfy InitData.
 | 
				
			||||||
var _ InitData = &testInitData{}
 | 
					var _ InitData = &testInitData{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (t *testInitData) UploadCerts() bool                       { return false }
 | 
					func (t *testInitData) UploadCerts() bool                                    { return false }
 | 
				
			||||||
func (t *testInitData) CertificateKey() string                  { return "" }
 | 
					func (t *testInitData) CertificateKey() string                               { return "" }
 | 
				
			||||||
func (t *testInitData) SetCertificateKey(key string)            {}
 | 
					func (t *testInitData) SetCertificateKey(key string)                         {}
 | 
				
			||||||
func (t *testInitData) SkipCertificateKeyPrint() bool           { return false }
 | 
					func (t *testInitData) SkipCertificateKeyPrint() bool                        { return false }
 | 
				
			||||||
func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration      { return nil }
 | 
					func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration                   { return nil }
 | 
				
			||||||
func (t *testInitData) DryRun() bool                            { return false }
 | 
					func (t *testInitData) DryRun() bool                                         { return false }
 | 
				
			||||||
func (t *testInitData) SkipTokenPrint() bool                    { return false }
 | 
					func (t *testInitData) SkipTokenPrint() bool                                 { return false }
 | 
				
			||||||
func (t *testInitData) IgnorePreflightErrors() sets.Set[string] { return nil }
 | 
					func (t *testInitData) IgnorePreflightErrors() sets.Set[string]              { return nil }
 | 
				
			||||||
func (t *testInitData) CertificateWriteDir() string             { return "" }
 | 
					func (t *testInitData) CertificateWriteDir() string                          { return "" }
 | 
				
			||||||
func (t *testInitData) CertificateDir() string                  { return "" }
 | 
					func (t *testInitData) CertificateDir() string                               { return "" }
 | 
				
			||||||
func (t *testInitData) KubeConfigDir() string                   { return "" }
 | 
					func (t *testInitData) KubeConfigDir() string                                { return "" }
 | 
				
			||||||
func (t *testInitData) KubeConfigPath() string                  { return "" }
 | 
					func (t *testInitData) KubeConfigPath() string                               { return "" }
 | 
				
			||||||
func (t *testInitData) ManifestDir() string                     { return "" }
 | 
					func (t *testInitData) ManifestDir() string                                  { return "" }
 | 
				
			||||||
func (t *testInitData) KubeletDir() string                      { return "" }
 | 
					func (t *testInitData) KubeletDir() string                                   { return "" }
 | 
				
			||||||
func (t *testInitData) ExternalCA() bool                        { return false }
 | 
					func (t *testInitData) ExternalCA() bool                                     { return false }
 | 
				
			||||||
func (t *testInitData) OutputWriter() io.Writer                 { return nil }
 | 
					func (t *testInitData) OutputWriter() io.Writer                              { return nil }
 | 
				
			||||||
func (t *testInitData) Client() (clientset.Interface, error)    { return nil, nil }
 | 
					func (t *testInitData) Client() (clientset.Interface, error)                 { return nil, nil }
 | 
				
			||||||
func (t *testInitData) Tokens() []string                        { return nil }
 | 
					func (t *testInitData) ClientWithoutBootstrap() (clientset.Interface, error) { return nil, nil }
 | 
				
			||||||
func (t *testInitData) PatchesDir() string                      { return "" }
 | 
					func (t *testInitData) Tokens() []string                                     { return nil }
 | 
				
			||||||
 | 
					func (t *testInitData) PatchesDir() string                                   { return "" }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,9 +82,10 @@ func runWaitControlPlanePhase(c workflow.RunData) error {
 | 
				
			|||||||
	// waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled
 | 
						// waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled
 | 
				
			||||||
	klog.V(1).Infoln("[wait-control-plane] Waiting for the API server to be healthy")
 | 
						klog.V(1).Infoln("[wait-control-plane] Waiting for the API server to be healthy")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	client, err := data.Client()
 | 
						// WaitForAPI uses the /healthz endpoint, thus a client without permissions works fine
 | 
				
			||||||
 | 
						client, err := data.ClientWithoutBootstrap()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errors.Wrap(err, "cannot obtain client")
 | 
							return errors.Wrap(err, "cannot obtain client without bootstrap")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	timeout := data.Cfg().ClusterConfiguration.APIServer.TimeoutForControlPlane.Duration
 | 
						timeout := data.Cfg().ClusterConfiguration.APIServer.TimeoutForControlPlane.Duration
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user