Repository: kubernetes/kubernetes
File: cmd/kubeadm/app/phases/etcd/local.go (Go)

Code before:
		klog.V(1).Infof("Updated etcd member list: %v", cluster)
	}

	fmt.Printf("[etcd] Creating static Pod manifest for %q\n", kubeadmconstants.Etcd)

	if err := prepareAndWriteEtcdStaticPod(manifestDir, patchesDir, cfg, endpoint, nodeName, cluster, isDryRun); err != nil {
		return err
	}

	if isDryRun {
		fmt.Println("[etcd] Would wait for the new etcd member to join the cluster")
		return nil
	}

	// Wait for the etcd pod to start before attempting to promote the learner
	// If the pod never starts, we should remove the added learner member from cluster
	fmt.Printf("[etcd] Waiting for the etcd pod to start on node %q\n", nodeName)
	waiter := apiclient.NewKubeWaiter(client, kubeadmapi.GetActiveTimeouts().ControlPlaneComponentHealthCheck.Duration, os.Stdout)
	if err := waiter.WaitForStaticPodRunning(nodeName, kubeadmconstants.Etcd); err != nil {
		klog.V(1).Infof("[etcd] The etcd pod did not start successfully, removing learner member %s", etcdPeerAddress)
		if removeErr := removeLearnerMember(etcdClient, etcdPeerAddress); removeErr != nil {
			klog.Warningf("[etcd] Failed to remove learner member after pod startup failure: %v", removeErr)
		} else {
			klog.V(1).Infof("[etcd] Successfully removed learner member %s", etcdPeerAddress)
		}
		return errors.Wrap(err, "The etcd pod did not start successfully")
	}

	learnerID, err := etcdClient.GetMemberID(etcdPeerAddress)
	if err != nil {
		return err
	}
	err = etcdClient.MemberPromote(learnerID)
	if err != nil {
		return err
	}

	fmt.Printf("[etcd] Waiting for the new etcd member to join the cluster. This can take up to %v\n", etcdHealthyCheckInterval*etcdHealthyCheckRetries)
	if _, err := etcdClient.WaitForClusterAvailable(etcdHealthyCheckRetries, etcdHealthyCheckInterval); err != nil {
		return err
	}

	return nil
}

// removeLearnerMember removes a learner member from the etcd cluster.
func removeLearnerMember(etcdClient *etcdutil.Client, etcdPeerAddress string) error {
	learnerID, err := etcdClient.GetMemberID(etcdPeerAddress)
	if err != nil {
		if errors.Is(etcdutil.ErrNoMemberIDForPeerURL, err) {
			klog.V(5).Infof("[etcd] Member was already removed, because no member id exists for peer %s", etcdPeerAddress)

Review comment (bug):
Like line 183: `failed to remove xxx`?
The error may be something like: `failed to start xxx`?
`the learner member was removed successfully`

Overall LGTM. Some logs or errors are not aligned.

/approve



Code after:
	}

	fmt.Printf("[etcd] Creating static Pod manifest for %q\n", kubeadmconstants.Etcd)

	if err := prepareAndWriteEtcdStaticPod(manifestDir, patchesDir, cfg, endpoint, nodeName, cluster, isDryRun); err != nil {
		return err
	}

	if isDryRun {
		fmt.Println("[etcd] Would wait for the new etcd member to join the cluster")
		return nil
	}

	learnerID, err := etcdClient.GetMemberID(etcdPeerAddress)
	if err != nil {
		return err
	}
	err = etcdClient.MemberPromote(learnerID)
	if err != nil {
		return err
	}

	fmt.Printf("[etcd] Waiting for the new etcd member to join the cluster. This can take up to %v\n", etcdHealthyCheckInterval*etcdHealthyCheckRetries)
	if _, err := etcdClient.WaitForClusterAvailable(etcdHealthyCheckRetries, etcdHealthyCheckInterval); err != nil {
		return err
	}

	return nil
}

// GetEtcdPodSpec returns the etcd static Pod actualized to the context of the current configuration
// NB. GetEtcdPodSpec methods holds the information about how kubeadm creates etcd static pod manifests.
func GetEtcdPodSpec(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, nodeName string, initialCluster []etcdutil.Member) v1.Pod {
	pathType := v1.HostPathDirectoryOrCreate
	etcdMounts := map[string]v1.Volume{
		etcdVolumeName:  staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.Local.DataDir, &pathType),
		certsVolumeName: staticpodutil.NewVolume(certsVolumeName, cfg.CertificatesDir+"/etcd", &pathType),
	}
	componentHealthCheckTimeout := kubeadmapi.GetActiveTimeouts().ControlPlaneComponentHealthCheck

	// probeHostname returns the correct localhost IP address family based on the endpoint AdvertiseAddress
	probeHostname, probePort, probeScheme := staticpodutil.GetEtcdProbeEndpoint(&cfg.Etcd, utilsnet.IsIPv6String(endpoint.AdvertiseAddress))
	return staticpodutil.ComponentPod(
		v1.Container{
			Name:            kubeadmconstants.Etcd,
			Command:         getEtcdCommand(cfg, endpoint, nodeName, initialCluster, kubeadmconstants.SupportedEtcdVersion),
			Image:           images.GetEtcdImage(cfg, kubeadmconstants.SupportedEtcdVersion),
			ImagePullPolicy: v1.PullIfNotPresent,
			// Mount the etcd datadir path read-write so etcd can store data in a more persistent manner
			VolumeMounts: []v1.VolumeMount{
				staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.Local.DataDir, false),