Merge pull request #77847 from yagonobre/reset-phase

Add phase runner to kubeadm reset
This commit is contained in:
Kubernetes Prow Robot 2019-05-16 06:05:56 -07:00 committed by GitHub
commit d823fa23c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 79 deletions

View File

@ -86,7 +86,6 @@ go_test(
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/options:go_default_library", "//cmd/kubeadm/app/cmd/options:go_default_library",
"//cmd/kubeadm/app/componentconfigs:go_default_library", "//cmd/kubeadm/app/componentconfigs:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",

View File

@ -84,7 +84,7 @@ func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {
cmds.AddCommand(NewCmdConfig(out)) cmds.AddCommand(NewCmdConfig(out))
cmds.AddCommand(NewCmdInit(out, nil)) cmds.AddCommand(NewCmdInit(out, nil))
cmds.AddCommand(NewCmdJoin(out, nil)) cmds.AddCommand(NewCmdJoin(out, nil))
cmds.AddCommand(NewCmdReset(in, out)) cmds.AddCommand(NewCmdReset(in, out, nil))
cmds.AddCommand(NewCmdVersion(out)) cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdToken(out, err)) cmds.AddCommand(NewCmdToken(out, err))
cmds.AddCommand(upgrade.NewCmdUpgrade(out)) cmds.AddCommand(upgrade.NewCmdUpgrade(out))

View File

@ -127,4 +127,7 @@ const (
// SkipCertificateKeyPrint flag instruct kubeadm to skip printing certificate key used to encrypt certs by 'kubeadm init'. // SkipCertificateKeyPrint flag instruct kubeadm to skip printing certificate key used to encrypt certs by 'kubeadm init'.
SkipCertificateKeyPrint = "skip-certificate-key-print" SkipCertificateKeyPrint = "skip-certificate-key-print"
// ForceReset flag instruct kubeadm to reset the node without prompting for confirmation
ForceReset = "force"
) )

View File

@ -28,6 +28,7 @@ import (
"github.com/lithammer/dedent" "github.com/lithammer/dedent"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog" "k8s.io/klog"
@ -35,6 +36,7 @@ import (
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
@ -48,99 +50,197 @@ import (
utilsexec "k8s.io/utils/exec" utilsexec "k8s.io/utils/exec"
) )
// NewCmdReset returns the "kubeadm reset" command // resetOptions defines all the options exposed via flags by kubeadm reset.
func NewCmdReset(in io.Reader, out io.Writer) *cobra.Command { type resetOptions struct {
var certsDir string certificatesDir string
criSocketPath string
forceReset bool
ignorePreflightErrors []string
kubeconfigPath string
}
// resetData defines all the runtime information used when running the kubeadm reset worklow;
// this data is shared across all the phases that are included in the workflow.
type resetData struct {
certificatesDir string
client clientset.Interface
criSocketPath string
forceReset bool
ignorePreflightErrors sets.String
inputReader io.Reader
outputWriter io.Writer
cfg *kubeadmapi.InitConfiguration
}
// newResetOptions returns a struct ready for being used for creating cmd join flags.
func newResetOptions() *resetOptions {
return &resetOptions{
certificatesDir: kubeadmapiv1beta2.DefaultCertificatesDir,
forceReset: false,
kubeconfigPath: kubeadmconstants.GetAdminKubeConfigPath(),
}
}
// newResetData returns a new resetData struct to be used for the execution of the kubeadm reset workflow.
func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out io.Writer) (*resetData, error) {
var cfg *kubeadmapi.InitConfiguration
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors)
if err != nil {
return nil, err
}
client, err := getClientset(options.kubeconfigPath, false)
if err == nil {
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false)
if err != nil {
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
}
} else {
klog.V(1).Infof("[reset] Could not obtain a client set from the kubeconfig file: %s", options.kubeconfigPath)
}
var criSocketPath string var criSocketPath string
var ignorePreflightErrors []string if options.criSocketPath == "" {
var forceReset bool criSocketPath, err = resetDetectCRISocket(cfg)
var client clientset.Interface if err != nil {
kubeConfigFile := kubeadmconstants.GetAdminKubeConfigPath() return nil, err
}
klog.V(1).Infof("[reset] Detected and using CRI socket: %s", criSocketPath)
}
return &resetData{
certificatesDir: options.certificatesDir,
client: client,
criSocketPath: criSocketPath,
forceReset: options.forceReset,
ignorePreflightErrors: ignorePreflightErrorsSet,
inputReader: in,
outputWriter: out,
cfg: cfg,
}, nil
}
// AddResetFlags adds reset flags
func AddResetFlags(flagSet *flag.FlagSet, resetOptions *resetOptions) {
flagSet.StringVar(
&resetOptions.certificatesDir, options.CertificatesDir, resetOptions.certificatesDir,
`The path to the directory where the certificates are stored. If specified, clean this directory.`,
)
flagSet.BoolVarP(
&resetOptions.forceReset, options.ForceReset, "f", false,
"Reset the node without prompting for confirmation.",
)
options.AddKubeConfigFlag(flagSet, &resetOptions.kubeconfigPath)
options.AddIgnorePreflightErrorsFlag(flagSet, &resetOptions.ignorePreflightErrors)
cmdutil.AddCRISocketFlag(flagSet, &resetOptions.criSocketPath)
}
// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(in io.Reader, out io.Writer, resetOptions *resetOptions) *cobra.Command {
if resetOptions == nil {
resetOptions = newResetOptions()
}
resetRunner := workflow.NewRunner()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "reset", Use: "reset",
Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'", Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors) c, err := resetRunner.InitData(args)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
var cfg *kubeadmapi.InitConfiguration err = resetRunner.Run(args)
client, err = getClientset(kubeConfigFile, false)
if err == nil {
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", kubeConfigFile)
cfg, err = configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "reset", false)
if err != nil {
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
}
} else {
klog.V(1).Infof("[reset] Could not obtain a client set from the kubeconfig file: %s", kubeConfigFile)
}
if criSocketPath == "" {
criSocketPath, err = resetDetectCRISocket(cfg)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
klog.V(1).Infof("[reset] Detected and using CRI socket: %s", criSocketPath) // TODO: remove this once we have all phases in place.
} // the method joinData.Run() itself should be removed too.
data := c.(*resetData)
r, err := NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath) kubeadmutil.CheckErr(data.Run())
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(r.Run(out, client, cfg))
}, },
} }
options.AddIgnorePreflightErrorsFlag(cmd.PersistentFlags(), &ignorePreflightErrors) AddResetFlags(cmd.Flags(), resetOptions)
options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile)
cmd.PersistentFlags().StringVar( // initialize the workflow runner with the list of phases
&certsDir, "cert-dir", kubeadmapiv1beta2.DefaultCertificatesDir, // TODO: append phases here
"The path to the directory where the certificates are stored. If specified, clean this directory.",
)
cmdutil.AddCRISocketFlag(cmd.PersistentFlags(), &criSocketPath) // sets the data builder function, that will be used by the runner
// both when running the entire workflow or single phases
resetRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) {
return newResetData(cmd, resetOptions, in, out)
})
cmd.PersistentFlags().BoolVarP( // binds the Runner to kubeadm init command by altering
&forceReset, "force", "f", false, // command help, adding --skip-phases flag and by adding phases subcommands
"Reset the node without prompting for confirmation.", resetRunner.BindToCommand(cmd)
)
return cmd return cmd
} }
// Reset defines struct used for kubeadm reset command // Cfg returns the InitConfiguration.
type Reset struct { func (r *resetData) Cfg() *kubeadmapi.InitConfiguration {
certsDir string return r.cfg
criSocketPath string
} }
// NewReset instantiate Reset struct // CertificatesDir returns the CertificatesDir.
func NewReset(in io.Reader, ignorePreflightErrors sets.String, forceReset bool, certsDir, criSocketPath string) (*Reset, error) { func (r *resetData) CertificatesDir() string {
if !forceReset { return r.certificatesDir
}
// Client returns the Client for accessing the cluster.
func (r *resetData) Client() clientset.Interface {
return r.client
}
// ForceReset returns the forceReset flag.
func (r *resetData) ForceReset() bool {
return r.forceReset
}
// InputReader returns the io.reader used to read messages.
func (r *resetData) InputReader() io.Reader {
return r.inputReader
}
// IgnorePreflightErrors returns the list of preflight errors to ignore.
func (r *resetData) IgnorePreflightErrors() sets.String {
return r.ignorePreflightErrors
}
func (r *resetData) preflight() error {
if !r.ForceReset() {
fmt.Println("[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.") fmt.Println("[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.")
fmt.Print("[reset] Are you sure you want to proceed? [y/N]: ") fmt.Print("[reset] Are you sure you want to proceed? [y/N]: ")
s := bufio.NewScanner(in) s := bufio.NewScanner(r.InputReader())
s.Scan() s.Scan()
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
return nil, err return err
} }
if strings.ToLower(s.Text()) != "y" { if strings.ToLower(s.Text()) != "y" {
return nil, errors.New("Aborted reset operation") return errors.New("Aborted reset operation")
} }
} }
fmt.Println("[preflight] Running pre-flight checks") fmt.Println("[preflight] Running pre-flight checks")
if err := preflight.RunRootCheckOnly(ignorePreflightErrors); err != nil { if err := preflight.RunRootCheckOnly(r.IgnorePreflightErrors()); err != nil {
return nil, err return err
} }
return &Reset{ return nil
certsDir: certsDir,
criSocketPath: criSocketPath,
}, nil
} }
// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join". // Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer, client clientset.Interface, cfg *kubeadmapi.InitConfiguration) error { func (r *resetData) Run() error {
var dirsToClean []string var dirsToClean []string
cfg := r.Cfg()
certsDir := r.CertificatesDir()
client := r.Client()
err := r.preflight()
if err != nil {
return err
}
// Reset the ClusterStatus for a given control-plane node. // Reset the ClusterStatus for a given control-plane node.
if isControlPlane() && cfg != nil { if isControlPlane() && cfg != nil {
@ -203,10 +303,10 @@ func (r *Reset) Run(out io.Writer, client clientset.Interface, cfg *kubeadmapi.I
// Remove contents from the config and pki directories // Remove contents from the config and pki directories
klog.V(1).Infoln("[reset] Removing contents from the config and pki directories") klog.V(1).Infoln("[reset] Removing contents from the config and pki directories")
if r.certsDir != kubeadmapiv1beta2.DefaultCertificatesDir { if certsDir != kubeadmapiv1beta2.DefaultCertificatesDir {
klog.Warningf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", r.certsDir) klog.Warningf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", certsDir)
} }
resetConfigDir(kubeadmconstants.KubernetesDir, r.certsDir) resetConfigDir(kubeadmconstants.KubernetesDir, certsDir)
// Output help text instructing user how to remove iptables rules // Output help text instructing user how to remove iptables rules
msg := dedent.Dedent(` msg := dedent.Dedent(`

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd package cmd
import ( import (
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -26,8 +25,6 @@ import (
"github.com/lithammer/dedent" "github.com/lithammer/dedent"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight" "k8s.io/kubernetes/cmd/kubeadm/app/preflight"
testutil "k8s.io/kubernetes/cmd/kubeadm/test" testutil "k8s.io/kubernetes/cmd/kubeadm/test"
@ -85,21 +82,6 @@ func assertDirEmpty(t *testing.T, path string) {
} }
} }
func TestNewReset(t *testing.T) {
var in io.Reader
certsDir := kubeadmapiv1beta2.DefaultCertificatesDir
criSocketPath := kubeadmconstants.DefaultDockerCRISocket
forceReset := true
ignorePreflightErrors := []string{"all"}
ignorePreflightErrorsSet, _ := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
ignorePreflightErrors = []string{}
ignorePreflightErrorsSet, _ = validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
}
func TestConfigDirCleaner(t *testing.T) { func TestConfigDirCleaner(t *testing.T) {
tests := map[string]struct { tests := map[string]struct {
resetDir string resetDir string