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

Automatically scale the default memory allocation #6900

Merged
merged 4 commits into from
Mar 5, 2020
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
88 changes: 65 additions & 23 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ import (
"github.com/pkg/errors"
"github.com/shirou/gopsutil/cpu"
gopshost "github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
"github.com/spf13/cobra"
"github.com/spf13/viper"
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
"k8s.io/minikube/pkg/drivers/kic/oci"
"k8s.io/minikube/pkg/minikube/bootstrapper/bsutil"
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/config"
Expand Down Expand Up @@ -83,7 +85,6 @@ const (
kvmGPU = "kvm-gpu"
kvmHidden = "kvm-hidden"
minikubeEnvPrefix = "MINIKUBE"
defaultMemorySize = "2000mb"
installAddons = "install-addons"
defaultDiskSize = "20000mb"
keepContext = "keep-context"
Expand Down Expand Up @@ -112,7 +113,8 @@ const (
interactive = "interactive"
waitTimeout = "wait-timeout"
nativeSSH = "native-ssh"
minimumMemorySize = "1024mb"
minUsableMem = 1024 // Kubernetes will not start with less than 1GB
minRecommendedMem = 2000 // Warn at no lower than existing configurations
minimumCPUS = 2
minimumDiskSize = "2000mb"
autoUpdate = "auto-update-drivers"
Expand Down Expand Up @@ -149,8 +151,8 @@ func initMinikubeFlags() {
startCmd.Flags().Bool(interactive, true, "Allow user prompts for more information")
startCmd.Flags().Bool(dryRun, false, "dry-run mode. Validates configuration, but does not mutate system state")

startCmd.Flags().Int(cpus, 2, "Number of CPUs allocated to the minikube VM.")
startCmd.Flags().String(memory, defaultMemorySize, "Amount of RAM allocated to the minikube VM (format: <number>[<unit>], where unit = b, k, m or g).")
startCmd.Flags().Int(cpus, 2, "Number of CPUs allocated to Kubernetes.")
startCmd.Flags().String(memory, "", "Amount of RAM to allocate to Kubernetes (format: <number>[<unit>], where unit = b, k, m or g).")
startCmd.Flags().String(humanReadableDiskSize, defaultDiskSize, "Disk size allocated to the minikube VM (format: <number>[<unit>], where unit = b, k, m or g).")
startCmd.Flags().Bool(downloadOnly, false, "If true, only download and cache files for later use - don't install or start anything.")
startCmd.Flags().Bool(cacheImages, true, "If true, cache docker images for the current bootstrapper and load them into the machine. Always false with --driver=none.")
Expand Down Expand Up @@ -636,23 +638,50 @@ func validateUser(drvName string) {
}
}

// validateDiskSize validates the disk size matches the minimum recommended
func validateDiskSize() {
diskSizeMB := pkgutil.CalculateSizeInMB(viper.GetString(humanReadableDiskSize))
if diskSizeMB < pkgutil.CalculateSizeInMB(minimumDiskSize) && !viper.GetBool(force) {
exit.WithCodeT(exit.Config, "Requested disk size {{.requested_size}} is less than minimum of {{.minimum_size}}", out.V{"requested_size": diskSizeMB, "minimum_size": pkgutil.CalculateSizeInMB(minimumDiskSize)})
// defaultMemorySize calculates the default memory footprint in MB
func defaultMemorySize(drvName string) int {
fallback := 2200
maximum := 6000

v, err := mem.VirtualMemory()
if err != nil {
return fallback
}
available := v.Total / 1024 / 1024

// For KIC, do not allocate more memory than the container has available (+ some slack)
if driver.IsKIC(drvName) {
s, err := oci.DaemonInfo(drvName)
if err != nil {
return fallback
}
maximum = int(s.TotalMemory/1024/1024) - 128
}

suggested := int(available / 4)

if suggested > maximum {
suggested = maximum
}

if suggested < fallback {
suggested = fallback
}

glog.Infof("Selecting memory default of %dMB, given %dMB available and %dMB maximum", suggested, available, maximum)
return suggested
}

// validateMemorySize validates the memory size matches the minimum recommended
func validateMemorySize() {
memorySizeMB := pkgutil.CalculateSizeInMB(viper.GetString(memory))
if memorySizeMB < pkgutil.CalculateSizeInMB(minimumMemorySize) && !viper.GetBool(force) {
exit.WithCodeT(exit.Config, "Requested memory allocation {{.requested_size}} is less than the minimum allowed of {{.minimum_size}}", out.V{"requested_size": memorySizeMB, "minimum_size": pkgutil.CalculateSizeInMB(minimumMemorySize)})
req := pkgutil.CalculateSizeInMB(viper.GetString(memory))
if req < minUsableMem && !viper.GetBool(force) {
exit.WithCodeT(exit.Config, "Requested memory allocation {{.requested}}MB is less than the usable minimum of {{.minimum}}MB",
out.V{"requested": req, "mininum": minUsableMem})
}
if memorySizeMB < pkgutil.CalculateSizeInMB(defaultMemorySize) && !viper.GetBool(force) {
out.T(out.Notice, "Requested memory allocation ({{.memory}}MB) is less than the default memory allocation of {{.default_memorysize}}MB. Beware that minikube might not work correctly or crash unexpectedly.",
out.V{"memory": memorySizeMB, "default_memorysize": pkgutil.CalculateSizeInMB(defaultMemorySize)})
if req < minRecommendedMem && !viper.GetBool(force) {
out.T(out.Notice, "Requested memory allocation ({{.requested}}MB) is less than the recommended minimum {{.recommended}}MB. Kubernetes may crash unexpectedly.",
out.V{"requested": req, "recommended": minRecommendedMem})
}
}

Expand All @@ -677,14 +706,23 @@ func validateCPUCount(local bool) {

// validateFlags validates the supplied flags against known bad combinations
func validateFlags(cmd *cobra.Command, drvName string) {
validateDiskSize()
validateMemorySize()
if cmd.Flags().Changed(humanReadableDiskSize) {
diskSizeMB := pkgutil.CalculateSizeInMB(viper.GetString(humanReadableDiskSize))
if diskSizeMB < pkgutil.CalculateSizeInMB(minimumDiskSize) && !viper.GetBool(force) {
exit.WithCodeT(exit.Config, "Requested disk size {{.requested_size}} is less than minimum of {{.minimum_size}}", out.V{"requested_size": diskSizeMB, "minimum_size": pkgutil.CalculateSizeInMB(minimumDiskSize)})
}
}

if !driver.HasResourceLimits(drvName) {
if cmd.Flags().Changed(cpus) {
if cmd.Flags().Changed(cpus) {
validateCPUCount(driver.BareMetal(drvName))
if !driver.HasResourceLimits(drvName) {
out.WarningT("The '{{.name}}' driver does not respect the --cpus flag", out.V{"name": drvName})
}
if cmd.Flags().Changed(memory) {
}

if cmd.Flags().Changed(memory) {
validateMemorySize()
if !driver.HasResourceLimits(drvName) {
out.WarningT("The '{{.name}}' driver does not respect the --memory flag", out.V{"name": drvName})
}
}
Expand All @@ -700,8 +738,6 @@ func validateFlags(cmd *cobra.Command, drvName string) {
}
}

validateCPUCount(driver.BareMetal(drvName))

// check that kubeadm extra args contain only whitelisted parameters
for param := range node.ExtraOptions.AsMap().Get(bsutil.Kubeadm) {
if !config.ContainsParam(bsutil.KubeadmExtraArgsWhitelist[bsutil.KubeadmCmdParam], param) &&
Expand Down Expand Up @@ -781,6 +817,11 @@ func generateCfgFromFlags(cmd *cobra.Command, k8sVersion string, drvName string)
kubeNodeName = "m01"
}

mem := defaultMemorySize(drvName)
if viper.GetString(memory) != "" {
mem = pkgutil.CalculateSizeInMB(viper.GetString(memory))
}

// Create the initial node, which will necessarily be a control plane
cp := config.Node{
Port: viper.GetInt(apiServerPort),
Expand All @@ -794,7 +835,8 @@ func generateCfgFromFlags(cmd *cobra.Command, k8sVersion string, drvName string)
Name: viper.GetString(config.ProfileName),
KeepContext: viper.GetBool(keepContext),
EmbedCerts: viper.GetBool(embedCerts),
Memory: pkgutil.CalculateSizeInMB(viper.GetString(memory)),
MinikubeISO: viper.GetString(isoURL),
Memory: mem,
CPUs: viper.GetInt(cpus),
DiskSize: pkgutil.CalculateSizeInMB(viper.GetString(humanReadableDiskSize)),
Driver: drvName,
Expand Down
2 changes: 1 addition & 1 deletion test/integration/aab_offline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestOffline(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
defer CleanupWithLogs(t, profile, cancel)

startArgs := []string{"start", "-p", profile, "--alsologtostderr", "-v=1", "--wait=true", "--container-runtime", runtime}
startArgs := []string{"start", "-p", profile, "--alsologtostderr", "-v=1", "--memory=2000", "--wait=true", "--container-runtime", runtime}
startArgs = append(startArgs, StartArgs()...)
c := exec.CommandContext(ctx, Target(), startArgs...)
env := os.Environ()
Expand Down
2 changes: 1 addition & 1 deletion test/integration/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestDockerFlags(t *testing.T) {
defer CleanupWithLogs(t, profile, cancel)

// Use the most verbose logging for the simplest test. If it fails, something is very wrong.
args := append([]string{"start", "-p", profile, "--cache-images=false", "--install-addons=false", "--wait=false", "--docker-env=FOO=BAR", "--docker-env=BAZ=BAT", "--docker-opt=debug", "--docker-opt=icc=true", "--alsologtostderr", "-v=5"}, StartArgs()...)
args := append([]string{"start", "-p", profile, "--cache-images=false", "--memory=1800", "--install-addons=false", "--wait=false", "--docker-env=FOO=BAR", "--docker-env=BAZ=BAT", "--docker-opt=debug", "--docker-opt=icc=true", "--alsologtostderr", "-v=5"}, StartArgs()...)
rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
if err != nil {
t.Errorf("%s failed: %v", rr.Args, err)
Expand Down
2 changes: 1 addition & 1 deletion test/integration/guest_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestGuestEnvironment(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
defer CleanupWithLogs(t, profile, cancel)

args := append([]string{"start", "-p", profile, "--install-addons=false", "--wait=false"}, StartArgs()...)
args := append([]string{"start", "-p", profile, "--install-addons=false", "--memory=1800", "--wait=false"}, StartArgs()...)
rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
if err != nil {
t.Errorf("%s failed: %v", rr.Args, err)
Expand Down
2 changes: 1 addition & 1 deletion test/integration/gvisor_addon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestGvisorAddon(t *testing.T) {
CleanupWithLogs(t, profile, cancel)
}()

startArgs := append([]string{"start", "-p", profile, "--container-runtime=containerd", "--docker-opt", "containerd=/var/run/containerd/containerd.sock"}, StartArgs()...)
startArgs := append([]string{"start", "-p", profile, "--memory=2200", "--container-runtime=containerd", "--docker-opt", "containerd=/var/run/containerd/containerd.sock"}, StartArgs()...)
rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
if err != nil {
t.Fatalf("%s failed: %v", rr.Args, err)
Expand Down
2 changes: 1 addition & 1 deletion test/integration/start_stop_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestStartStop(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
defer CleanupWithLogs(t, profile, cancel)

startArgs := append([]string{"start", "-p", profile, "--alsologtostderr", "-v=3", "--wait=true"}, tc.args...)
startArgs := append([]string{"start", "-p", profile, "--memory=2200", "--alsologtostderr", "-v=3", "--wait=true"}, tc.args...)
startArgs = append(startArgs, StartArgs()...)
startArgs = append(startArgs, fmt.Sprintf("--kubernetes-version=%s", tc.version))

Expand Down
2 changes: 1 addition & 1 deletion test/integration/version_upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestVersionUpgrade(t *testing.T) {
}
}

args := append([]string{"start", "-p", profile, fmt.Sprintf("--kubernetes-version=%s", constants.OldestKubernetesVersion), "--alsologtostderr", "-v=1"}, StartArgs()...)
args := append([]string{"start", "-p", profile, "--memory=2200", fmt.Sprintf("--kubernetes-version=%s", constants.OldestKubernetesVersion), "--alsologtostderr", "-v=1"}, StartArgs()...)
rr := &RunResult{}
r := func() error {
rr, err = Run(t, exec.CommandContext(ctx, tf.Name(), args...))
Expand Down