From b673e2d0a0b129e1b413fa11e0c55bd85d0c8fde Mon Sep 17 00:00:00 2001 From: Devan Goodwin Date: Wed, 5 Oct 2016 16:25:16 -0300 Subject: [PATCH 1/3] Add kubeadm preflight check framework. Includes checks for verifying services exist and are enabled, ports are open, directories do not exist or are empty, and required binaries are in the path. --- cmd/kubeadm/app/checks/checks.go | 191 +++++++++++++++++++++++++++ cmd/kubeadm/app/checks/initsystem.go | 83 ++++++++++++ cmd/kubeadm/app/cmd/init.go | 18 ++- cmd/kubeadm/app/cmd/join.go | 19 ++- 4 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 cmd/kubeadm/app/checks/checks.go create mode 100644 cmd/kubeadm/app/checks/initsystem.go diff --git a/cmd/kubeadm/app/checks/checks.go b/cmd/kubeadm/app/checks/checks.go new file mode 100644 index 00000000000..1b5a39e16db --- /dev/null +++ b/cmd/kubeadm/app/checks/checks.go @@ -0,0 +1,191 @@ +/* +Copyright 2016 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 checks + +import ( + "fmt" + "io" + "net" + "os" + "os/exec" +) + +// 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) +} + +// ServiceCheck verifies that the given service is enabled and active. If we do not +// detect a supported init system however, all checks are skipped and a warning is +// returned. +type ServiceCheck struct { + service string +} + +func (sc ServiceCheck) Check() (warnings []string, errors []string) { + + initSystem := getInitSystem() + if initSystem == nil { + return []string{"no supported init system detected, skipping service checks"}, nil + } + + warnings = []string{} + + if !initSystem.ServiceExists(sc.service) { + warnings = append(warnings, fmt.Sprintf("%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'", + 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'", + sc.service, sc.service)) + } + + return warnings, nil +} + +// PortOpenCheck ensures the given port is available for use. +type PortOpenCheck struct { + port int +} + +func (poc PortOpenCheck) Check() (warnings []string, errors []string) { + errors = []string{} + // 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)) + } + if ln != nil { + ln.Close() + } + + 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{} + // If it doesn't exist we are good: + if _, err := os.Stat(dac.path); os.IsNotExist(err) { + return nil, nil + } + + 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)) + 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)) + } + + return nil, errors +} + +// InPathChecks checks if the given executable is present in the path. +type InPathCheck struct { + executable string + mandatory bool +} + +func (ipc InPathCheck) Check() (warnings []string, errors []string) { + _, 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 as a warning: + return []string{fmt.Sprintf("%s not found in system path.", ipc.executable)}, nil + } + return nil, nil +} + +func RunInitMasterChecks() { + // TODO: Some of these ports should come from kubeadm config eventually: + checks := []PreFlightCheck{ + ServiceCheck{service: "kubelet"}, + ServiceCheck{service: "docker"}, + PortOpenCheck{port: 443}, + PortOpenCheck{port: 2379}, + PortOpenCheck{port: 8080}, + PortOpenCheck{port: 10250}, + PortOpenCheck{port: 10251}, + PortOpenCheck{port: 10252}, + DirAvailableCheck{path: "/etc/kubernetes"}, + DirAvailableCheck{path: "/var/lib/etcd"}, + DirAvailableCheck{path: "/var/lib/kubelet"}, + InPathCheck{executable: "socat", mandatory: true}, + InPathCheck{executable: "ethtool", mandatory: true}, + } + + runChecks(checks) +} + +func RunJoinNodeChecks() { + // TODO: Some of these ports should come from kubeadm config eventually: + checks := []PreFlightCheck{ + ServiceCheck{service: "kubelet"}, + ServiceCheck{service: "docker"}, + 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: "ethtool", mandatory: true}, + } + + 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) + } + for _, errMsg := range errors { + foundErrors = true + fmt.Printf("ERROR: %s\n", errMsg) + } + } + if foundErrors { + os.Exit(1) + } +} diff --git a/cmd/kubeadm/app/checks/initsystem.go b/cmd/kubeadm/app/checks/initsystem.go new file mode 100644 index 00000000000..06dd49ad4a2 --- /dev/null +++ b/cmd/kubeadm/app/checks/initsystem.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 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 checks + +import ( + "fmt" + "os/exec" + "strings" +) + +type InitSystem interface { + + // ServiceExists ensures the service is defined for this init system. + ServiceExists(service string) bool + + // ServiceIsEnabled ensures the service is enabled to start on each boot. + ServiceIsEnabled(service string) bool + + // ServiceIsActive ensures the service is running, or attempting to run. (crash looping in the case of kubelet) + ServiceIsActive(service string) bool +} + +type SystemdInitSystem struct{} + +func (sysd SystemdInitSystem) ServiceExists(service string) bool { + args := []string{"status", service} + outBytes, _ := exec.Command("systemctl", args...).Output() + output := string(outBytes) + if strings.Contains(output, "Loaded: not-found") { + return false + } + return true +} + +func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool { + args := []string{"is-enabled", service} + _, err := exec.Command("systemctl", args...).Output() + if err != nil { + fmt.Println(err) + return false + } + return true +} + +// ServiceIsActive will check is the service is "active". In the case of +// crash looping services (kubelet in our case) status will return as +// "activating", so we will consider this active as well. +func (sysd SystemdInitSystem) ServiceIsActive(service string) bool { + args := []string{"is-active", service} + // Ignoring error here, command returns non-0 if in "activating" status: + outBytes, _ := exec.Command("systemctl", args...).Output() + output := strings.TrimSpace(string(outBytes)) + if output == "active" || output == "activating" { + return true + } + return false +} + +// 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 { + // Assume existence of systemctl in path implies this is a systemd system: + _, err := exec.LookPath("systemctl") + if err == nil { + return &SystemdInitSystem{} + } + return nil +} diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index bd369b20d00..15946a6a145 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -25,6 +25,7 @@ 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" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/api" @@ -49,6 +50,7 @@ var ( func NewCmdInit(out io.Writer) *cobra.Command { cfg := &kubeadmapi.MasterConfiguration{} var cfgPath string + var skipChecks bool cmd := &cobra.Command{ Use: "init", Short: "Run this in order to set up the Kubernetes master.", @@ -58,7 +60,7 @@ func NewCmdInit(out io.Writer) *cobra.Command { cmdutil.CheckErr(fmt.Errorf(" %v", err)) } } - i, err := NewInit(cfgPath, cfg) + i, err := NewInit(cfgPath, cfg, skipChecks) check(err) check(i.Run(out)) }, @@ -124,6 +126,10 @@ func NewCmdInit(out io.Writer) *cobra.Command { "etcd client key file. Note: The path must be in /etc/ssl/certs", ) 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", + ) return cmd } @@ -132,7 +138,7 @@ type Init struct { cfg *kubeadmapi.MasterConfiguration } -func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration) (*Init, error) { +func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipChecks bool) (*Init, error) { if cfgPath != "" { b, err := ioutil.ReadFile(cfgPath) if err != nil { @@ -142,6 +148,14 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration) (*Init, error) return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err) } } + + if !skipChecks { + fmt.Println("Running pre-flight checks") + kubechecks.RunInitMasterChecks() + } else { + fmt.Println("Skipping pre-flight checks") + } + // Auto-detect the IP if len(cfg.API.AdvertiseAddresses) == 0 { // TODO(phase1+) perhaps we could actually grab eth0 and eth1 diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 99ff5a0c97a..a2b6508f40e 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -24,6 +24,7 @@ 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" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -43,11 +44,12 @@ var ( // NewCmdJoin returns "kubeadm join" command. func NewCmdJoin(out io.Writer) *cobra.Command { cfg := &kubeadmapi.NodeConfiguration{} + var skipChecks 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) + err := RunJoin(out, cmd, args, cfg, skipChecks) cmdutil.CheckErr(err) }, } @@ -57,11 +59,24 @@ func NewCmdJoin(out io.Writer) *cobra.Command { "(required) Shared secret used to secure bootstrap. Must match the output of 'kubeadm init'", ) + cmd.PersistentFlags().BoolVar( + &skipChecks, "skip-checks", false, + "skip 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) error { +func RunJoin(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.NodeConfiguration, skipChecks bool) error { + + if !skipChecks { + fmt.Println("Running pre-flight checks") + kubechecks.RunJoinNodeChecks() + } 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)") From 16b159c12be94dafc5216d6cf13e0df0bbaf43b0 Mon Sep 17 00:00:00 2001 From: Derek McQuay Date: Tue, 4 Oct 2016 13:45:24 -0700 Subject: [PATCH 2/3] kubeadm implement preflight checks Includes checks for verifying services exist and are enabled, ports are open, directories do not exist or are empty, and required binaries are in the path. Checks that user running kubeamd init and join is root and will only execute command if user is root. Moved away from using kubectl error handling to having kubeadm handle its own errors. This should allow kubeadm to have more meaningful errors, exit codes, and logging for specific kubeadm use cases. --- cmd/kubeadm/app/cmd/init.go | 19 +-- cmd/kubeadm/app/cmd/join.go | 25 ++-- cmd/kubeadm/app/kubeadm.go | 7 - .../app/{checks => preflight}/checks.go | 122 ++++++++++++------ cmd/kubeadm/app/util/error.go | 92 +++++++++++++ cmd/kubeadm/kubeadm.go | 4 +- .../util/initsystem}/initsystem.go | 4 +- 7 files changed, 205 insertions(+), 68 deletions(-) rename cmd/kubeadm/app/{checks => preflight}/checks.go (54%) create mode 100644 cmd/kubeadm/app/util/error.go rename {cmd/kubeadm/app/checks => pkg/util/initsystem}/initsystem.go (97%) 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 { From 4231c046dd6830ab5cc52b7d0c8e80e811daf2e4 Mon Sep 17 00:00:00 2001 From: Devan Goodwin Date: Thu, 13 Oct 2016 09:47:53 -0300 Subject: [PATCH 3/3] Fix errors and improve output in kubeadm pre-flight checks. Add skip-preflight-checks to known flags. Fix bug with preflight checks not returning system is-active as errors. Fix error handling to use correct function. --- cmd/kubeadm/app/cmd/init.go | 4 ++-- cmd/kubeadm/app/preflight/checks.go | 5 ++--- hack/verify-flags/known-flags.txt | 1 + pkg/util/initsystem/initsystem.go | 3 --- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 1cd47409055..4ac4298bb60 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -27,11 +27,11 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/cloudprovider" _ "k8s.io/kubernetes/pkg/cloudprovider/providers" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/runtime" netutil "k8s.io/kubernetes/pkg/util/net" ) @@ -150,7 +150,7 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight } if !skipPreFlight { - fmt.Println("Running pre-flight checks") + fmt.Println(" Running pre-flight checks") err := preflight.RunInitMasterChecks() if err != nil { return nil, err diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 73874de5f1b..748d8ecfb85 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -49,7 +49,6 @@ type ServiceCheck struct { } func (sc ServiceCheck) Check() (warnings, errors []error) { - initSystem := initsystem.GetInitSystem() if initSystem == nil { return []error{fmt.Errorf("no supported init system detected, skipping service checks for %s", sc.service)}, nil @@ -74,7 +73,7 @@ func (sc ServiceCheck) Check() (warnings, errors []error) { sc.service, sc.service)) } - return warnings, nil + return warnings, errors } // PortOpenCheck ensures the given port is available for use. @@ -219,7 +218,7 @@ func runChecks(checks []PreFlightCheck) error { for _, c := range checks { warnings, errors := c.Check() for _, w := range warnings { - fmt.Printf("WARNING: %s\n", w) + fmt.Printf(" WARNING: %s\n", w) } for _, e := range errors { found = append(found, e) diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index a4f1457368c..73cbf66633a 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -512,6 +512,7 @@ since-seconds since-time skip-generated-rewrite skip-munges +skip-preflight-checks sort-by source-file ssh-env diff --git a/pkg/util/initsystem/initsystem.go b/pkg/util/initsystem/initsystem.go index 1dedf7e44c6..43ea818eb9d 100644 --- a/pkg/util/initsystem/initsystem.go +++ b/pkg/util/initsystem/initsystem.go @@ -17,13 +17,11 @@ limitations under the License. package initsystem import ( - "fmt" "os/exec" "strings" ) type InitSystem interface { - // ServiceExists ensures the service is defined for this init system. ServiceExists(service string) bool @@ -50,7 +48,6 @@ func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool { args := []string{"is-enabled", service} _, err := exec.Command("systemctl", args...).Output() if err != nil { - fmt.Println(err) return false } return true