Merge pull request #70098 from fabriziopandini/kubeadm-graduate-kubeconfig

Kubeadm graduate kubeconfig phase
pull/58/head
k8s-ci-robot 2018-10-30 12:22:48 -07:00 committed by GitHub
commit 739998f8fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 266 additions and 540 deletions

View File

@ -50,7 +50,6 @@ import (
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster"
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
@ -130,6 +129,7 @@ type initData struct {
ignorePreflightErrors sets.String
certificatesDir string
dryRunDir string
externalCA bool
client clientset.Interface
}
@ -146,7 +146,7 @@ func NewCmdInit(out io.Writer) *cobra.Command {
kubeadmutil.CheckErr(err)
data := c.(initData)
fmt.Printf("[init] using Kubernetes version: %s\n", data.cfg.KubernetesVersion)
fmt.Printf("[init] Using Kubernetes version: %s\n", data.cfg.KubernetesVersion)
err = initRunner.Run()
kubeadmutil.CheckErr(err)
@ -166,8 +166,9 @@ func NewCmdInit(out io.Writer) *cobra.Command {
// initialize the workflow runner with the list of phases
initRunner.AppendPhase(phases.NewPreflightMasterPhase())
initRunner.AppendPhase(phases.NewCertsPhase())
initRunner.AppendPhase(phases.NewKubeletStartPhase())
initRunner.AppendPhase(phases.NewCertsPhase())
initRunner.AppendPhase(phases.NewKubeConfigPhase())
// TODO: add other phases to the runner.
// sets the data builder function, that will be used by the runner
@ -313,6 +314,9 @@ func newInitData(cmd *cobra.Command, options *initOptions) (initData, error) {
}
}
// Checks if an external CA is provided by the user.
externalCA, _ := certsphase.UsingExternalCA(cfg)
return initData{
cfg: cfg,
certificatesDir: cfg.CertificatesDir,
@ -320,6 +324,7 @@ func newInitData(cmd *cobra.Command, options *initOptions) (initData, error) {
dryRun: options.dryRun,
dryRunDir: dryRunDir,
ignorePreflightErrors: ignorePreflightErrorsSet,
externalCA: externalCA,
}, nil
}
@ -380,6 +385,11 @@ func (d initData) KubeletDir() string {
return kubeadmconstants.KubeletRunDirectory
}
// ExternalCA returns true if an external CA is provided by the user.
func (d initData) ExternalCA() bool {
return d.externalCA
}
// 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.
// Important. This function must be called after the admin.conf kubeconfig file is created.
@ -426,18 +436,6 @@ func runInit(i *initData, out io.Writer) error {
adminKubeConfigPath := filepath.Join(kubeConfigDir, kubeadmconstants.AdminKubeConfigFileName)
if res, _ := certsphase.UsingExternalCA(i.cfg); !res {
// PHASE 2: Generate kubeconfig files for the admin and the kubelet
glog.V(2).Infof("[init] generating kubeconfig files")
if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil {
return err
}
} else {
fmt.Println("[externalca] the file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated")
}
if features.Enabled(i.cfg.FeatureGates, features.Auditing) {
// Setup the AuditPolicy (either it was passed in and exists or it wasn't passed in and generate a default policy)
if i.cfg.AuditPolicyConfiguration.Path != "" {

View File

@ -76,7 +76,6 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",

View File

@ -22,7 +22,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
@ -36,23 +35,6 @@ import (
)
var (
allCertsLongDesc = normalizer.LongDesc(`
Generates a self-signed CA to provision identities for each component in the cluster (including nodes)
and client certificates to be used by various components.
If a given certificate and private key pair both exist, kubeadm skips the generation step and
existing files will be used.
` + cmdutil.AlphaDisclaimer)
allCertsExample = normalizer.Examples(`
# Creates all PKI assets necessary to establish the control plane,
# functionally equivalent to what generated by kubeadm init.
kubeadm alpha phase certs all
# Creates all PKI assets using options read from a configuration file.
kubeadm alpha phase certs all --config masterconfiguration.yaml
`)
saKeyLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the private key for signing service account tokens along with its public key, and saves them into
%s and %s files.
@ -71,6 +53,7 @@ var (
// (and thus with different runtime data struct, all of them requested to be compliant to this interface)
type certsData interface {
Cfg() *kubeadmapi.InitConfiguration
ExternalCA() bool
CertificateDir() string
CertificateWriteDir() string
}
@ -80,7 +63,8 @@ func NewCertsPhase() workflow.Phase {
return workflow.Phase{
Name: "certs",
Short: "Certificate generation",
Phases: getCertsSubPhases(),
Phases: newCertSubPhases(),
Run: runCerts,
}
}
@ -102,8 +86,8 @@ func getCertsSubCommands() []*cobra.Command {
return []*cobra.Command{certscmdphase.NewCmdCertsRenewal()}
}
// getCertsSubPhases returns sub phases for certs phase
func getCertsSubPhases() []workflow.Phase {
// newCertSubPhases returns sub phases for certs phase
func newCertSubPhases() []workflow.Phase {
subPhases := []workflow.Phase{}
certTree, _ := certsphase.GetDefaultCertList().AsMap().CertTree()
@ -116,8 +100,6 @@ func getCertsSubPhases() []workflow.Phase {
certPhase := newCertSubPhase(cert, runCertPhase(cert, ca))
subPhases = append(subPhases, certPhase)
}
subPhases = append(subPhases, caPhase)
}
// SA creates the private/public key pair, which doesn't use x509 at all
@ -133,19 +115,6 @@ func getCertsSubPhases() []workflow.Phase {
return subPhases
}
func runCertsSa(c workflow.RunData) error {
data, ok := c.(certsData)
if !ok {
return errors.New("certs phase invoked with an invalid data struct")
}
cfg := data.Cfg()
cfg.CertificatesDir = data.CertificateWriteDir()
defer func() { cfg.CertificatesDir = data.CertificateDir() }()
return certsphase.CreateServiceAccountKeyAndPublicKeyFiles(cfg)
}
func newCertSubPhase(certSpec *certsphase.KubeadmCert, run func(c workflow.RunData) error) workflow.Phase {
phase := workflow.Phase{
Name: certSpec.Name,
@ -195,6 +164,37 @@ func getSANDescription(certSpec *certsphase.KubeadmCert) string {
return fmt.Sprintf("\n\nDefault SANs are %s", strings.Join(sans, ", "))
}
func runCertsSa(c workflow.RunData) error {
data, ok := c.(certsData)
if !ok {
return errors.New("certs phase invoked with an invalid data struct")
}
// if external CA mode, skip service account key generation
if data.ExternalCA() {
fmt.Printf("[certs] External CA mode: Using existing sa keys\n")
return nil
}
// if dryrunning, write certificates to a temporary folder (and defer restore to the path originally specified by the user)
cfg := data.Cfg()
cfg.CertificatesDir = data.CertificateWriteDir()
defer func() { cfg.CertificatesDir = data.CertificateDir() }()
// create the new service account key (or use existing)
return certsphase.CreateServiceAccountKeyAndPublicKeyFiles(cfg)
}
func runCerts(c workflow.RunData) error {
data, ok := c.(certsData)
if !ok {
return errors.New("certs phase invoked with an invalid data struct")
}
fmt.Printf("[certs] Using certificateDir folder %q\n", data.CertificateWriteDir())
return nil
}
func runCAPhase(ca *certsphase.KubeadmCert) func(c workflow.RunData) error {
return func(c workflow.RunData) error {
data, ok := c.(certsData)
@ -202,10 +202,24 @@ func runCAPhase(ca *certsphase.KubeadmCert) func(c workflow.RunData) error {
return errors.New("certs phase invoked with an invalid data struct")
}
// if external CA mode, skips certificate authority generation
if data.ExternalCA() {
fmt.Printf("[certs] External CA mode: Using existing %s certificate authority\n", ca.BaseName)
return nil
}
// if using external etcd, skips etcd certificate authority generation
if data.Cfg().Etcd.External != nil && ca.Name == "etcd-ca" {
fmt.Printf("[certs] External etcd mode: Skipping %s certificate authority generation\n", ca.BaseName)
return nil
}
// if dryrunning, write certificates authority to a temporary folder (and defer restore to the path originally specified by the user)
cfg := data.Cfg()
cfg.CertificatesDir = data.CertificateWriteDir()
defer func() { cfg.CertificatesDir = data.CertificateDir() }()
// create the new certificate authority (or use existing)
return certsphase.CreateCACertAndKeyFiles(ca, cfg)
}
}
@ -217,10 +231,24 @@ func runCertPhase(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert)
return errors.New("certs phase invoked with an invalid data struct")
}
// if external CA mode, skip certificate generation
if data.ExternalCA() {
fmt.Printf("[certs] External CA mode: Using existing %s certificate\n", cert.BaseName)
return nil
}
// if using external etcd, skips etcd certificates generation
if data.Cfg().Etcd.External != nil && cert.CAName == "etcd-ca" {
fmt.Printf("[certs] External etcd mode: Skipping %s certificate authority generation\n", cert.BaseName)
return nil
}
// if dryrunning, write certificates to a temporary folder (and defer restore to the path originally specified by the user)
cfg := data.Cfg()
cfg.CertificatesDir = data.CertificateWriteDir()
defer func() { cfg.CertificatesDir = data.CertificateDir() }()
// create the new certificate (or use existing)
return certsphase.CreateCertAndKeyFilesWithCA(cert, caCert, cfg)
}
}

View File

@ -19,14 +19,13 @@ package phases
import (
"fmt"
"io"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
@ -34,62 +33,136 @@ import (
)
var (
allKubeconfigLongDesc = normalizer.LongDesc(`
Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.
` + cmdutil.AlphaDisclaimer)
allKubeconfigExample = normalizer.Examples(`
# Generates all kubeconfig files, functionally equivalent to what generated
# by kubeadm init.
kubeadm alpha phase kubeconfig all
# Generates all kubeconfig files using options read from a configuration file.
kubeadm alpha phase kubeconfig all --config masterconfiguration.yaml
`)
adminKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the kubeconfig file for the admin and for kubeadm itself, and saves it to %s file.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.AdminKubeConfigFileName)
kubeletKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the kubeconfig file for the kubelet to use and saves it to %s file.
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.
`+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName))
controllerManagerKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the kubeconfig file for the controller manager to use and saves it to %s file.
`+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName))
schedulerKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the kubeconfig file for the scheduler to use and saves it to %s file.
`+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName))
kubeconfigLongDesc = normalizer.LongDesc(`
kubeconfig file utilities.
` + cmdutil.AlphaDisclaimer)
userKubeconfigLongDesc = normalizer.LongDesc(`
Outputs a kubeconfig file for an additional user.
` + cmdutil.AlphaDisclaimer)
Outputs a kubeconfig file for an additional user.
` + cmdutil.AlphaDisclaimer)
userKubeconfigExample = normalizer.Examples(`
# Outputs a kubeconfig file for an additional user named foo
kubeadm alpha phase kubeconfig user --client-name=foo
`)
# Outputs a kubeconfig file for an additional user named foo
kubeadm alpha kubeconfig user --client-name=foo
`)
kubeconfigFilePhaseProperties = map[string]struct {
name string
short string
long string
}{
kubeadmconstants.AdminKubeConfigFileName: {
name: "admin",
short: "Generates a kubeconfig file for the admin to use and for kubeadm itself",
long: "Generates the kubeconfig file for the admin and for kubeadm itself, and saves it to %s file.",
},
kubeadmconstants.KubeletKubeConfigFileName: {
name: "kubelet",
short: "Generates a kubeconfig file for the kubelet to use *only* for cluster bootstrapping purposes",
long: normalizer.LongDesc(`
Generates the kubeconfig file for the kubelet to use and saves it to %s file.
Please note that this should *only* be used for cluster bootstrapping purposes. After your control plane is up,
you should request all kubelet credentials from the CSR API.`),
},
kubeadmconstants.ControllerManagerKubeConfigFileName: {
name: "controller-manager",
short: "Generates a kubeconfig file for the controller manager to use",
long: "Generates the kubeconfig file for the controller manager to use and saves it to %s file",
},
kubeadmconstants.SchedulerKubeConfigFileName: {
name: "scheduler",
short: "Generates a kubeconfig file for the scheduler to use",
long: "Generates the kubeconfig file for the scheduler to use and saves it to %s file.",
},
}
)
// kubeConfigData defines the behavior that a runtime data struct passed to the kubeconfig phase
// should have. Please note that we are using an interface in order to make this phase reusable in different workflows
// (and thus with different runtime data struct, all of them requested to be compliant to this interface)
type kubeConfigData interface {
Cfg() *kubeadmapi.InitConfiguration
ExternalCA() bool
CertificateDir() string
CertificateWriteDir() string
KubeConfigDir() string
}
// NewKubeConfigPhase creates a kubeadm workflow phase that creates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.
func NewKubeConfigPhase() workflow.Phase {
return workflow.Phase{
Name: "kubeconfig",
Short: "Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file",
Phases: []workflow.Phase{
NewKubeConfigFilePhase(kubeadmconstants.AdminKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.KubeletKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.ControllerManagerKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.SchedulerKubeConfigFileName),
},
Run: runKubeConfig,
}
}
// NewKubeConfigFilePhase creates a kubeadm workflow phase that creates a kubeconfig file.
func NewKubeConfigFilePhase(kubeConfigFileName string) workflow.Phase {
return workflow.Phase{
Name: kubeconfigFilePhaseProperties[kubeConfigFileName].name,
Short: kubeconfigFilePhaseProperties[kubeConfigFileName].short,
Long: fmt.Sprintf(kubeconfigFilePhaseProperties[kubeConfigFileName].long, kubeConfigFileName),
Run: runKubeConfigFile(kubeConfigFileName),
}
}
func runKubeConfig(c workflow.RunData) error {
data, ok := c.(kubeConfigData)
if !ok {
return errors.New("kubeconfig phase invoked with an invalid data struct")
}
fmt.Printf("[kubeconfig] Using kubeconfig folder %q\n", data.KubeConfigDir())
return nil
}
// runKubeConfigFile executes kubeconfig creation logic.
func runKubeConfigFile(kubeConfigFileName string) func(workflow.RunData) error {
return func(c workflow.RunData) error {
data, ok := c.(kubeConfigData)
if !ok {
return errors.New("kubeconfig phase invoked with an invalid data struct")
}
// if external CA mode, skip certificate authority generation
if data.ExternalCA() {
//TODO: implement validation of existing kubeconfig files
fmt.Printf("[kubeconfig] External CA mode: Using user provided %s\n", kubeConfigFileName)
return nil
}
// if dryrunning, reads certificates from a temporary folder (and defer restore to the path originally specified by the user)
cfg := data.Cfg()
cfg.CertificatesDir = data.CertificateWriteDir()
defer func() { cfg.CertificatesDir = data.CertificateDir() }()
// creates the KubeConfig file (or use existing)
return kubeconfigphase.CreateKubeConfigFile(kubeConfigFileName, data.KubeConfigDir(), data.Cfg())
}
}
// NewCmdKubeConfig returns main command for kubeconfig phase
func NewCmdKubeConfig(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "kubeconfig",
Short: "Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file",
Long: cmdutil.MacroCommandLongDescription,
Short: "kubeconfig file utilities",
Long: kubeconfigLongDesc,
}
cmd.AddCommand(getKubeConfigSubCommands(out, kubeadmconstants.KubernetesDir, "")...)
cmd.AddCommand(NewCmdUserKubeConfig(out, kubeadmconstants.KubernetesDir, ""))
return cmd
}
// getKubeConfigSubCommands returns sub commands for kubeconfig phase
func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion string) []*cobra.Command {
// NewCmdUserKubeConfig returns sub commands for kubeconfig phase
func NewCmdUserKubeConfig(out io.Writer, outDir, defaultKubernetesVersion string) *cobra.Command {
cfg := &kubeadmapiv1beta1.InitConfiguration{}
@ -98,96 +171,36 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st
var cfgPath, token, clientName string
var organizations []string
var subCmds []*cobra.Command
subCmdProperties := []struct {
use string
short string
long string
examples string
cmdFunc func(outDir string, cfg *kubeadmapi.InitConfiguration) error
}{
{
use: "all",
short: "Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file",
long: allKubeconfigLongDesc,
examples: allKubeconfigExample,
cmdFunc: kubeconfigphase.CreateInitKubeConfigFiles,
},
{
use: "admin",
short: "Generates a kubeconfig file for the admin to use and for kubeadm itself",
long: adminKubeconfigLongDesc,
cmdFunc: kubeconfigphase.CreateAdminKubeConfigFile,
},
{
use: "kubelet",
short: "Generates a kubeconfig file for the kubelet to use. Please note that this should be used *only* for bootstrapping purposes",
long: kubeletKubeconfigLongDesc,
cmdFunc: kubeconfigphase.CreateKubeletKubeConfigFile,
},
{
use: "controller-manager",
short: "Generates a kubeconfig file for the controller manager to use",
long: controllerManagerKubeconfigLongDesc,
cmdFunc: kubeconfigphase.CreateControllerManagerKubeConfigFile,
},
{
use: "scheduler",
short: "Generates a kubeconfig file for the scheduler to use",
long: schedulerKubeconfigLongDesc,
cmdFunc: kubeconfigphase.CreateSchedulerKubeConfigFile,
},
{
use: "user",
short: "Outputs a kubeconfig file for an additional user",
long: userKubeconfigLongDesc,
examples: userKubeconfigExample,
cmdFunc: func(outDir string, cfg *kubeadmapi.InitConfiguration) error {
if clientName == "" {
return errors.New("missing required argument --client-name")
}
// Creates the UX Command
cmd := &cobra.Command{
Use: "user",
Short: "Outputs a kubeconfig file for an additional user",
Long: userKubeconfigLongDesc,
Example: userKubeconfigExample,
Run: runCmdPhase(func(outDir string, cfg *kubeadmapi.InitConfiguration) error {
if clientName == "" {
return errors.New("missing required argument --client-name")
}
// if the kubeconfig file for an additional user has to use a token, use it
if token != "" {
return kubeconfigphase.WriteKubeConfigWithToken(out, cfg, clientName, token)
}
// if the kubeconfig file for an additional user has to use a token, use it
if token != "" {
return kubeconfigphase.WriteKubeConfigWithToken(out, cfg, clientName, token)
}
// Otherwise, write a kubeconfig file with a generate client cert
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg, clientName, organizations)
},
},
// Otherwise, write a kubeconfig file with a generate client cert
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg, clientName, organizations)
}, &outDir, &cfgPath, cfg, defaultKubernetesVersion),
}
for _, properties := range subCmdProperties {
// Creates the UX Command
cmd := &cobra.Command{
Use: properties.use,
Short: properties.short,
Long: properties.long,
Example: properties.examples,
Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg, defaultKubernetesVersion),
}
// Add flags to the command
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where certificates are stored")
cmd.Flags().StringVar(&cfg.APIEndpoint.AdvertiseAddress, "apiserver-advertise-address", cfg.APIEndpoint.AdvertiseAddress, "The IP address the API server is accessible on")
cmd.Flags().Int32Var(&cfg.APIEndpoint.BindPort, "apiserver-bind-port", cfg.APIEndpoint.BindPort, "The port the API server is accessible on")
cmd.Flags().StringVar(&outDir, "kubeconfig-dir", outDir, "The path where to save the kubeconfig file")
cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig, instead of client certificates")
cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of user. It will be used as the CN if client certificates are created")
cmd.Flags().StringSliceVar(&organizations, "org", organizations, "The orgnizations of the client certificate. It will be used as the O if client certificates are created")
// Add flags to the command
if properties.use != "user" {
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
}
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where certificates are stored")
cmd.Flags().StringVar(&cfg.APIEndpoint.AdvertiseAddress, "apiserver-advertise-address", cfg.APIEndpoint.AdvertiseAddress, "The IP address the API server is accessible on")
cmd.Flags().Int32Var(&cfg.APIEndpoint.BindPort, "apiserver-bind-port", cfg.APIEndpoint.BindPort, "The port the API server is accessible on")
cmd.Flags().StringVar(&outDir, "kubeconfig-dir", outDir, "The path where to save the kubeconfig file")
if properties.use == "all" || properties.use == "kubelet" {
cmd.Flags().StringVar(&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, `The node name that should be used for the kubelet client certificate`)
}
if properties.use == "user" {
cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig, instead of client certificates")
cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of user. It will be used as the CN if client certificates are created")
cmd.Flags().StringSliceVar(&organizations, "org", organizations, "The orgnizations of the client certificate. It will be used as the O if client certificates are created")
}
subCmds = append(subCmds, cmd)
}
return subCmds
return cmd
}

View File

@ -20,299 +20,30 @@ import (
"bytes"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
kubeconfigtestutil "k8s.io/kubernetes/cmd/kubeadm/test/kubeconfig"
)
func TestKubeConfigCSubCommandsHasFlags(t *testing.T) {
cmd := NewCmdUserKubeConfig(nil, "", phaseTestK8sVersion)
subCmds := getKubeConfigSubCommands(nil, "", phaseTestK8sVersion)
commonFlags := []string{
flags := []string{
"cert-dir",
"apiserver-advertise-address",
"apiserver-bind-port",
"kubeconfig-dir",
"token",
"client-name",
}
var tests = []struct {
command string
additionalFlags []string
}{
{
command: "all",
additionalFlags: []string{
"config",
"node-name",
},
},
{
command: "admin",
additionalFlags: []string{
"config",
},
},
{
command: "kubelet",
additionalFlags: []string{
"config",
"node-name",
},
},
{
command: "controller-manager",
additionalFlags: []string{
"config",
},
},
{
command: "scheduler",
additionalFlags: []string{
"config",
},
},
{
command: "user",
additionalFlags: []string{
"token",
"client-name",
},
},
}
for _, test := range tests {
expectedFlags := append(commonFlags, test.additionalFlags...)
cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...)
}
}
func TestKubeConfigSubCommandsThatCreateFilesWithFlags(t *testing.T) {
commonFlags := []string{
"--apiserver-advertise-address=1.2.3.4",
"--apiserver-bind-port=1234",
}
var tests = []struct {
command string
additionalFlags []string
expectedFiles []string
}{
{
command: "all",
additionalFlags: []string{"--node-name=valid-nome-name"},
expectedFiles: []string{
kubeadmconstants.AdminKubeConfigFileName,
kubeadmconstants.KubeletKubeConfigFileName,
kubeadmconstants.ControllerManagerKubeConfigFileName,
kubeadmconstants.SchedulerKubeConfigFileName,
},
},
{
command: "admin",
expectedFiles: []string{kubeadmconstants.AdminKubeConfigFileName},
},
{
command: "kubelet",
additionalFlags: []string{"--node-name=valid-nome-name"},
expectedFiles: []string{kubeadmconstants.KubeletKubeConfigFileName},
},
{
command: "controller-manager",
expectedFiles: []string{kubeadmconstants.ControllerManagerKubeConfigFileName},
},
{
command: "scheduler",
expectedFiles: []string{kubeadmconstants.SchedulerKubeConfigFileName},
},
}
var kubeConfigAssertions = map[string]struct {
clientName string
organizations []string
}{
kubeadmconstants.AdminKubeConfigFileName: {
clientName: "kubernetes-admin",
organizations: []string{kubeadmconstants.MastersGroup},
},
kubeadmconstants.KubeletKubeConfigFileName: {
clientName: "system:node:valid-nome-name",
organizations: []string{kubeadmconstants.NodesGroup},
},
kubeadmconstants.ControllerManagerKubeConfigFileName: {
clientName: kubeadmconstants.ControllerManagerUser,
},
kubeadmconstants.SchedulerKubeConfigFileName: {
clientName: kubeadmconstants.SchedulerUser,
},
}
for _, test := range tests {
// Create temp folder for the test case
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
// Adds a pki folder with a ca certs to the temp folder
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)
outputdir := tmpdir
// Retrieves ca cert for assertions
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName)
if err != nil {
t.Fatalf("couldn't retrieve ca cert: %v", err)
}
// Get subcommands working in the temporary directory
subCmds := getKubeConfigSubCommands(nil, tmpdir, phaseTestK8sVersion)
// Execute the subcommand
certDirFlag := fmt.Sprintf("--cert-dir=%s", pkidir)
outputDirFlag := fmt.Sprintf("--kubeconfig-dir=%s", outputdir)
allFlags := append(commonFlags, certDirFlag)
allFlags = append(allFlags, outputDirFlag)
allFlags = append(allFlags, test.additionalFlags...)
cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...)
// Checks that requested files are there
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
// Checks contents of generated files
for _, file := range test.expectedFiles {
// reads generated files
config, err := clientcmd.LoadFromFile(filepath.Join(tmpdir, file))
if err != nil {
t.Errorf("couldn't load generated kubeconfig file: %v", err)
}
// checks that CLI flags are properly propagated and kubeconfig properties are correct
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
expectedClientName := kubeConfigAssertions[file].clientName
expectedOrganizations := kubeConfigAssertions[file].organizations
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, expectedClientName, expectedOrganizations...)
}
}
}
func TestKubeConfigSubCommandsThatCreateFilesWithConfigFile(t *testing.T) {
var tests = []struct {
command string
expectedFiles []string
}{
{
command: "all",
expectedFiles: []string{
kubeadmconstants.AdminKubeConfigFileName,
kubeadmconstants.KubeletKubeConfigFileName,
kubeadmconstants.ControllerManagerKubeConfigFileName,
kubeadmconstants.SchedulerKubeConfigFileName,
},
},
{
command: "admin",
expectedFiles: []string{kubeadmconstants.AdminKubeConfigFileName},
},
{
command: "kubelet",
expectedFiles: []string{kubeadmconstants.KubeletKubeConfigFileName},
},
{
command: "controller-manager",
expectedFiles: []string{kubeadmconstants.ControllerManagerKubeConfigFileName},
},
{
command: "scheduler",
expectedFiles: []string{kubeadmconstants.SchedulerKubeConfigFileName},
},
}
var kubeConfigAssertions = map[string]struct {
clientName string
organizations []string
}{
kubeadmconstants.AdminKubeConfigFileName: {
clientName: "kubernetes-admin",
organizations: []string{kubeadmconstants.MastersGroup},
},
kubeadmconstants.KubeletKubeConfigFileName: {
clientName: "system:node:valid-node-name",
organizations: []string{kubeadmconstants.NodesGroup},
},
kubeadmconstants.ControllerManagerKubeConfigFileName: {
clientName: kubeadmconstants.ControllerManagerUser,
},
kubeadmconstants.SchedulerKubeConfigFileName: {
clientName: kubeadmconstants.SchedulerUser,
},
}
for _, test := range tests {
// Create temp folder for the test case
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
// Adds a pki folder with a ca certs to the temp folder
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)
// Retrieves ca cert for assertions
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName)
if err != nil {
t.Fatalf("couldn't retrieve ca cert: %v", err)
}
// Adds a master configuration file
cfg := &kubeadmapi.InitConfiguration{
APIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
CertificatesDir: pkidir,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"},
}
cfgPath := testutil.SetupInitConfigurationFile(t, tmpdir, cfg)
// Get subcommands working in the temporary directory
subCmds := getKubeConfigSubCommands(nil, tmpdir, phaseTestK8sVersion)
// Execute the subcommand
configFlag := fmt.Sprintf("--config=%s", cfgPath)
cmdtestutil.RunSubCommand(t, subCmds, test.command, configFlag)
// Checks that requested files are there
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
// Checks contents of generated files
for _, file := range test.expectedFiles {
// reads generated files
config, err := clientcmd.LoadFromFile(filepath.Join(tmpdir, file))
if err != nil {
t.Errorf("couldn't load generated kubeconfig file: %v", err)
}
// checks that config file properties are properly propagated and kubeconfig properties are correct
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
expectedClientName := kubeConfigAssertions[file].clientName
expectedOrganizations := kubeConfigAssertions[file].organizations
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, expectedClientName, expectedOrganizations...)
}
}
cmdtestutil.AssertSubCommandHasFlags(t, []*cobra.Command{cmd}, "user", flags...)
}
func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
@ -361,11 +92,11 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
buf := new(bytes.Buffer)
// Get subcommands working in the temporary directory
subCmds := getKubeConfigSubCommands(buf, tmpdir, phaseTestK8sVersion)
cmd := NewCmdUserKubeConfig(buf, tmpdir, phaseTestK8sVersion)
// Execute the subcommand
allFlags := append(commonFlags, test.additionalFlags...)
cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...)
cmdtestutil.RunSubCommand(t, []*cobra.Command{cmd}, test.command, allFlags...)
// reads kubeconfig written to stdout
config, err := clientcmd.Load(buf.Bytes())

View File

@ -81,7 +81,7 @@ func runPreflightMaster(c workflow.RunData) error {
return errors.New("preflight phase invoked with an invalid data struct")
}
fmt.Println("[preflight] running pre-flight checks")
fmt.Println("[preflight] Running pre-flight checks")
if err := preflight.RunInitMasterChecks(utilsexec.New(), data.Cfg(), data.IgnorePreflightErrors()); err != nil {
return err
}

View File

@ -25,7 +25,6 @@ import (
"github.com/golang/glog"
"github.com/pkg/errors"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -57,7 +56,7 @@ func CreatePKIAssets(cfg *kubeadmapi.InitConfiguration) error {
return errors.Wrap(err, "error creating PKI assets")
}
fmt.Printf("[certificates] valid certificates and keys now exist in %q\n", cfg.CertificatesDir)
fmt.Printf("[certs] valid certificates and keys now exist in %q\n", cfg.CertificatesDir)
// Service accounts are not x509 certs, so handled separately
if err := CreateServiceAccountKeyAndPublicKeyFiles(cfg); err != nil {
@ -190,15 +189,14 @@ func writeCertificateAuthorithyFilesIfNotExist(pkiDir string, baseName string, c
// kubeadm doesn't validate the existing certificate Authority more than this;
// Basically, if we find a certificate file with the same path; and it is a CA
// kubeadm thinks those files are equal and doesn't bother writing a new file
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName)
fmt.Printf("[certs] Using the existing %q certificate and key\n", baseName)
} else {
// Write .crt and .key files to disk
fmt.Printf("[certs] Generating %q certificate and key\n", baseName)
if err := pkiutil.WriteCertAndKey(pkiDir, baseName, caCert, caKey); err != nil {
return errors.Wrapf(err, "failure while saving %s certificate and key", baseName)
}
fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName)
}
return nil
}
@ -226,17 +224,16 @@ func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert
// Basically, if we find a certificate file with the same path; and it is signed by
// the expected certificate authority, kubeadm thinks those files are equal and
// doesn't bother writing a new file
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName)
fmt.Printf("[certs] Using the existing %q certificate and key\n", baseName)
} else {
// Write .crt and .key files to disk
fmt.Printf("[certs] Generating %q certificate and key\n", baseName)
if err := pkiutil.WriteCertAndKey(pkiDir, baseName, cert, key); err != nil {
return errors.Wrapf(err, "failure while saving %s certificate and key", baseName)
}
fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName)
if pkiutil.HasServerAuth(cert) {
fmt.Printf("[certificates] %s serving cert is signed for DNS names %v and IPs %v\n", baseName, cert.DNSNames, cert.IPAddresses)
fmt.Printf("[certs] %s serving cert is signed for DNS names %v and IPs %v\n", baseName, cert.DNSNames, cert.IPAddresses)
}
}
@ -261,10 +258,12 @@ func writeKeyFilesIfNotExist(pkiDir string, baseName string, key *rsa.PrivateKey
// kubeadm doesn't validate the existing certificate key more than this;
// Basically, if we find a key file with the same path kubeadm thinks those files
// are equal and doesn't bother writing a new file
fmt.Printf("[certificates] Using the existing %s key.\n", baseName)
fmt.Printf("[certs] Using the existing %q key\n", baseName)
} else {
// Write .key and .pub files to disk
fmt.Printf("[certs] Generating %q key and public key\n", baseName)
if err := pkiutil.WriteKey(pkiDir, baseName, key); err != nil {
return errors.Wrapf(err, "failure while saving %s key", baseName)
}
@ -272,7 +271,6 @@ func writeKeyFilesIfNotExist(pkiDir string, baseName string, key *rsa.PrivateKey
if err := pkiutil.WritePublicKey(pkiDir, baseName, &key.PublicKey); err != nil {
return errors.Wrapf(err, "failure while saving %s public key", baseName)
}
fmt.Printf("[certificates] Generated %s key and public key.\n", baseName)
}
return nil

View File

@ -18,17 +18,15 @@ package kubeconfig
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"fmt"
"io"
"os"
"path/filepath"
"crypto/rsa"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
certutil "k8s.io/client-go/util/cert"
@ -88,32 +86,11 @@ func CreateJoinControlPlaneKubeConfigFiles(outDir string, cfg *kubeadmapi.InitCo
)
}
// CreateAdminKubeConfigFile create a kubeconfig file for the admin to use and for kubeadm itself.
// CreateKubeConfigFile creates a kubeconfig file.
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
func CreateAdminKubeConfigFile(outDir string, cfg *kubeadmapi.InitConfiguration) error {
glog.V(1).Infoln("create a kubeconfig file for the admin and for kubeadm itself")
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.AdminKubeConfigFileName)
}
// CreateKubeletKubeConfigFile create a kubeconfig file for the Kubelet to use.
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
func CreateKubeletKubeConfigFile(outDir string, cfg *kubeadmapi.InitConfiguration) error {
glog.V(1).Infoln("creating a kubeconfig file for the Kubelet")
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.KubeletKubeConfigFileName)
}
// CreateControllerManagerKubeConfigFile create a kubeconfig file for the ControllerManager to use.
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
func CreateControllerManagerKubeConfigFile(outDir string, cfg *kubeadmapi.InitConfiguration) error {
glog.V(1).Infoln("creating kubeconfig file for the ControllerManager")
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.ControllerManagerKubeConfigFileName)
}
// CreateSchedulerKubeConfigFile create a create a kubeconfig file for the Scheduler to use.
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
func CreateSchedulerKubeConfigFile(outDir string, cfg *kubeadmapi.InitConfiguration) error {
glog.V(1).Infoln("creating kubeconfig file for Scheduler")
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.SchedulerKubeConfigFileName)
func CreateKubeConfigFile(kubeConfigFileName string, outDir string, cfg *kubeadmapi.InitConfiguration) error {
glog.V(1).Infof("creating kubeconfig file for %s", kubeConfigFileName)
return createKubeConfigFiles(outDir, cfg, kubeConfigFileName)
}
// createKubeConfigFiles creates all the requested kubeconfig files.
@ -248,12 +225,12 @@ func createKubeConfigFileIfNotExists(outDir, filename string, config *clientcmda
// Check if the file exist, and if it doesn't, just write it to disk
if _, err := os.Stat(kubeConfigFilePath); os.IsNotExist(err) {
fmt.Printf("[kubeconfig] Writing %q kubeconfig file\n", filename)
err = kubeconfigutil.WriteToDisk(kubeConfigFilePath, config)
if err != nil {
return errors.Wrapf(err, "failed to save kubeconfig file %s on disk", kubeConfigFilePath)
}
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", kubeConfigFilePath)
return nil
}

View File

@ -292,22 +292,6 @@ func TestCreateKubeconfigFilesAndWrappers(t *testing.T) {
kubeadmconstants.SchedulerKubeConfigFileName,
},
},
{ // Test CreateAdminKubeConfigFile (wrapper to createKubeConfigFile)
createKubeConfigFunction: CreateAdminKubeConfigFile,
expectedFiles: []string{kubeadmconstants.AdminKubeConfigFileName},
},
{ // Test CreateKubeletKubeConfigFile (wrapper to createKubeConfigFile)
createKubeConfigFunction: CreateKubeletKubeConfigFile,
expectedFiles: []string{kubeadmconstants.KubeletKubeConfigFileName},
},
{ // Test CreateControllerManagerKubeConfigFile (wrapper to createKubeConfigFile)
createKubeConfigFunction: CreateControllerManagerKubeConfigFile,
expectedFiles: []string{kubeadmconstants.ControllerManagerKubeConfigFileName},
},
{ // Test createKubeConfigFile (wrapper to createKubeConfigFile)
createKubeConfigFunction: CreateSchedulerKubeConfigFile,
expectedFiles: []string{kubeadmconstants.SchedulerKubeConfigFileName},
},
}
for _, test := range tests {

View File

@ -40,18 +40,16 @@ func TestCmdInitToken(t *testing.T) {
args string
expected bool
}{
/*
{
name: "invalid token size",
args: "--token=abcd:1234567890abcd",
expected: false,
},
{
name: "invalid token non-lowercase",
args: "--token=Abcdef:1234567890abcdef",
expected: false,
},
*/
{
name: "invalid token size",
args: "--token=abcd:1234567890abcd",
expected: false,
},
{
name: "invalid token non-lowercase",
args: "--token=Abcdef:1234567890abcdef",
expected: false,
},
{
name: "valid token is accepted",
args: "--token=abcdef.0123456789abcdef",

View File

@ -36,11 +36,6 @@ docs/admin/kubeadm_alpha_phase_controlplane_scheduler.md
docs/admin/kubeadm_alpha_phase_etcd.md
docs/admin/kubeadm_alpha_phase_etcd_local.md
docs/admin/kubeadm_alpha_phase_kubeconfig.md
docs/admin/kubeadm_alpha_phase_kubeconfig_admin.md
docs/admin/kubeadm_alpha_phase_kubeconfig_all.md
docs/admin/kubeadm_alpha_phase_kubeconfig_controller-manager.md
docs/admin/kubeadm_alpha_phase_kubeconfig_kubelet.md
docs/admin/kubeadm_alpha_phase_kubeconfig_scheduler.md
docs/admin/kubeadm_alpha_phase_kubeconfig_user.md
docs/admin/kubeadm_alpha_phase_kubelet.md
docs/admin/kubeadm_alpha_phase_kubelet_config.md
@ -81,6 +76,11 @@ docs/admin/kubeadm_init_phase_certs_etcd-server.md
docs/admin/kubeadm_init_phase_certs_front-proxy-ca.md
docs/admin/kubeadm_init_phase_certs_front-proxy-client.md
docs/admin/kubeadm_init_phase_certs_sa.md
docs/admin/kubeadm_init_phase_kubeconfig.md
docs/admin/kubeadm_init_phase_kubeconfig_admin.md
docs/admin/kubeadm_init_phase_kubeconfig_controller-manager.md
docs/admin/kubeadm_init_phase_kubeconfig_kubelet.md
docs/admin/kubeadm_init_phase_kubeconfig_scheduler.md
docs/admin/kubeadm_init_phase_kubelet-start.md
docs/admin/kubeadm_init_phase_preflight.md
docs/admin/kubeadm_join.md
@ -132,11 +132,6 @@ docs/man/man1/kubeadm-alpha-phase-controlplane-scheduler.1
docs/man/man1/kubeadm-alpha-phase-controlplane.1
docs/man/man1/kubeadm-alpha-phase-etcd-local.1
docs/man/man1/kubeadm-alpha-phase-etcd.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-admin.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-all.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-controller-manager.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-kubelet.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-scheduler.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig-user.1
docs/man/man1/kubeadm-alpha-phase-kubeconfig.1
docs/man/man1/kubeadm-alpha-phase-kubelet-config-annotate-cri.1
@ -179,6 +174,11 @@ docs/man/man1/kubeadm-init-phase-certs-front-proxy-ca.1
docs/man/man1/kubeadm-init-phase-certs-front-proxy-client.1
docs/man/man1/kubeadm-init-phase-certs-sa.1
docs/man/man1/kubeadm-init-phase-certs.1
docs/man/man1/kubeadm-init-phase-kubeconfig-admin.1
docs/man/man1/kubeadm-init-phase-kubeconfig-controller-manager.1
docs/man/man1/kubeadm-init-phase-kubeconfig-kubelet.1
docs/man/man1/kubeadm-init-phase-kubeconfig-scheduler.1
docs/man/man1/kubeadm-init-phase-kubeconfig.1
docs/man/man1/kubeadm-init-phase-kubelet-start.1
docs/man/man1/kubeadm-init-phase-preflight.1
docs/man/man1/kubeadm-init-phase.1