minikube/pkg/minikube/cluster/setup.go

288 lines
9.6 KiB
Go

/*
Copyright 2020 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 cluster
import (
"fmt"
"net"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host"
"github.com/golang/glog"
"github.com/spf13/viper"
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
"k8s.io/minikube/pkg/addons"
"k8s.io/minikube/pkg/minikube/bootstrapper"
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/kubeconfig"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/proxy"
"k8s.io/minikube/pkg/util/retry"
)
const (
waitTimeout = "wait-timeout"
waitUntilHealthy = "wait"
embedCerts = "embed-certs"
keepContext = "keep-context"
imageRepository = "image-repository"
containerRuntime = "container-runtime"
)
// InitialSetup performs all necessary operations on the initial control plane node when first spinning up a cluster
func InitialSetup(cc config.ClusterConfig, n config.Node, existingAddons map[string]bool) (*kubeconfig.Settings, error) {
_, preExists, machineAPI, host := StartMachine(&cc, &n)
defer machineAPI.Close()
// Must be written before bootstrap, otherwise health checks may flake due to stale IP
kubeconfig, err := setupKubeconfig(host, &cc, &n, cc.Name)
if err != nil {
exit.WithError("Failed to setup kubeconfig", err)
}
// setup kubeadm (must come after setupKubeconfig)
bs := setupKubeAdm(machineAPI, cc, n)
// pull images or restart cluster
out.T(out.Launch, "Launching Kubernetes ... ")
err = bs.StartCluster(cc)
if err != nil {
/*config := cruntime.Config{Type: viper.GetString(containerRuntime), Runner: mRunner, ImageRepository: cc.KubernetesConfig.ImageRepository, KubernetesVersion: cc.KubernetesConfig.KubernetesVersion}
cr, err := cruntime.New(config)
exit.WithLogEntries("Error starting cluster", err, logs.FindProblems(cr, bs, mRunner))*/
exit.WithError("Error starting cluster", err)
}
// enable addons, both old and new!
if existingAddons != nil {
addons.Start(viper.GetString(config.MachineProfile), existingAddons, config.AddonList)
}
// Skip pre-existing, because we already waited for health
if viper.GetBool(waitUntilHealthy) && !preExists {
if err := bs.WaitForCluster(cc, viper.GetDuration(waitTimeout)); err != nil {
exit.WithError("Wait failed", err)
}
}
return kubeconfig, nil
}
// setupKubeAdm adds any requested files into the VM before Kubernetes is started
func setupKubeAdm(mAPI libmachine.API, cfg config.ClusterConfig, n config.Node) bootstrapper.Bootstrapper {
bs, err := Bootstrapper(mAPI, viper.GetString(cmdcfg.Bootstrapper), n.Name)
if err != nil {
exit.WithError("Failed to get bootstrapper", err)
}
for _, eo := range config.ExtraOptions {
out.T(out.Option, "{{.extra_option_component_name}}.{{.key}}={{.value}}", out.V{"extra_option_component_name": eo.Component, "key": eo.Key, "value": eo.Value})
}
// Loads cached images, generates config files, download binaries
if err := bs.UpdateCluster(cfg); err != nil {
exit.WithError("Failed to update cluster", err)
}
if err := bs.SetupCerts(cfg.KubernetesConfig, n); err != nil {
exit.WithError("Failed to setup certs", err)
}
return bs
}
func setupKubeconfig(h *host.Host, c *config.ClusterConfig, n *config.Node, clusterName string) (*kubeconfig.Settings, error) {
addr, err := h.Driver.GetURL()
if err != nil {
exit.WithError("Failed to get driver URL", err)
}
if !driver.IsKIC(h.DriverName) {
addr = strings.Replace(addr, "tcp://", "https://", -1)
addr = strings.Replace(addr, ":2376", ":"+strconv.Itoa(n.Port), -1)
}
if c.KubernetesConfig.APIServerName != constants.APIServerName {
addr = strings.Replace(addr, n.IP, c.KubernetesConfig.APIServerName, -1)
}
kcs := &kubeconfig.Settings{
ClusterName: clusterName,
ClusterServerAddress: addr,
ClientCertificate: localpath.MakeMiniPath("client.crt"),
ClientKey: localpath.MakeMiniPath("client.key"),
CertificateAuthority: localpath.MakeMiniPath("ca.crt"),
KeepContext: viper.GetBool(keepContext),
EmbedCerts: viper.GetBool(embedCerts),
}
kcs.SetPath(kubeconfig.PathFromEnv())
if err := kubeconfig.Update(kcs); err != nil {
return kcs, err
}
return kcs, nil
}
// StartMachine starts a VM
func StartMachine(cfg *config.ClusterConfig, node *config.Node) (runner command.Runner, preExists bool, machineAPI libmachine.API, host *host.Host) {
m, err := machine.NewAPIClient()
if err != nil {
exit.WithError("Failed to get machine client", err)
}
host, preExists = startHost(m, *cfg, *node)
runner, err = machine.CommandRunner(host)
if err != nil {
exit.WithError("Failed to get command runner", err)
}
ip := validateNetwork(host, runner)
// Bypass proxy for minikube's vm host ip
err = proxy.ExcludeIP(ip)
if err != nil {
out.ErrT(out.FailureType, "Failed to set NO_PROXY Env. Please use `export NO_PROXY=$NO_PROXY,{{.ip}}`.", out.V{"ip": ip})
}
node.IP = ip
config.SaveNodeToProfile(cfg, node)
return runner, preExists, m, host
}
// startHost starts a new minikube host using a VM or None
func startHost(api libmachine.API, mc config.ClusterConfig, n config.Node) (*host.Host, bool) {
exists, err := api.Exists(n.Name)
if err != nil {
exit.WithError("Failed to check if machine exists", err)
}
host, err := machine.StartHost(api, mc, n)
if err != nil {
exit.WithError("Unable to start VM. Please investigate and run 'minikube delete' if possible", err)
}
return host, exists
}
// validateNetwork tries to catch network problems as soon as possible
func validateNetwork(h *host.Host, r command.Runner) string {
ip, err := h.Driver.GetIP()
if err != nil {
exit.WithError("Unable to get VM IP address", err)
}
optSeen := false
warnedOnce := false
for _, k := range proxy.EnvVars {
if v := os.Getenv(k); v != "" {
if !optSeen {
out.T(out.Internet, "Found network options:")
optSeen = true
}
out.T(out.Option, "{{.key}}={{.value}}", out.V{"key": k, "value": v})
ipExcluded := proxy.IsIPExcluded(ip) // Skip warning if minikube ip is already in NO_PROXY
k = strings.ToUpper(k) // for http_proxy & https_proxy
if (k == "HTTP_PROXY" || k == "HTTPS_PROXY") && !ipExcluded && !warnedOnce {
out.WarningT("You appear to be using a proxy, but your NO_PROXY environment does not include the minikube IP ({{.ip_address}}). Please see {{.documentation_url}} for more details", out.V{"ip_address": ip, "documentation_url": "https://minikube.sigs.k8s.io/docs/reference/networking/proxy/"})
warnedOnce = true
}
}
}
if !driver.BareMetal(h.Driver.DriverName()) && !driver.IsKIC(h.Driver.DriverName()) {
trySSH(h, ip)
}
tryLookup(r)
tryRegistry(r)
return ip
}
func trySSH(h *host.Host, ip string) {
if viper.GetBool("force") {
return
}
sshAddr := net.JoinHostPort(ip, "22")
dial := func() (err error) {
d := net.Dialer{Timeout: 3 * time.Second}
conn, err := d.Dial("tcp", sshAddr)
if err != nil {
out.WarningT("Unable to verify SSH connectivity: {{.error}}. Will retry...", out.V{"error": err})
return err
}
_ = conn.Close()
return nil
}
if err := retry.Expo(dial, time.Second, 13*time.Second); err != nil {
exit.WithCodeT(exit.IO, `minikube is unable to connect to the VM: {{.error}}
This is likely due to one of two reasons:
- VPN or firewall interference
- {{.hypervisor}} network configuration issue
Suggested workarounds:
- Disable your local VPN or firewall software
- Configure your local VPN or firewall to allow access to {{.ip}}
- Restart or reinstall {{.hypervisor}}
- Use an alternative --vm-driver
- Use --force to override this connectivity check
`, out.V{"error": err, "hypervisor": h.Driver.DriverName(), "ip": ip})
}
}
func tryLookup(r command.Runner) {
// DNS check
if rr, err := r.RunCmd(exec.Command("nslookup", "kubernetes.io", "-type=ns")); err != nil {
glog.Infof("%s failed: %v which might be okay will retry nslookup without query type", rr.Args, err)
// will try with without query type for ISOs with different busybox versions.
if _, err = r.RunCmd(exec.Command("nslookup", "kubernetes.io")); err != nil {
glog.Warningf("nslookup failed: %v", err)
out.WarningT("Node may be unable to resolve external DNS records")
}
}
}
func tryRegistry(r command.Runner) {
// Try an HTTPS connection to the image repository
proxy := os.Getenv("HTTPS_PROXY")
opts := []string{"-sS"}
if proxy != "" && !strings.HasPrefix(proxy, "localhost") && !strings.HasPrefix(proxy, "127.0") {
opts = append([]string{"-x", proxy}, opts...)
}
repo := viper.GetString(imageRepository)
if repo == "" {
repo = images.DefaultKubernetesRepo
}
opts = append(opts, fmt.Sprintf("https://%s/", repo))
if rr, err := r.RunCmd(exec.Command("curl", opts...)); err != nil {
glog.Warningf("%s failed: %v", rr.Args, err)
out.WarningT("VM is unable to access {{.repository}}, you may need to configure a proxy or set --image-repository", out.V{"repository": repo})
}
}