Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add solution catalog to help users who run into known problems #3931

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,14 +525,14 @@ func bootstrapCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner b
if preexisting {
console.OutStyle("restarting", "Relaunching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
if err := bs.RestartCluster(kc); err != nil {
exit.WithProblems("Error restarting cluster", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("Error restarting cluster", err, logs.FindProblems(r, bs, runner))
}
return
}

console.OutStyle("launch", "Launching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
if err := bs.StartCluster(kc); err != nil {
exit.WithProblems("Error starting cluster", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("Error starting cluster", err, logs.FindProblems(r, bs, runner))
}
}

Expand All @@ -549,7 +549,7 @@ func validateCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner bo
}
err := pkgutil.RetryAfter(20, k8sStat, 3*time.Second)
if err != nil {
exit.WithProblems("kubelet checks failed", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("kubelet checks failed", err, logs.FindProblems(r, bs, runner))
}
aStat := func() (err error) {
st, err := bs.GetAPIServerStatus(net.ParseIP(ip))
Expand All @@ -562,7 +562,7 @@ func validateCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner bo

err = pkgutil.RetryAfter(30, aStat, 10*time.Second)
if err != nil {
exit.WithProblems("apiserver checks failed", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("apiserver checks failed", err, logs.FindProblems(r, bs, runner))
}
console.OutLn("")
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/minikube/console/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ var styles = map[string]style{
"log-entry": {Prefix: " "}, // Indent
"crushed": {Prefix: "💔 "},
"url": {Prefix: "👉 "},
"documentation": {Prefix: "🗎 "},
"issues": {Prefix: "📚 "},
"issue": {Prefix: " ▪ "}, // Indented bullet

// Specialized purpose styles
"iso-download": {Prefix: "💿 ", LowPrefix: "@ "},
Expand Down
49 changes: 26 additions & 23 deletions pkg/minikube/exit/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ package exit
import (
"fmt"
"os"
"strings"

"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/console"
"k8s.io/minikube/pkg/minikube/problem"
)

// Exit codes based on sysexits(3)
Expand All @@ -40,8 +39,8 @@ const (
Config = 78 // Config represents an unconfigured or misconfigured state
Permissions = 77 // Permissions represents a permissions error

// MaxProblems controls the number of problems to show for each source
MaxProblems = 3
// MaxLogEntries controls the number of log entries to show for each source
MaxLogEntries = 3
)

// Usage outputs a usage error and exits with error code 64
Expand All @@ -60,21 +59,33 @@ func WithCode(code int, format string, a ...interface{}) {

// WithError outputs an error and exits.
func WithError(msg string, err error) {
p := problem.FromError(err)
if p != nil {
WithProblem(msg, p)
}
displayError(msg, err)
// Here is where we would insert code to optionally upload a stack trace.

// We can be smarter about guessing exit codes, but EX_SOFTWARE should suffice.
os.Exit(Software)
}

// WithProblems outputs an error along with any autodetected problems, and exits.
func WithProblems(msg string, err error, problems map[string][]string) {
// WithProblem outputs info related to a known problem and exits.
func WithProblem(msg string, p *problem.Problem) {
console.Err("\n")
console.Fatal(msg)
p.Display()
console.Err("\n")
console.ErrStyle("sad", "If the advice does not help, please let us know: ")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
os.Exit(Config)
}

// WithLogEntries outputs an error along with any important log entries, and exits.
func WithLogEntries(msg string, err error, entries map[string][]string) {
displayError(msg, err)

for name, lines := range problems {
for name, lines := range entries {
console.OutStyle("failure", "Problems detected in %q:", name)
if len(lines) > MaxProblems {
lines = lines[:MaxProblems]
if len(lines) > MaxLogEntries {
lines = lines[:MaxLogEntries]
}
for _, l := range lines {
console.OutStyle("log-entry", l)
Expand All @@ -86,17 +97,9 @@ func WithProblems(msg string, err error, problems map[string][]string) {
func displayError(msg string, err error) {
// use Warning because Error will display a duplicate message to stderr
glog.Warningf(fmt.Sprintf("%s: %v", msg, err))
console.Err("\n")
console.Fatal(msg+": %v", err)
console.Err("\n")
// unfortunately Cause only supports one level of actual error wrapping
cause := errors.Cause(err)
text := cause.Error()
if strings.Contains(text, "VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path") ||
strings.Contains(text, "Driver \"kvm2\" not found. Do you have the plugin binary \"docker-machine-driver-kvm2\" accessible in your PATH?") {
console.ErrStyle("usage", "Make sure to install all necessary requirements, according to the documentation:")
console.ErrStyle("url", "https://kubernetes.io/docs/tasks/tools/install-minikube/")
} else {
console.ErrStyle("sad", "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
}
console.ErrStyle("sad", "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
}
143 changes: 143 additions & 0 deletions pkg/minikube/problem/err_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 problem

import "regexp"

// re is a shortcut around regexp.MustCompile
func re(s string) *regexp.Regexp {
return regexp.MustCompile(s)
}

// vmProblems are VM related problems
var vmProblems = map[string]match{
"VBOX_NOT_FOUND": {
Regexp: re(`VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path`),
Advice: "Install VirtualBox, ensure that VBoxManage is executable and in path, or select an alternative value for --vm-driver",
URL: "https://www.virtualbox.org/wiki/Downloads",
Issues: []int{3784, 3776},
},
"VBOX_VTX_DISABLED": {
Regexp: re(`This computer doesn't have VT-X/AMD-v enabled`),
Advice: "In some environments, this message is incorrect. Try 'minikube start --no-vtx-check'",
Issues: []int{3900},
},
"KVM2_NOT_FOUND": {
Regexp: re(`Driver "kvm2" not found. Do you have the plugin binary .* accessible in your PATH`),
Advice: "Please install the minikube kvm2 VM driver, or select an alternative --vm-driver",
URL: "https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver",
},
"KVM2_NO_IP": {
Regexp: re(`Error starting stopped host: Machine didn't return an IP after 120 seconds`),
Advice: "The KVM driver is unable to ressurect this old VM. Please run `minikube delete` to delete it and try again.",
Issues: []int{3901, 3566, 3434},
},
"VM_DOES_NOT_EXIST": {
Regexp: re(`Error getting state for host: machine does not exist`),
Advice: "Your system no longer knows about the VM previously created by minikube. Run 'minikube delete' to reset your local state.",
Issues: []int{3864},
},
"VM_IP_NOT_FOUND": {
Regexp: re(`Error getting ssh host name for driver: IP not found`),
Advice: "The minikube VM is offline. Please run 'minikube start' to start it again.",
Issues: []int{3849, 3648},
},
}

// proxyDoc is the URL to proxy documentation
const proxyDoc = "https://github.com/kubernetes/minikube/blob/master/docs/http_proxy.md"

// netProblems are network related problems.
var netProblems = map[string]match{
"GCR_UNAVAILABLE": {
Regexp: re(`gcr.io.*443: connect: invalid argument`),
Advice: "minikube is unable to access the Google Container Registry. You may need to configure it to use a HTTP proxy.",
URL: proxyDoc,
Issues: []int{3860},
},
"DOWNLOAD_RESET_BY_PEER": {
Regexp: re(`Error downloading .*connection reset by peer`),
Advice: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3909},
},
"DOWNLOAD_IO_TIMEOUT": {
Regexp: re(`Error downloading .*timeout`),
Advice: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3846},
},
"DOWNLOAD_TLS_OVERSIZED": {
Regexp: re(`failed to download.*tls: oversized record received with length`),
Advice: "A firewall is interfering with minikube's ability to make outgoing HTTPS requests. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3857, 3759},
},
"ISO_DOWNLOAD_FAILED": {
Regexp: re(`iso: failed to download`),
Advice: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3922},
},
"PULL_TIMEOUT_EXCEEDED": {
Regexp: re(`failed to pull image k8s.gcr.io.*Client.Timeout exceeded while awaiting headers`),
Advice: "A firewall is blocking Docker within the minikube VM from reaching the internet. You may need to configure it to use a proxy.",
URL: proxyDoc,
Issues: []int{3898},
},
"SSH_AUTH_FAILURE": {
Regexp: re(`ssh: handshake failed: ssh: unable to authenticate.*, no supported methods remain`),
Advice: "Your host is failing to route packets to the minikube VM. If you have VPN software, try turning it off or configuring it so that it does not re-route traffic to the VM IP. If not, check your VM environment routing options.",
Issues: []int{3930},
},
"SSH_TCP_FAILURE": {
Regexp: re(`dial tcp .*:22: connectex: A connection attempt failed because the connected party did not properly respond`),
Advice: "Your host is failing to route packets to the minikube VM. If you have VPN software, try turning it off or configuring it so that it does not re-route traffic to the VM IP. If not, check your VM environment routing options.",
Issues: []int{3388},
},
"INVALID_PROXY_HOSTNAME": {
Regexp: re(`dial tcp: lookup.*: no such host`),
Advice: "Verify that your HTTP_PROXY and HTTPS_PROXY environment variables are set correctly.",
URL: proxyDoc,
},
}

// deployProblems are Kubernetes deployment problems.
var deployProblems = map[string]match{
"DOCKER_UNAVAILABLE": {
Regexp: re(`Error configuring auth on host: OS type not recognized`),
Advice: "Docker inside the VM is unavailable. Try running 'minikube delete' to reset the VM.",
Issues: []int{3952},
},
"INVALID_KUBERNETES_VERSION": {
Regexp: re(`No Major.Minor.Patch elements found`),
Advice: "Specify --kubernetes-version in v<major>.<minor.<build> form. example: 'v1.1.14'",
},
"KUBERNETES_VERSION_MISSING_V": {
Regexp: re(`strconv.ParseUint: parsing "": invalid syntax`),
Advice: "Check that your --kubernetes-version has a leading 'v'. For example: 'v1.1.14'",
},
}

// osProblems are operating-system specific issues
var osProblems = map[string]match{
"NON_C_DRIVE": {
Regexp: re(`.iso: The system cannot find the path specified.`),
Advice: "Run minikube from the C: drive.",
Issues: []int{1574},
},
}
87 changes: 87 additions & 0 deletions pkg/minikube/problem/problem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 problem helps deliver actionable feedback to a user based on an error message.
package problem

import (
"regexp"

"k8s.io/minikube/pkg/minikube/console"
)

const issueBase = "https://github.com/kubernetes/minikube/issue"

// Problem represents a known problem in minikube.
type Problem struct {
ID string
Err error
Advice string
URL string
Issues []int
}

// match maps a regular expression to problem metadata.
type match struct {
Regexp *regexp.Regexp
Advice string
URL string
Issues []int
}

// Display problem metadata to the console
func (p *Problem) Display() {
console.ErrStyle("failure", "Error: [%s] %v", p.ID, p.Err)
console.ErrStyle("tip", "Advice: %s", p.Advice)
if p.URL != "" {
console.ErrStyle("documentation", "Documentation: %s", p.URL)
}
if len(p.Issues) == 0 {
return
}
console.ErrStyle("issues", "Related issues:")
issues := p.Issues
if len(issues) > 3 {
issues = issues[0:3]
}
for _, i := range issues {
console.ErrStyle("issue", "%s/%d", issueBase, i)
}
}

// FromError returns a known problem from an error.
func FromError(err error) *Problem {
maps := []map[string]match{
osProblems,
vmProblems,
netProblems,
deployProblems,
}
for _, m := range maps {
for k, v := range m {
if v.Regexp.MatchString(err.Error()) {
return &Problem{
Err: err,
Advice: v.Advice,
URL: v.URL,
ID: k,
Issues: v.Issues,
}
}
}
}
return nil
}