Skip to content

Commit

Permalink
kubeadm: Fully implement kubeadm init --dry-run
Browse files Browse the repository at this point in the history
  • Loading branch information
luxas committed Aug 22, 2017
1 parent a4b719d commit 5d22be6
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 131 deletions.
1 change: 1 addition & 0 deletions cmd/kubeadm/app/cmd/BUILD
Expand Up @@ -42,6 +42,7 @@ go_library(
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/dryrun:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/pubkeypin:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
Expand Down
67 changes: 58 additions & 9 deletions cmd/kubeadm/app/cmd/init.go
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"time"

Expand Down Expand Up @@ -51,6 +52,7 @@ import (
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
"k8s.io/kubernetes/pkg/api"
Expand Down Expand Up @@ -242,28 +244,40 @@ func (i *Init) Run(out io.Writer) error {
return fmt.Errorf("couldn't parse kubernetes version %q: %v", i.cfg.KubernetesVersion, err)
}

// Get directories to write files to; can be faked if we're dry-running
pkiDir, kubeConfigDir, manifestDir, err := getDirectoriesToUse(i.dryRun, i.cfg.CertificatesDir)
if err != nil {
return err
}

adminKubeConfigPath := filepath.Join(kubeConfigDir, kubeadmconstants.AdminKubeConfigFileName)

// PHASE 1: Generate certificates
if err := certsphase.CreatePKIAssets(i.cfg); err != nil {
if err := certsphase.CreatePKIAssets(pkiDir, i.cfg); err != nil {
return err
}

// PHASE 2: Generate kubeconfig files for the admin and the kubelet
if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeadmconstants.KubernetesDir, i.cfg); err != nil {
if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, pkiDir, i.cfg); err != nil {
return err
}

// PHASE 3: Bootstrap the control plane
manifestPath := kubeadmconstants.GetStaticPodDirectory()
if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestPath, i.cfg); err != nil {
if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestDir, i.cfg); err != nil {
return err
}
// Add etcd static pod spec only if external etcd is not configured
if len(i.cfg.Etcd.Endpoints) == 0 {
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestPath, i.cfg); err != nil {
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestDir, i.cfg); err != nil {
return err
}
}

// If we're dry-running, print the generated manifests
if err := printFilesIfDryRunning(i.dryRun, manifestDir); err != nil {
return err
}

client, err := createClientsetAndOptionallyWaitForReady(i.cfg, i.dryRun)
if err != nil {
return err
Expand Down Expand Up @@ -294,7 +308,7 @@ func (i *Init) Run(out io.Writer) error {
}

// Create the cluster-info ConfigMap with the associated RBAC rules
if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, kubeadmconstants.GetAdminKubeConfigPath()); err != nil {
if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, adminKubeConfigPath); err != nil {
return err
}
if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil {
Expand Down Expand Up @@ -331,7 +345,7 @@ func (i *Init) Run(out io.Writer) error {
}

// Load the CA certificate from so we can pin its public key
caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
caCert, err := pkiutil.TryLoadCertFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName)

// Generate the Master host/port pair used by initDoneTempl
masterHostPort, err := kubeadmutil.GetMasterHostPort(i.cfg)
Expand All @@ -340,8 +354,7 @@ func (i *Init) Run(out io.Writer) error {
}

ctx := map[string]string{
"KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(),
"KubeConfigName": kubeadmconstants.AdminKubeConfigFileName,
"KubeConfigPath": adminKubeConfigPath,
"Token": i.cfg.Token,
"CAPubKeyPin": pubkeypin.Hash(caCert),
"MasterHostPort": masterHostPort,
Expand Down Expand Up @@ -374,3 +387,39 @@ func createClientsetAndOptionallyWaitForReady(cfg *kubeadmapi.MasterConfiguratio
}
return client, nil
}

// getDirectoriesToUse returns the (in order) certificates, kubeconfig and Static Pod manifest directories, followed by a possible error
// This behaves differently when dry-running vs the normal flow
func getDirectoriesToUse(dryRun bool, defaultPkiDir string) (string, string, string, error) {
if dryRun {
dryRunDir, err := ioutil.TempDir("", "kubeadm-init-dryrun")
if err != nil {
return "", "", "", fmt.Errorf("couldn't create a temporary directory: %v")
}
// Use the same temp dir for all
return dryRunDir, dryRunDir, dryRunDir, nil
}

return defaultPkiDir, kubeadmconstants.KubernetesDir, kubeadmconstants.GetStaticPodDirectory(), nil
}

// printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup
func printFilesIfDryRunning(dryRun bool, manifestDir string) error {
if !dryRun {
return nil
}

fmt.Printf("[dryrun] Wrote certificates, kubeconfig files and control plane manifests to %q\n", manifestDir)
fmt.Println("[dryrun] Won't print certificates or kubeconfig files due to the sensitive nature of them")
fmt.Printf("[dryrun] Please go and examine the %q directory for details about what would be written\n", manifestDir)

// Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests
files := []dryrunutil.DryRunFile{}
for _, component := range kubeadmconstants.MasterComponents {
realPath := kubeadmconstants.GetStaticPodFilepath(component, manifestDir)
outputPath := kubeadmconstants.GetStaticPodFilepath(component, kubeadmconstants.GetStaticPodDirectory())
files = append(files, dryrunutil.NewDryRunFile(realPath, outputPath))
}

return dryrunutil.DryRunPrintFileContents(files, os.Stdout)
}
8 changes: 4 additions & 4 deletions cmd/kubeadm/app/cmd/phases/certs.go
Expand Up @@ -54,7 +54,7 @@ func getCertsSubCommands() []*cobra.Command {
subCmdProperties := []struct {
use string
short string
cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error
cmdFunc func(pkiDir string, cfg *kubeadmapi.MasterConfiguration) error
}{
{
use: "all",
Expand All @@ -64,7 +64,7 @@ func getCertsSubCommands() []*cobra.Command {
{
use: "ca",
short: "Generate CA certificate and key for a Kubernetes cluster.",
cmdFunc: certsphase.CreateCACertAndKeyfiles,
cmdFunc: certsphase.CreateCACertAndKeyFiles,
},
{
use: "apiserver",
Expand Down Expand Up @@ -118,7 +118,7 @@ func getCertsSubCommands() []*cobra.Command {
}

// runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
func runCmdFunc(cmdFunc func(pkiDir string, cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {

// the following statement build a clousure that wraps a call to a cmdFunc, binding
// the function itself with the specific parameters of each sub command.
Expand All @@ -135,7 +135,7 @@ func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath
kubeadmutil.CheckErr(err)

// Execute the cmdFunc
err = cmdFunc(internalcfg)
err = cmdFunc(internalcfg.CertificatesDir, internalcfg)
kubeadmutil.CheckErr(err)
}
}
44 changes: 27 additions & 17 deletions cmd/kubeadm/app/cmd/phases/kubeconfig.go
Expand Up @@ -57,29 +57,39 @@ func getKubeConfigSubCommands(out io.Writer, outDir string) []*cobra.Command {
cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
}{
{
use: "all",
short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.",
cmdFunc: kubeconfigphase.CreateInitKubeConfigFiles,
use: "all",
short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.",
cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
return kubeconfigphase.CreateInitKubeConfigFiles(outDir, cfg.CertificatesDir, cfg)
},
},
{
use: "admin",
short: "Generate a kubeconfig file for the admin to use and for kubeadm itself.",
cmdFunc: kubeconfigphase.CreateAdminKubeConfigFile,
use: "admin",
short: "Generate a kubeconfig file for the admin to use and for kubeadm itself.",
cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
return kubeconfigphase.CreateAdminKubeConfigFile(outDir, cfg.CertificatesDir, cfg)
},
},
{
use: "kubelet",
short: "Generate a kubeconfig file for the Kubelet to use. Please note that this should *only* be used for bootstrapping purposes. After your control plane is up, you should request all kubelet credentials from the CSR API.",
cmdFunc: kubeconfigphase.CreateKubeletKubeConfigFile,
use: "kubelet",
short: "Generate a kubeconfig file for the Kubelet to use. Please note that this should *only* be used for bootstrapping purposes. After your control plane is up, you should request all kubelet credentials from the CSR API.",
cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
return kubeconfigphase.CreateKubeletKubeConfigFile(outDir, cfg.CertificatesDir, cfg)
},
},
{
use: "controller-manager",
short: "Generate a kubeconfig file for the Controller Manager to use.",
cmdFunc: kubeconfigphase.CreateControllerManagerKubeConfigFile,
use: "controller-manager",
short: "Generate a kubeconfig file for the Controller Manager to use.",
cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
return kubeconfigphase.CreateControllerManagerKubeConfigFile(outDir, cfg.CertificatesDir, cfg)
},
},
{
use: "scheduler",
short: "Generate a kubeconfig file for the Scheduler to use.",
cmdFunc: kubeconfigphase.CreateSchedulerKubeConfigFile,
use: "scheduler",
short: "Generate a kubeconfig file for the Scheduler to use.",
cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
return kubeconfigphase.CreateSchedulerKubeConfigFile(outDir, cfg.CertificatesDir, cfg)
},
},
{
use: "user",
Expand All @@ -91,11 +101,11 @@ func getKubeConfigSubCommands(out io.Writer, outDir string) []*cobra.Command {

// if the kubeconfig file for an additional user has to use a token, use it
if token != "" {
return kubeconfigphase.WriteKubeConfigWithToken(out, cfg, clientName, token)
return kubeconfigphase.WriteKubeConfigWithToken(out, cfg.CertificatesDir, cfg, clientName, token)
}

// Otherwise, write a kubeconfig file with a generate client cert
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg, clientName)
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg.CertificatesDir, cfg, clientName)
},
},
}
Expand Down

0 comments on commit 5d22be6

Please sign in to comment.