288 lines
9.6 KiB
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})
|
|
}
|
|
}
|