Skip to content

auto-pause addon: automatically pause Kubernetes when not in use #10427

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

Merged
merged 26 commits into from
Feb 25, 2021
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ _testmain.go
*.test
*.prof

/deploy/kicbase/auto-pause
/out
/_gopath

6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -648,7 +648,7 @@ KICBASE_ARCH = linux/arm64,linux/amd64
KICBASE_IMAGE_REGISTRIES ?= $(REGISTRY)/kicbase:$(KIC_VERSION) kicbase/stable:$(KIC_VERSION)

.PHONY: push-kic-base-image
push-kic-base-image: docker-multi-arch-builder ## Push multi-arch local/kicbase:latest to all remote registries
push-kic-base-image: deploy/kicbase/auto-pause docker-multi-arch-builder ## Push multi-arch local/kicbase:latest to all remote registries
ifdef AUTOPUSH
docker login gcr.io/k8s-minikube
docker login docker.pkg.github.com
@@ -812,6 +812,10 @@ site: site/themes/docsy/assets/vendor/bootstrap/package.js out/hugo/hugo ## Serv
out/mkcmp:
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $@ cmd/performance/mkcmp/main.go

.PHONY: deploy/kicbase/auto-pause # auto pause binary to be used for kic image work arround for not passing the whole repo as docker context
deploy/kicbase/auto-pause: $(SOURCE_GENERATED) $(SOURCE_FILES)
GOOS=linux GOARCH=$(GOARCH) go build -o $@ cmd/auto-pause/auto-pause.go

.PHONY: out/performance-bot
out/performance-bot:
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $@ cmd/performance/pr-bot/bot.go
123 changes: 123 additions & 0 deletions cmd/auto-pause/auto-pause.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2021 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 main

import (
"fmt"
"log"
"net/http"
"sync"
"time"

"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/cruntime"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/style"
)

var unpauseRequests = make(chan struct{})
var done = make(chan struct{})
var mu sync.Mutex

// TODO: initialize with current state (handle the case that user enables auto-pause after it is already paused)
var runtimePaused = false
var version = "0.0.1"

// TODO: #10597 make this configurable to support containerd/cri-o
var runtime = "docker"

func main() {
// TODO: #10595 make this configurable
const interval = time.Minute * 1
// channel for incoming messages
go func() {
for {
// On each iteration new timer is created
select {
// TODO: #10596 make it memory-leak proof
case <-time.After(interval):
runPause()
case <-unpauseRequests:
fmt.Printf("Got request\n")
if runtimePaused {
runUnpause()
}

done <- struct{}{}
}
}
}()

http.HandleFunc("/", handler) // each request calls handler
fmt.Printf("Starting auto-pause server %s at port 8080 \n", version)
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
}

// handler echoes the Path component of the requested URL.
func handler(w http.ResponseWriter, r *http.Request) {
unpauseRequests <- struct{}{}
<-done
fmt.Fprintf(w, "allow")
}

func runPause() {
mu.Lock()
defer mu.Unlock()
if runtimePaused {
return
}

r := command.NewExecRunner(true)

cr, err := cruntime.New(cruntime.Config{Type: runtime, Runner: r})
if err != nil {
exit.Error(reason.InternalNewRuntime, "Failed runtime", err)
}

uids, err := cluster.Pause(cr, r, []string{"kube-system"})
if err != nil {
exit.Error(reason.GuestPause, "Pause", err)
}

runtimePaused = true

out.Step(style.Unpause, "Paused {{.count}} containers", out.V{"count": len(uids)})
}

func runUnpause() {
fmt.Println("unpausing...")
mu.Lock()
defer mu.Unlock()

r := command.NewExecRunner(true)

cr, err := cruntime.New(cruntime.Config{Type: runtime, Runner: r})
if err != nil {
exit.Error(reason.InternalNewRuntime, "Failed runtime", err)
}

uids, err := cluster.Unpause(cr, r, nil)
if err != nil {
exit.Error(reason.GuestUnpause, "Unpause", err)
}
runtimePaused = false

out.Step(style.Unpause, "Unpaused {{.count}} containers", out.V{"count": len(uids)})
}
9 changes: 8 additions & 1 deletion cmd/minikube/cmd/status.go
Original file line number Diff line number Diff line change
@@ -379,7 +379,14 @@ func nodeStatus(api libmachine.API, cc config.ClusterConfig, n config.Node) (*St
return st, nil
}

hostname, _, port, err := driver.ControlPlaneEndpoint(&cc, &n, host.DriverName)
var hostname string
var port int
if cc.Addons["auto-pause"] {
hostname, _, port, err = driver.AutoPauseProxyEndpoint(&cc, &n, host.DriverName)
} else {
hostname, _, port, err = driver.ControlPlaneEndpoint(&cc, &n, host.DriverName)
}

if err != nil {
klog.Errorf("forwarded endpoint: %v", err)
st.Kubeconfig = Misconfigured
10 changes: 10 additions & 0 deletions deploy/addons/auto-pause/auto-pause.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=Auto Pause Service

[Service]
Type=simple
ExecStart=/usr/local/bin/auto-pause
Restart=always

[Install]
WantedBy=multi-user.target
47 changes: 47 additions & 0 deletions deploy/addons/auto-pause/auto-pause.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: auto-pause
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auto-pause-proxy
namespace: auto-pause
labels:
app: auto-pause-proxy
spec:
replicas: 1
selector:
matchLabels:
app: auto-pause-proxy
template:
metadata:
creationTimestamp: null
labels:
app: auto-pause-proxy
spec:
volumes:
- name: ha-cfg
hostPath:
path: /var/lib/minikube/haproxy.cfg
type: File
- name: lua-script
hostPath:
path: /var/lib/minikube/unpause.lua
type: File
containers:
- name: auto-pause
image: "haproxy:2.3.5-alpine"
ports:
- name: https
containerPort: 6443
hostPort: 32443
protocol: TCP
volumeMounts:
- name: ha-cfg
mountPath: /usr/local/etc/haproxy/haproxy.cfg
readOnly: true
- name: lua-script
mountPath: /etc/haproxy/unpause.lua
37 changes: 37 additions & 0 deletions deploy/addons/auto-pause/haproxy.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#---------------------------------------------------------------------
# Configure HAProxy for Kubernetes API Server
#---------------------------------------------------------------------
listen stats
bind *:9000
mode http
stats enable
stats hide-version
stats uri /
stats refresh 30s
option httplog

# change haproxy.cfg file with the following
global
lua-load /etc/haproxy/unpause.lua

############## Configure HAProxy Secure Frontend #############
frontend k8s-api-https-proxy
bind *:6443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
default_backend k8s-api-https
############## Configure HAProxy SecureBackend #############
backend k8s-api-https
balance roundrobin
mode tcp
#tcp-request inspect-delay 10s
#tcp-request content lua.foo_action
tcp-request inspect-delay 10s
tcp-request content lua.unpause 192.168.49.2 8080
tcp-request content reject if { var(req.blocked) -m bool }
option tcplog
option tcp-check
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server k8s-api-1 192.168.49.2:8443 check

57 changes: 57 additions & 0 deletions deploy/addons/auto-pause/unpause.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
local function unpause(txn, addr, port)
if not addr then addr = '127.0.0.1' end
if not port then port = 5000 end

-- Set up a request to the service
local hdrs = {
[1] = string.format('host: %s:%s', addr, port),
[2] = 'accept: */*',
[3] = 'connection: close'
}

local req = {
[1] = string.format('GET /%s HTTP/1.1', tostring(txn.f:src())),
[2] = table.concat(hdrs, '\r\n'),
[3] = '\r\n'
}

req = table.concat(req, '\r\n')

-- Use core.tcp to get an instance of the Socket class
local socket = core.tcp()
socket:settimeout(5)

-- Connect to the service and send the request
if socket:connect(addr, port) then
if socket:send(req) then
-- Skip response headers
while true do
local line, _ = socket:receive('*l')

if not line then break end
if line == '' then break end
end

-- Get response body, if any
local content = socket:receive('*a')

-- Check if this request should be allowed
if content and content == 'allow' then
txn:set_var('req.blocked', false)
return
end
else
core.Alert('Could not connect to IP Checker server (send)')
end

socket:close()
else
core.Alert('Could not connect to IP Checker server (connect)')
end

-- The request should be blocked
txn:set_var('req.blocked', true)
end

core.register_action('unpause', {'tcp-req'}, unpause, 2)

2 changes: 2 additions & 0 deletions deploy/kicbase/Dockerfile
Original file line number Diff line number Diff line change
@@ -28,6 +28,8 @@ COPY 10-network-security.conf /etc/sysctl.d/10-network-security.conf
COPY 11-tcp-mtu-probing.conf /etc/sysctl.d/11-tcp-mtu-probing.conf
COPY clean-install /usr/local/bin/clean-install
COPY entrypoint /usr/local/bin/entrypoint
# must first run make out/auto-pause
COPY auto-pause /usr/local/bin/auto-pause

# Install dependencies, first from apt, then from release tarballs.
# NOTE: we use one RUN to minimize layers.
65 changes: 59 additions & 6 deletions pkg/addons/addons.go
Original file line number Diff line number Diff line change
@@ -38,12 +38,15 @@ import (
"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/machine"
"k8s.io/minikube/pkg/minikube/mustload"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/out/register"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/storageclass"
"k8s.io/minikube/pkg/minikube/style"
"k8s.io/minikube/pkg/minikube/sysinit"
"k8s.io/minikube/pkg/util/retry"
)

@@ -201,13 +204,19 @@ https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Dri
}
}

cmd, err := machine.CommandRunner(host)
runner, err := machine.CommandRunner(host)
if err != nil {
return errors.Wrap(err, "command runner")
}

if name == "auto-pause" && !enable { // needs to be disabled before deleting the service file in the internal disable
if err := sysinit.New(runner).DisableNow("auto-pause"); err != nil {
klog.ErrorS(err, "failed to disable", "service", "auto-pause")
}
}

data := assets.GenerateTemplateData(addon, cc.KubernetesConfig)
return enableOrDisableAddonInternal(cc, addon, cmd, data, enable)
return enableOrDisableAddonInternal(cc, addon, runner, data, enable)
}

func isAddonAlreadySet(cc *config.ClusterConfig, addon *assets.Addon, enable bool) bool {
@@ -223,7 +232,7 @@ func isAddonAlreadySet(cc *config.ClusterConfig, addon *assets.Addon, enable boo
return false
}

func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon, cmd command.Runner, data interface{}, enable bool) error {
func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon, runner command.Runner, data interface{}, enable bool) error {
deployFiles := []string{}

for _, addon := range addon.Assets {
@@ -242,13 +251,13 @@ func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon,

if enable {
klog.Infof("installing %s", fPath)
if err := cmd.Copy(f); err != nil {
if err := runner.Copy(f); err != nil {
return err
}
} else {
klog.Infof("Removing %+v", fPath)
defer func() {
if err := cmd.Remove(f); err != nil {
if err := runner.Remove(f); err != nil {
klog.Warningf("error removing %s; addon should still be disabled as expected", fPath)
}
}()
@@ -260,7 +269,7 @@ func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon,

// Retry, because sometimes we race against an apiserver restart
apply := func() error {
_, err := cmd.RunCmd(kubectlCommand(cc, deployFiles, enable))
_, err := runner.RunCmd(kubectlCommand(cc, deployFiles, enable))
if err != nil {
klog.Warningf("apply failed, will retry: %v", err)
}
@@ -435,3 +444,47 @@ func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]boo
}
}
}

// enableOrDisableAutoPause enables the service after the config was copied by generic enble
func enableOrDisableAutoPause(cc *config.ClusterConfig, name string, val string) error {
enable, err := strconv.ParseBool(val)
if err != nil {
return errors.Wrapf(err, "parsing bool: %s", name)
}
out.Infof("auto-pause addon is an alpha feature and still in early development. Please file issues to help us make it better.")
out.Infof("https://github.com/kubernetes/minikube/labels/co%2Fauto-pause")

if !driver.IsKIC(cc.Driver) || runtime.GOARCH != "amd64" {
exit.Message(reason.Usage, `auto-pause currently is only supported on docker driver/docker runtime/amd64. Track progress of others here: https://github.com/kubernetes/minikube/issues/10601`)
}
co := mustload.Running(cc.Name)
if enable {
if err := sysinit.New(co.CP.Runner).EnableNow("auto-pause"); err != nil {
klog.ErrorS(err, "failed to enable", "service", "auto-pause")
}
}

port := co.CP.Port // api server port
if enable { // if enable then need to calculate the forwarded port
port = constants.AutoPauseProxyPort
if driver.NeedsPortForward(cc.Driver) {
port, err = oci.ForwardedPort(cc.Driver, cc.Name, port)
if err != nil {
klog.ErrorS(err, "failed to get forwarded port for", "auto-pause port", port)
}
}
}

updated, err := kubeconfig.UpdateEndpoint(cc.Name, co.CP.Hostname, port, kubeconfig.PathFromEnv(), kubeconfig.NewExtension())
if err != nil {
klog.ErrorS(err, "failed to update kubeconfig", "auto-pause proxy endpoint")
return err
}
if updated {
klog.Infof("%s context has been updated to point to auto-pause proxy %s:%s", cc.Name, co.CP.Hostname, co.CP.Port)
} else {
klog.Info("no need to update kube-context for auto-pause proxy")
}

return nil
}
6 changes: 6 additions & 0 deletions pkg/addons/config.go
Original file line number Diff line number Diff line change
@@ -42,6 +42,12 @@ var addonPodLabels = map[string]string{

// Addons is a list of all addons
var Addons = []*Addon{
{
name: "auto-pause",
set: SetBool,
callbacks: []setFn{EnableOrDisableAddon, enableOrDisableAutoPause},
},

{
name: "dashboard",
set: SetBool,
4 changes: 4 additions & 0 deletions pkg/drivers/kic/kic.go
Original file line number Diff line number Diff line change
@@ -127,6 +127,10 @@ func (d *Driver) Create() error {
ListenAddress: listAddr,
ContainerPort: constants.RegistryAddonPort,
},
oci.PortMapping{
ListenAddress: listAddr,
ContainerPort: constants.AutoPauseProxyPort,
},
)

exists, err := oci.ContainerExists(d.OCIBinary, params.Name, true)
4 changes: 2 additions & 2 deletions pkg/drivers/kic/types.go
Original file line number Diff line number Diff line change
@@ -24,9 +24,9 @@ import (

const (
// Version is the current version of kic
Version = "v0.0.17-1613934488-10548"
Version = "v0.0.17-1614202509-10427"
// SHA of the kic base image
baseImageSHA = "5cacd48d07f699a171eedf65ef1490bd59a523ffcd90662e3b66eb838c5a1b5d"
baseImageSHA = "93f2448d272ebad3d564c04474cafe03bb7440149c241d1d010b2e90e14da213"
// The name of the GCR kicbase repository
gcrRepo = "gcr.io/k8s-minikube/kicbase-builds"
// The name of the Dockerhub kicbase repository
28 changes: 28 additions & 0 deletions pkg/minikube/assets/addons.go
Original file line number Diff line number Diff line change
@@ -71,6 +71,34 @@ func (a *Addon) IsEnabled(cc *config.ClusterConfig) bool {
// Addons is the list of addons
// TODO: Make dynamically loadable: move this data to a .yaml file within each addon directory
var Addons = map[string]*Addon{
"auto-pause": NewAddon([]*BinAsset{
MustBinAsset(
"deploy/addons/auto-pause/auto-pause.yaml.tmpl",
vmpath.GuestAddonsDir,
"auto-pause.yaml",
"0640"),
MustBinAsset(
"deploy/addons/auto-pause/haproxy.cfg",
"/var/lib/minikube/",
"haproxy.cfg",
"0640"),
MustBinAsset(
"deploy/addons/auto-pause/unpause.lua",
"/var/lib/minikube/",
"unpause.lua",
"0640"),
MustBinAsset(
"deploy/addons/auto-pause/auto-pause.service",
"/etc/systemd/system/",
"auto-pause.service",
"0640"),

//GuestPersistentDir
}, false, "auto-pause", map[string]string{
"haproxy": "haproxy:2.3.5",
}, map[string]string{
"haproxy": "gcr.io",
}),
"dashboard": NewAddon([]*BinAsset{
// We want to create the kubernetes-dashboard ns first so that every subsequent object can be created
MustBinAsset("deploy/addons/dashboard/dashboard-ns.yaml", vmpath.GuestAddonsDir, "dashboard-ns.yaml", "0640"),
3 changes: 3 additions & 0 deletions pkg/minikube/constants/constants.go
Original file line number Diff line number Diff line change
@@ -41,6 +41,9 @@ const (
DockerDaemonPort = 2376
// APIServerPort is the default API server port
APIServerPort = 8443
// AutoPauseProxyPort is the port to be used as a reverse proxy for apiserver port
AutoPauseProxyPort = 32443

// SSHPort is the SSH serviceport on the node vm and container
SSHPort = 22
// RegistryAddonPort os the default registry addon port
10 changes: 10 additions & 0 deletions pkg/minikube/driver/endpoint.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import (
"fmt"
"net"

"k8s.io/klog/v2"
"k8s.io/minikube/pkg/drivers/kic/oci"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
@@ -29,6 +30,9 @@ import (
func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
if NeedsPortForward(driverName) {
port, err := oci.ForwardedPort(cc.Driver, cc.Name, cp.Port)
if err != nil {
klog.Warningf("failed to get forwarded control plane port %v", err)
}
hostname := oci.DaemonHost(driverName)

ip := net.ParseIP(hostname)
@@ -54,3 +58,9 @@ func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName
}
return hostname, ip, cp.Port, nil
}

// AutoPauseProxyEndpoint returns the endpoint for the auto-pause (reverse proxy to api-sever)
func AutoPauseProxyEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
cp.Port = constants.AutoPauseProxyPort
return ControlPlaneEndpoint(cc, cp, driverName)
}
10 changes: 10 additions & 0 deletions pkg/minikube/sysinit/openrc.go
Original file line number Diff line number Diff line change
@@ -117,11 +117,21 @@ func (s *OpenRC) Disable(svc string) error {
return nil
}

// DisableNow not implemented for openRC
func (s *OpenRC) DisableNow(svc string) error {
return fmt.Errorf("disable now is not implemented for OpenRC! PRs to fix are welcomed")
}

// Enable does nothing
func (s *OpenRC) Enable(svc string) error {
return nil
}

// EnableNow not implemented for openRC
func (s *OpenRC) EnableNow(svc string) error {
return fmt.Errorf("enable now is not implemented for OpenRC! PRs to fix are welcomed")
}

// Restart restarts a service
func (s *OpenRC) Restart(svc string) error {
rr, err := s.r.RunCmd(exec.Command("sudo", "service", svc, "restart"))
6 changes: 6 additions & 0 deletions pkg/minikube/sysinit/sysinit.go
Original file line number Diff line number Diff line change
@@ -41,9 +41,15 @@ type Manager interface {
// Disable disables a service
Disable(string) error

// Disable disables a service and stops it right after.
DisableNow(string) error

// Enable enables a service
Enable(string) error

// EnableNow enables a service and starts it right after.
EnableNow(string) error

// Start starts a service idempotently
Start(string) error

15 changes: 15 additions & 0 deletions pkg/minikube/sysinit/systemd.go
Original file line number Diff line number Diff line change
@@ -52,6 +52,12 @@ func (s *Systemd) Disable(svc string) error {
return err
}

// DisableNow disables a service and stops it too (not waiting for next restart)
func (s *Systemd) DisableNow(svc string) error {
_, err := s.r.RunCmd(exec.Command("sudo", "systemctl", "disable", "--now", svc))
return err
}

// Enable enables a service
func (s *Systemd) Enable(svc string) error {
if svc == "kubelet" {
@@ -61,6 +67,15 @@ func (s *Systemd) Enable(svc string) error {
return err
}

// Enable enables a service and then activates it too (not waiting for next start)
func (s *Systemd) EnableNow(svc string) error {
if svc == "kubelet" {
return errors.New("please don't enable kubelet as it creates a race condition; if it starts on systemd boot it will pick up /etc/hosts before we have time to configure /etc/hosts")
}
_, err := s.r.RunCmd(exec.Command("sudo", "systemctl", "enable", "--now", svc))
return err
}

// Start starts a service
func (s *Systemd) Start(svc string) error {
if err := s.daemonReload(); err != nil {
2 changes: 1 addition & 1 deletion site/content/en/docs/commands/start.md
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ minikube start [flags]
--apiserver-names strings A set of apiserver names which are used in the generated certificate for kubernetes. This can be used if you want to make the apiserver available from outside the machine
--apiserver-port int The apiserver listening port (default 8443)
--auto-update-drivers If set, automatically updates drivers to the latest version. Defaults to true. (default true)
--base-image string The base image to use for docker/podman drivers. Intended for local development. (default "gcr.io/k8s-minikube/kicbase-builds:v0.0.17-1613934488-10548@sha256:5cacd48d07f699a171eedf65ef1490bd59a523ffcd90662e3b66eb838c5a1b5d")
--base-image string The base image to use for docker/podman drivers. Intended for local development. (default "gcr.io/k8s-minikube/kicbase-builds:v0.0.17-1614202509-10427@sha256:93f2448d272ebad3d564c04474cafe03bb7440149c241d1d010b2e90e14da213")
--cache-images If true, cache docker images for the current bootstrapper and load them into the machine. Always false with --driver=none. (default true)
--cni string CNI plug-in to use. Valid options: auto, bridge, calico, cilium, flannel, kindnet, or path to a CNI manifest (default: auto)
--container-runtime string The container runtime to be used (docker, cri-o, containerd). (default "docker")