diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index bb9ee2327b0..02d4c4a03c1 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -607,6 +607,7 @@ func (j *joinData) Client() (clientset.Interface, error) { WithWriter(os.Stdout). AppendReactor(dryRun.GetClusterInfoReactor()). AppendReactor(dryRun.GetKubeadmConfigReactor()). + AppendReactor(dryRun.GetKubeadmCertsReactor()). AppendReactor(dryRun.GetKubeProxyConfigReactor()). AppendReactor(dryRun.GetKubeletConfigReactor()) diff --git a/cmd/kubeadm/app/cmd/phases/join/checketcd.go b/cmd/kubeadm/app/cmd/phases/join/checketcd.go index e7cca6c16ad..1d4ea2335c4 100644 --- a/cmd/kubeadm/app/cmd/phases/join/checketcd.go +++ b/cmd/kubeadm/app/cmd/phases/join/checketcd.go @@ -56,6 +56,11 @@ func runCheckEtcdPhase(c workflow.RunData) error { return nil } + if data.DryRun() { + fmt.Println("[dryrun] Would check that the etcd cluster is healthy") + return nil + } + fmt.Println("[check-etcd] Checking that the etcd cluster is healthy") // Checks that the etcd cluster is healthy diff --git a/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go b/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go index c1dd8701596..e03a02a8ea6 100644 --- a/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go +++ b/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go @@ -292,6 +292,11 @@ func runControlPlanePrepareKubeconfigPhaseLocal(c workflow.RunData) error { fmt.Println("[kubeconfig] Generating kubeconfig files") fmt.Printf("[kubeconfig] Using kubeconfig folder %q\n", data.KubeConfigDir()) + // If we're dry-running, load certs from tmp dir, and defer to restore to the path originally specified by the user + certsDir := cfg.CertificatesDir + cfg.CertificatesDir = data.CertificateWriteDir() + defer func() { cfg.CertificatesDir = certsDir }() + // Generate kubeconfig files for controller manager, scheduler and for the admin/kubeadm itself // NB. The kubeconfig file for kubelet will be generated by the TLS bootstrap process in // following steps of the join --control-plane workflow @@ -303,6 +308,14 @@ func runControlPlanePrepareKubeconfigPhaseLocal(c workflow.RunData) error { } func bootstrapClient(data JoinData) (clientset.Interface, error) { + if data.DryRun() { + client, err := data.Client() + if err != nil { + return nil, err + } + return client, nil + } + tlsBootstrapCfg, err := data.TLSBootstrapCfg() if err != nil { return nil, errors.Wrap(err, "unable to access the cluster") diff --git a/cmd/kubeadm/app/phases/etcd/local.go b/cmd/kubeadm/app/phases/etcd/local.go index 93d10e6f1c8..9e9b19004af 100644 --- a/cmd/kubeadm/app/phases/etcd/local.go +++ b/cmd/kubeadm/app/phases/etcd/local.go @@ -38,6 +38,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/images" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd" staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" "k8s.io/kubernetes/cmd/kubeadm/app/util/users" @@ -139,19 +140,20 @@ func RemoveStackedEtcdMemberFromCluster(client clientset.Interface, cfg *kubeadm // for an additional etcd member that is joining an existing local/stacked etcd cluster. // Other members of the etcd cluster will be notified of the joining node in beforehand as well. func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifestDir, patchesDir string, nodeName string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, isDryRun bool, certificatesDir string) error { - // creates an etcd client that connects to all the local/stacked etcd members - klog.V(1).Info("creating etcd client that connects to etcd pods") - etcdClient, err := etcdutil.NewFromCluster(client, certificatesDir) - if err != nil { - return err - } - etcdPeerAddress := etcdutil.GetPeerURL(endpoint) var cluster []etcdutil.Member + var etcdClient *etcdutil.Client + var err error if isDryRun { fmt.Printf("[etcd] Would add etcd member: %s\n", etcdPeerAddress) } else { + // Creates an etcd client that connects to all the local/stacked etcd members. + klog.V(1).Info("creating etcd client that connects to etcd pods") + etcdClient, err = etcdutil.NewFromCluster(client, certificatesDir) + if err != nil { + return err + } klog.V(1).Infof("[etcd] Adding etcd member: %s", etcdPeerAddress) if features.Enabled(cfg.FeatureGates, features.EtcdLearnerMode) { cluster, err = etcdClient.AddMemberAsLearner(nodeName, etcdPeerAddress) @@ -323,5 +325,11 @@ func prepareAndWriteEtcdStaticPod(manifestDir string, patchesDir string, cfg *ku return err } + // If dry-running, print the static etcd pod manifest file. + if isDryRun { + realPath := kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestDir) + outputPath := kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, kubeadmconstants.GetStaticPodDirectory()) + return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{dryrunutil.NewFileToPrint(realPath, outputPath)}, os.Stdout) + } return nil } diff --git a/cmd/kubeadm/app/util/apiclient/dryrun.go b/cmd/kubeadm/app/util/apiclient/dryrun.go index 8c73fffc08b..fb40d7c18f0 100644 --- a/cmd/kubeadm/app/util/apiclient/dryrun.go +++ b/cmd/kubeadm/app/util/apiclient/dryrun.go @@ -415,6 +415,24 @@ func (d *DryRun) GetKubeadmConfigReactor() *testing.SimpleReactor { } } +// GetKubeadmCertsReactor returns a reactor that handles the GET action of the "kubeadm-certs" Secret. +func (d *DryRun) GetKubeadmCertsReactor() *testing.SimpleReactor { + return &testing.SimpleReactor{ + Verb: "get", + Resource: "secrets", + Reaction: func(action testing.Action) (bool, runtime.Object, error) { + a := action.(testing.GetAction) + if a.GetName() != constants.KubeadmCertsSecret || a.GetNamespace() != metav1.NamespaceSystem { + return false, nil, nil + } + + obj := getKubeadmCertsSecret() + d.LogObject(obj, action.GetResource().GroupVersion()) + return true, obj, nil + }, + } +} + // GetKubeletConfigReactor returns a reactor that handles the GET action of the "kubelet-config" // ConfigMap. func (d *DryRun) GetKubeletConfigReactor() *testing.SimpleReactor { @@ -513,6 +531,17 @@ func getConfigMap(namespace, name string, data map[string]string) *corev1.Config } } +// getSecret returns a fake Secret object. +func getSecret(namespace, name string, data map[string][]byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: data, + } +} + // getClusterInfoConfigMap returns a fake "cluster-info" ConfigMap. func getClusterInfoConfigMap() *corev1.ConfigMap { kubeconfig := dedent.Dedent(`apiVersion: v1 @@ -546,6 +575,7 @@ controllerManager: extraArgs: - name: cluster-signing-duration value: 24h +controlPlaneEndpoint: 192.168.0.101:6443 dns: {} encryptionAlgorithm: RSA-2048 etcd: @@ -568,6 +598,23 @@ scheduler: {} return getConfigMap(metav1.NamespaceSystem, constants.KubeadmConfigConfigMap, data) } +// getKubeadmCertsSecret returns a fake "kubeadm-certs" Secret. +func getKubeadmCertsSecret() *corev1.Secret { + // The cert data is empty because the actual content is not relevant for the dryrun test. + data := map[string][]byte{ + constants.CACertName: {}, + constants.CAKeyName: {}, + constants.FrontProxyCACertName: {}, + constants.FrontProxyCAKeyName: {}, + constants.ServiceAccountPrivateKeyName: {}, + constants.ServiceAccountPublicKeyName: {}, + strings.ReplaceAll(constants.EtcdCACertName, "/", "-"): {}, + strings.ReplaceAll(constants.EtcdCAKeyName, "/", "-"): {}, + } + + return getSecret(metav1.NamespaceSystem, constants.KubeadmCertsSecret, data) +} + // getKubeletConfigMap returns a fake "kubelet-config" ConfigMap. func getKubeletConfigMap() *corev1.ConfigMap { configData := `apiVersion: kubelet.config.k8s.io/v1beta1