diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 15946a6a145..1cd47409055 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -25,8 +25,8 @@ import ( "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubechecks "k8s.io/kubernetes/cmd/kubeadm/app/checks" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" + "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/cloudprovider" @@ -50,7 +50,7 @@ var ( func NewCmdInit(out io.Writer) *cobra.Command { cfg := &kubeadmapi.MasterConfiguration{} var cfgPath string - var skipChecks bool + var skipPreFlight bool cmd := &cobra.Command{ Use: "init", Short: "Run this in order to set up the Kubernetes master.", @@ -60,7 +60,7 @@ func NewCmdInit(out io.Writer) *cobra.Command { cmdutil.CheckErr(fmt.Errorf(" %v", err)) } } - i, err := NewInit(cfgPath, cfg, skipChecks) + i, err := NewInit(cfgPath, cfg, skipPreFlight) check(err) check(i.Run(out)) }, @@ -127,8 +127,8 @@ func NewCmdInit(out io.Writer) *cobra.Command { ) cmd.PersistentFlags().MarkDeprecated("external-etcd-keyfile", "this flag will be removed when componentconfig exists") cmd.PersistentFlags().BoolVar( - &skipChecks, "skip-checks", false, - "skip checks normally run before modifying the system", + &skipPreFlight, "skip-preflight-checks", false, + "skip preflight checks normally run before modifying the system", ) return cmd @@ -138,7 +138,7 @@ type Init struct { cfg *kubeadmapi.MasterConfiguration } -func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipChecks bool) (*Init, error) { +func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight bool) (*Init, error) { if cfgPath != "" { b, err := ioutil.ReadFile(cfgPath) if err != nil { @@ -149,9 +149,12 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipChecks boo } } - if !skipChecks { + if !skipPreFlight { fmt.Println("Running pre-flight checks") - kubechecks.RunInitMasterChecks() + err := preflight.RunInitMasterChecks() + if err != nil { + return nil, err + } } else { fmt.Println("Skipping pre-flight checks") } diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index a2b6508f40e..adca8ed82d0 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -24,10 +24,9 @@ import ( "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubechecks "k8s.io/kubernetes/cmd/kubeadm/app/checks" kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node" + "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( @@ -44,13 +43,13 @@ var ( // NewCmdJoin returns "kubeadm join" command. func NewCmdJoin(out io.Writer) *cobra.Command { cfg := &kubeadmapi.NodeConfiguration{} - var skipChecks bool + var skipPreFlight bool cmd := &cobra.Command{ Use: "join", Short: "Run this on any machine you wish to join an existing cluster.", Run: func(cmd *cobra.Command, args []string) { - err := RunJoin(out, cmd, args, cfg, skipChecks) - cmdutil.CheckErr(err) + err := RunJoin(out, cmd, args, cfg, skipPreFlight) + kubeadmutil.CheckErr(err) }, } @@ -60,24 +59,26 @@ func NewCmdJoin(out io.Writer) *cobra.Command { ) cmd.PersistentFlags().BoolVar( - &skipChecks, "skip-checks", false, - "skip checks normally run before modifying the system", + &skipPreFlight, "skip-preflight-checks", false, + "skip preflight checks normally run before modifying the system", ) return cmd } // RunJoin executes worked node provisioning and tries to join an existing cluster. -func RunJoin(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.NodeConfiguration, skipChecks bool) error { - - if !skipChecks { +func RunJoin(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.NodeConfiguration, skipPreFlight bool) error { + // TODO(phase1+) this we are missing args from the help text, there should be a way to tell cobra about it + if !skipPreFlight { fmt.Println("Running pre-flight checks") - kubechecks.RunJoinNodeChecks() + err := preflight.RunJoinNodeChecks() + if err != nil { + return err + } } else { fmt.Println("Skipping pre-flight checks") } - // TODO(phase1+) this we are missing args from the help text, there should be a way to tell cobra about it if len(args) == 0 { return fmt.Errorf(" must specify master IP address (see --help)") } diff --git a/cmd/kubeadm/app/kubeadm.go b/cmd/kubeadm/app/kubeadm.go index e90bb775e11..f17ac226895 100644 --- a/cmd/kubeadm/app/kubeadm.go +++ b/cmd/kubeadm/app/kubeadm.go @@ -19,7 +19,6 @@ package app import ( "os" - "github.com/renstrom/dedent" "github.com/spf13/pflag" "k8s.io/kubernetes/cmd/kubeadm/app/cmd" @@ -27,12 +26,6 @@ import ( "k8s.io/kubernetes/pkg/util/logs" ) -var AlphaWarningOnExit = dedent.Dedent(` - kubeadm: I am an alpha version, my authors welcome your feedback and bug reports - kubeadm: please create an issue using https://github.com/kubernetes/kubernetes/issues/new - kubeadm: and make sure to mention @kubernetes/sig-cluster-lifecycle. Thank you! -`) - func Run() error { logs.InitLogs() defer logs.FlushLogs() diff --git a/cmd/kubeadm/app/checks/checks.go b/cmd/kubeadm/app/preflight/checks.go similarity index 54% rename from cmd/kubeadm/app/checks/checks.go rename to cmd/kubeadm/app/preflight/checks.go index 1b5a39e16db..73874de5f1b 100644 --- a/cmd/kubeadm/app/checks/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package checks +package preflight import ( "fmt" @@ -22,12 +22,23 @@ import ( "net" "os" "os/exec" + + "k8s.io/kubernetes/pkg/util/initsystem" ) +type PreFlightError struct { + Msg string + Count int +} + +func (e *PreFlightError) Error() string { + return fmt.Sprintf("preflight check error\n count: %d \n msg: %s", e.Count, e.Msg) +} + // PreFlightCheck validates the state of the system to ensure kubeadm will be // successful as often as possilble. type PreFlightCheck interface { - Check() (warnings []string, errors []string) + Check() (warnings, errors []error) } // ServiceCheck verifies that the given service is enabled and active. If we do not @@ -37,29 +48,29 @@ type ServiceCheck struct { service string } -func (sc ServiceCheck) Check() (warnings []string, errors []string) { +func (sc ServiceCheck) Check() (warnings, errors []error) { - initSystem := getInitSystem() + initSystem := initsystem.GetInitSystem() if initSystem == nil { - return []string{"no supported init system detected, skipping service checks"}, nil + return []error{fmt.Errorf("no supported init system detected, skipping service checks for %s", sc.service)}, nil } - warnings = []string{} + warnings = []error{} if !initSystem.ServiceExists(sc.service) { - warnings = append(warnings, fmt.Sprintf("%s service does not exist", sc.service)) + warnings = append(warnings, fmt.Errorf("%s service does not exist", sc.service)) return warnings, nil } if !initSystem.ServiceIsEnabled(sc.service) { warnings = append(warnings, - fmt.Sprintf("%s service is not enabled, please run 'systemctl enable %s.service'", + fmt.Errorf("%s service is not enabled, please run 'systemctl enable %s.service'", sc.service, sc.service)) } if !initSystem.ServiceIsActive(sc.service) { errors = append(errors, - fmt.Sprintf("%s service is not active, please run 'systemctl start %s.service'", + fmt.Errorf("%s service is not active, please run 'systemctl start %s.service'", sc.service, sc.service)) } @@ -71,12 +82,12 @@ type PortOpenCheck struct { port int } -func (poc PortOpenCheck) Check() (warnings []string, errors []string) { - errors = []string{} +func (poc PortOpenCheck) Check() (warnings, errors []error) { + errors = []error{} // TODO: Get IP from KubeadmConfig ln, err := net.Listen("tcp", fmt.Sprintf(":%d", poc.port)) if err != nil { - errors = append(errors, fmt.Sprintf("Port %d is in use.", poc.port)) + errors = append(errors, fmt.Errorf("Port %d is in use", poc.port)) } if ln != nil { ln.Close() @@ -85,14 +96,28 @@ func (poc PortOpenCheck) Check() (warnings []string, errors []string) { return nil, errors } +// IsRootCheck verifies user is root +type IsRootCheck struct { + root bool +} + +func (irc IsRootCheck) Check() (warnings, errors []error) { + errors = []error{} + if os.Getuid() != 0 { + errors = append(errors, fmt.Errorf("user is not running as root")) + } + + return nil, errors +} + // DirAvailableCheck checks if the given directory either does not exist, or // is empty. type DirAvailableCheck struct { path string } -func (dac DirAvailableCheck) Check() (warnings []string, errors []string) { - errors = []string{} +func (dac DirAvailableCheck) Check() (warnings, errors []error) { + errors = []error{} // If it doesn't exist we are good: if _, err := os.Stat(dac.path); os.IsNotExist(err) { return nil, nil @@ -100,14 +125,14 @@ func (dac DirAvailableCheck) Check() (warnings []string, errors []string) { f, err := os.Open(dac.path) if err != nil { - errors = append(errors, fmt.Sprintf("Unable to check if %s is empty: %s", dac.path, err)) + errors = append(errors, fmt.Errorf("unable to check if %s is empty: %s", dac.path, err)) return nil, errors } defer f.Close() _, err = f.Readdirnames(1) if err != io.EOF { - errors = append(errors, fmt.Sprintf("%s is not empty", dac.path)) + errors = append(errors, fmt.Errorf("%s is not empty", dac.path)) } return nil, errors @@ -119,22 +144,23 @@ type InPathCheck struct { mandatory bool } -func (ipc InPathCheck) Check() (warnings []string, errors []string) { +func (ipc InPathCheck) Check() (warnings, errors []error) { _, err := exec.LookPath(ipc.executable) if err != nil { if ipc.mandatory { // Return as an error: - return nil, []string{fmt.Sprintf("%s not found in system path.", ipc.executable)} + return nil, []error{fmt.Errorf("%s not found in system path", ipc.executable)} } // Return as a warning: - return []string{fmt.Sprintf("%s not found in system path.", ipc.executable)}, nil + return []error{fmt.Errorf("%s not found in system path", ipc.executable)}, nil } return nil, nil } -func RunInitMasterChecks() { +func RunInitMasterChecks() error { // TODO: Some of these ports should come from kubeadm config eventually: checks := []PreFlightCheck{ + IsRootCheck{root: true}, ServiceCheck{service: "kubelet"}, ServiceCheck{service: "docker"}, PortOpenCheck{port: 443}, @@ -146,46 +172,68 @@ func RunInitMasterChecks() { DirAvailableCheck{path: "/etc/kubernetes"}, DirAvailableCheck{path: "/var/lib/etcd"}, DirAvailableCheck{path: "/var/lib/kubelet"}, - InPathCheck{executable: "socat", mandatory: true}, + InPathCheck{executable: "ebtables", mandatory: true}, InPathCheck{executable: "ethtool", mandatory: true}, + InPathCheck{executable: "ip", mandatory: true}, + InPathCheck{executable: "iptables", mandatory: true}, + InPathCheck{executable: "mount", mandatory: true}, + InPathCheck{executable: "nsenter", mandatory: true}, + InPathCheck{executable: "socat", mandatory: true}, + InPathCheck{executable: "tc", mandatory: false}, + InPathCheck{executable: "touch", mandatory: false}, } - runChecks(checks) + return runChecks(checks) } -func RunJoinNodeChecks() { +func RunJoinNodeChecks() error { // TODO: Some of these ports should come from kubeadm config eventually: checks := []PreFlightCheck{ - ServiceCheck{service: "kubelet"}, + IsRootCheck{root: true}, ServiceCheck{service: "docker"}, + ServiceCheck{service: "kubelet"}, PortOpenCheck{port: 8080}, PortOpenCheck{port: 10250}, PortOpenCheck{port: 10251}, PortOpenCheck{port: 10252}, DirAvailableCheck{path: "/etc/kubernetes"}, DirAvailableCheck{path: "/var/lib/kubelet"}, - InPathCheck{executable: "socat", mandatory: true}, + InPathCheck{executable: "ebtables", mandatory: true}, InPathCheck{executable: "ethtool", mandatory: true}, + InPathCheck{executable: "ip", mandatory: true}, + InPathCheck{executable: "iptables", mandatory: true}, + InPathCheck{executable: "mount", mandatory: true}, + InPathCheck{executable: "nsenter", mandatory: true}, + InPathCheck{executable: "socat", mandatory: true}, + InPathCheck{executable: "tc", mandatory: false}, + InPathCheck{executable: "touch", mandatory: false}, } - runChecks(checks) + return runChecks(checks) } // runChecks runs each check, displays it's warnings/errors, and once all // are processed will exit if any errors occurred. -func runChecks(checks []PreFlightCheck) { - foundErrors := false - for _, check := range checks { - warnings, errors := check.Check() - for _, warnMsg := range warnings { - fmt.Printf("WARNING: %s\n", warnMsg) +func runChecks(checks []PreFlightCheck) error { + found := []error{} + for _, c := range checks { + warnings, errors := c.Check() + for _, w := range warnings { + fmt.Printf("WARNING: %s\n", w) } - for _, errMsg := range errors { - foundErrors = true - fmt.Printf("ERROR: %s\n", errMsg) + for _, e := range errors { + found = append(found, e) } } - if foundErrors { - os.Exit(1) + if len(found) > 0 { + errors := "\n" + for _, i := range found { + errors += "\t" + i.Error() + "\n" + } + return &PreFlightError{ + Msg: errors, + Count: len(found), + } } + return nil } diff --git a/cmd/kubeadm/app/util/error.go b/cmd/kubeadm/app/util/error.go new file mode 100644 index 00000000000..d8f7c6ec95b --- /dev/null +++ b/cmd/kubeadm/app/util/error.go @@ -0,0 +1,92 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "os" + "strings" + + "k8s.io/kubernetes/cmd/kubeadm/app/preflight" + + "github.com/golang/glog" + "github.com/renstrom/dedent" +) + +const ( + DefaultErrorExitCode = 1 + PreFlight = 2 +) + +var AlphaWarningOnExit = dedent.Dedent(` + kubeadm: I am an alpha version, my authors welcome your feedback and bug reports + kubeadm: please create issue an using https://github.com/kubernetes/kubernetes/issues/new + kubeadm: and make sure to mention @kubernetes/sig-cluster-lifecycle. Thank you! +`) + +type debugError interface { + DebugError() (msg string, args []interface{}) +} + +var fatalErrHandler = fatal + +// BehaviorOnFatal allows you to override the default behavior when a fatal +// error occurs, which is to call os.Exit(code). You can pass 'panic' as a function +// here if you prefer the panic() over os.Exit(1). +func BehaviorOnFatal(f func(string, int)) { + fatalErrHandler = f +} + +// fatal prints the message if set and then exits. If V(2) or greater, glog.Fatal +// is invoked for extended information. +func fatal(msg string, code int) { + if len(msg) > 0 { + // add newline if needed + if !strings.HasSuffix(msg, "\n") { + msg += "\n" + } + + if glog.V(2) { + glog.FatalDepth(2, msg) + } + fmt.Fprint(os.Stderr, msg) + } + os.Exit(code) +} + +// CheckErr prints a user friendly error to STDERR and exits with a non-zero +// exit code. Unrecognized errors will be printed with an "error: " prefix. +// +// This method is generic to the command in use and may be used by non-Kubectl +// commands. +func CheckErr(err error) { + checkErr("", err, fatalErrHandler) +} + +// checkErr formats a given error as a string and calls the passed handleErr +// func with that string and an kubectl exit code. +func checkErr(prefix string, err error, handleErr func(string, int)) { + switch err.(type) { + case nil: + return + case *preflight.PreFlightError: + handleErr(err.Error(), PreFlight) + default: + fmt.Printf(AlphaWarningOnExit) + handleErr(err.Error(), DefaultErrorExitCode) + } +} diff --git a/cmd/kubeadm/kubeadm.go b/cmd/kubeadm/kubeadm.go index 902e46127da..6c743cd7a21 100644 --- a/cmd/kubeadm/kubeadm.go +++ b/cmd/kubeadm/kubeadm.go @@ -21,12 +21,12 @@ import ( "os" "k8s.io/kubernetes/cmd/kubeadm/app" + "k8s.io/kubernetes/cmd/kubeadm/app/util" ) -// TODO(phase1+): check for root func main() { if err := app.Run(); err != nil { - fmt.Printf(app.AlphaWarningOnExit) + fmt.Printf(util.AlphaWarningOnExit) os.Exit(1) } os.Exit(0) diff --git a/cmd/kubeadm/app/checks/initsystem.go b/pkg/util/initsystem/initsystem.go similarity index 97% rename from cmd/kubeadm/app/checks/initsystem.go rename to pkg/util/initsystem/initsystem.go index 06dd49ad4a2..1dedf7e44c6 100644 --- a/cmd/kubeadm/app/checks/initsystem.go +++ b/pkg/util/initsystem/initsystem.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package checks +package initsystem import ( "fmt" @@ -73,7 +73,7 @@ func (sysd SystemdInitSystem) ServiceIsActive(service string) bool { // getInitSystem returns an InitSystem for the current system, or nil // if we cannot detect a supported init system for pre-flight checks. // This indicates we will skip init system checks, not an error. -func getInitSystem() InitSystem { +func GetInitSystem() InitSystem { // Assume existence of systemctl in path implies this is a systemd system: _, err := exec.LookPath("systemctl") if err == nil {