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

Implement --static-ip flag #15553

Merged
merged 3 commits into from
Jan 9, 2023
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
30 changes: 30 additions & 0 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,12 @@ func validateFlags(cmd *cobra.Command, drvName string) {
validateCNI(cmd, viper.GetString(containerRuntime))
}

if cmd.Flags().Changed(staticIP) {
if err := validateStaticIP(viper.GetString(staticIP), drvName, viper.GetString(subnet)); err != nil {
exit.Message(reason.Usage, "{{.err}}", out.V{"err": err})
}
}

if driver.IsSSH(drvName) {
sshIPAddress := viper.GetString(sshIPAddress)
if sshIPAddress == "" {
Expand Down Expand Up @@ -1772,6 +1778,30 @@ func validateSubnet(subnet string) error {
return nil
}

func validateStaticIP(staticIP, drvName, subnet string) error {
if !driver.IsKIC(drvName) {
if staticIP != "" {
out.WarningT("--static-ip is only implemented on Docker and Podman drivers, flag will be ignored")
}
return nil
}
if subnet != "" {
out.WarningT("--static-ip overrides --subnet, --subnet will be ignored")
}
ip := net.ParseIP(staticIP)
if !ip.IsPrivate() {
return fmt.Errorf("static IP must be private")
}
if ip.To4() == nil {
return fmt.Errorf("static IP must be IPv4")
}
lastOctet, _ := strconv.Atoi(strings.Split(ip.String(), ".")[3])
if lastOctet < 2 || lastOctet > 254 {
return fmt.Errorf("static IPs last octet must be between 2 and 254 (X.X.X.2 - X.X.X.254), for example 192.168.200.200")
}
return nil
}

func validateBareMetal(drvName string) {
if !driver.BareMetal(drvName) {
return
Expand Down
7 changes: 7 additions & 0 deletions cmd/minikube/cmd/start_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const (
qemuFirmwarePath = "qemu-firmware-path"
socketVMnetClientPath = "socket-vmnet-client-path"
socketVMnetPath = "socket-vmnet-path"
staticIP = "static-ip"
)

var (
Expand Down Expand Up @@ -200,6 +201,7 @@ func initMinikubeFlags() {
startCmd.Flags().String(binaryMirror, "", "Location to fetch kubectl, kubelet, & kubeadm binaries from.")
startCmd.Flags().Bool(disableOptimizations, false, "If set, disables optimizations that are set for local Kubernetes. Including decreasing CoreDNS replicas from 2 to 1. Defaults to false.")
startCmd.Flags().Bool(disableMetrics, false, "If set, disables metrics reporting (CPU and memory usage), this can improve CPU usage. Defaults to false.")
startCmd.Flags().String(staticIP, "", "Set a static IP for the minikube cluster, the IP must be: private, IPv4, and the last octet must be between 2 and 254, for example 192.168.200.200 (Docker and Podman drivers only)")
}

// initKubernetesFlags inits the commandline flags for Kubernetes related options
Expand Down Expand Up @@ -571,6 +573,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
CustomQemuFirmwarePath: viper.GetString(qemuFirmwarePath),
SocketVMnetClientPath: viper.GetString(socketVMnetClientPath),
SocketVMnetPath: viper.GetString(socketVMnetPath),
StaticIP: viper.GetString(staticIP),
KubernetesConfig: config.KubernetesConfig{
KubernetesVersion: k8sVersion,
ClusterName: ClusterFlagValue(),
Expand Down Expand Up @@ -747,6 +750,10 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC
out.WarningT("You cannot add or remove extra disks for an existing minikube cluster. Please first delete the cluster.")
}

if cmd.Flags().Changed(staticIP) && viper.GetString(staticIP) != existing.StaticIP {
out.WarningT("You cannot change the static IP of an existing minikube cluster. Please first delete the cluster.")
}

updateBoolFromFlag(cmd, &cc.KeepContext, keepContext)
updateBoolFromFlag(cmd, &cc.EmbedCerts, embedCerts)
updateStringFromFlag(cmd, &cc.MinikubeISO, isoURL)
Expand Down
59 changes: 59 additions & 0 deletions cmd/minikube/cmd/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,62 @@ func TestValidateSubnet(t *testing.T) {
})
}
}

func TestValidateStaticIP(t *testing.T) {
tests := []struct {
staticIP string
drvName string
errorMsg string
}{
{
staticIP: "8.8.8.8",
drvName: "docker",
errorMsg: "static IP must be private",
},
{
staticIP: "8.8.8.8",
drvName: "hyperkit",
errorMsg: "",
},
{
staticIP: "fdfc:a4c0:e99e:7ad3::",
drvName: "docker",
errorMsg: "static IP must be IPv4",
},
{
staticIP: "192.168.49.0",
drvName: "docker",
errorMsg: "static IPs last octet must be between 2 and 254 (X.X.X.2 - X.X.X.254), for example 192.168.200.200",
},
{
staticIP: "192.168.49.1",
drvName: "docker",
errorMsg: "static IPs last octet must be between 2 and 254 (X.X.X.2 - X.X.X.254), for example 192.168.200.200",
},
{
staticIP: "192.168.49.255",
drvName: "docker",
errorMsg: "static IPs last octet must be between 2 and 254 (X.X.X.2 - X.X.X.254), for example 192.168.200.200",
},
{
staticIP: "192.168.49.2",
drvName: "docker",
errorMsg: "",
},
{
staticIP: "192.168.49.254",
drvName: "docker",
errorMsg: "",
},
}
for _, tt := range tests {
gotError := ""
got := validateStaticIP(tt.staticIP, tt.drvName, "")
if got != nil {
gotError = got.Error()
}
if gotError != tt.errorMsg {
t.Errorf("validateStaticIP(%s, %s): got %v, expected %v", tt.staticIP, tt.drvName, got, tt.errorMsg)
}
}
}
15 changes: 13 additions & 2 deletions pkg/drivers/kic/kic.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ import (
"k8s.io/minikube/pkg/minikube/cruntime"
"k8s.io/minikube/pkg/minikube/download"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/style"
"k8s.io/minikube/pkg/minikube/sysinit"
"k8s.io/minikube/pkg/util/retry"
Expand Down Expand Up @@ -92,8 +94,17 @@ func (d *Driver) Create() error {
if networkName == "" {
networkName = d.NodeConfig.ClusterName
}
if gateway, err := oci.CreateNetwork(d.OCIBinary, networkName, d.NodeConfig.Subnet); err != nil {
out.WarningT("Unable to create dedicated network, this might result in cluster IP change after restart: {{.error}}", out.V{"error": err})
staticIP := d.NodeConfig.StaticIP
if gateway, err := oci.CreateNetwork(d.OCIBinary, networkName, d.NodeConfig.Subnet, staticIP); err != nil {
msg := "Unable to create dedicated network, this might result in cluster IP change after restart: {{.error}}"
args := out.V{"error": err}
if staticIP != "" {
exit.Message(reason.IfDedicatedNetwork, msg, args)
}
out.WarningT(msg, args)
} else if gateway != nil && staticIP != "" {
params.Network = networkName
params.IP = staticIP
} else if gateway != nil {
params.Network = networkName
ip := gateway.To4()
Expand Down
12 changes: 10 additions & 2 deletions pkg/drivers/kic/oci/network_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func firstSubnetAddr(subnet string) string {
}

// CreateNetwork creates a network returns gateway and error, minikube creates one network per cluster
func CreateNetwork(ociBin, networkName, subnet string) (net.IP, error) {
func CreateNetwork(ociBin, networkName, subnet, staticIP string) (net.IP, error) {
defaultBridgeName := defaultBridgeName(ociBin)
if networkName == defaultBridgeName {
klog.Infof("skipping creating network since default network %s was specified", networkName)
Expand All @@ -84,12 +84,20 @@ func CreateNetwork(ociBin, networkName, subnet string) (net.IP, error) {
klog.Warningf("failed to get mtu information from the %s's default network %q: %v", ociBin, defaultBridgeName, err)
}

tries := 20

// we don't want to increment the subnet IP on network creation failure if the user specifies a static IP, so set tries to 1
if staticIP != "" {
tries = 1
subnet = staticIP
}

// retry up to 5 times to create container network
for attempts, subnetAddr := 0, firstSubnetAddr(subnet); attempts < 5; attempts++ {
// Rather than iterate through all of the valid subnets, give up at 20 to avoid a lengthy user delay for something that is unlikely to work.
// will be like 192.168.49.0/24,..., 192.168.220.0/24 (in increment steps of 9)
var subnet *network.Parameters
subnet, err = network.FreeSubnet(subnetAddr, 9, 20)
subnet, err = network.FreeSubnet(subnetAddr, 9, tries)
if err != nil {
klog.Errorf("failed to find free subnet for %s network %s after %d attempts: %v", ociBin, networkName, 20, err)
return nil, fmt.Errorf("un-retryable: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion pkg/drivers/kic/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ type Config struct {
Envs map[string]string // key,value of environment variables passed to the node
KubernetesVersion string // Kubernetes version to install
ContainerRuntime string // container runtime kic is running
Network string // network to run with kic
Network string // network to run with kic
Subnet string // subnet to be used on kic cluster
StaticIP string // static IP for the kic cluster
ExtraArgs []string // a list of any extra option to pass to oci binary during creation time, for example --expose 8080...
ListenAddress string // IP Address to listen to
}
1 change: 1 addition & 0 deletions pkg/minikube/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type ClusterConfig struct {
CustomQemuFirmwarePath string
SocketVMnetClientPath string
SocketVMnetPath string
StaticIP string
}

// KubernetesConfig contains the parameters used to configure the VM Kubernetes.
Expand Down
2 changes: 2 additions & 0 deletions pkg/minikube/reason/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ var (
IfMountPort = Kind{ID: "IF_MOUNT_PORT", ExitCode: ExLocalNetworkError}
// minikube failed to access an ssh client on the host machine
IfSSHClient = Kind{ID: "IF_SSH_CLIENT", ExitCode: ExLocalNetworkError}
// minikube failed to create a dedicated network
IfDedicatedNetwork = Kind{ID: "IF_DEDICATED_NETWORK", ExitCode: ExLocalNetworkError}

// minikube failed to cache kubernetes binaries for the current runtime
InetCacheBinaries = Kind{ID: "INET_CACHE_BINARIES", ExitCode: ExInternetError}
Expand Down
1 change: 1 addition & 0 deletions pkg/minikube/registry/drvs/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func configure(cc config.ClusterConfig, n config.Node) (interface{}, error) {
ExtraArgs: extraArgs,
Network: cc.Network,
Subnet: cc.Subnet,
StaticIP: cc.StaticIP,
ListenAddress: cc.ListenAddress,
}), nil
}
Expand Down
1 change: 1 addition & 0 deletions site/content/en/docs/commands/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ minikube start [flags]
--ssh-key string SSH key (ssh driver only)
--ssh-port int SSH port (ssh driver only) (default 22)
--ssh-user string SSH user (ssh driver only) (default "root")
--static-ip string Set a static IP for the minikube cluster, the IP must be: private, IPv4, and the last octet must be between 2 and 254, for example 192.168.200.200 (Docker and Podman drivers only)
--subnet string Subnet to be used on kic cluster. If left empty, minikube will choose subnet address, beginning from 192.168.49.0. (docker and podman driver only)
--trace string Send trace events. Options include: [gcp]
--uuid string Provide VM UUID to restore MAC address (hyperkit driver only)
Expand Down
3 changes: 3 additions & 0 deletions site/content/en/docs/contrib/errorcodes.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ minikube failed to parse or find port for mount
"IF_SSH_CLIENT" (Exit code ExLocalNetworkError)
minikube failed to access an ssh client on the host machine

"IF_DEDICATED_NETWORK" (Exit code ExLocalNetworkError)
minikube failed to create a dedicated network

"INET_CACHE_BINARIES" (Exit code ExInternetError)
minikube failed to cache kubernetes binaries for the current runtime

Expand Down
3 changes: 3 additions & 0 deletions site/content/en/docs/contrib/tests.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ verifies the docker driver and run with an existing network
## TestKicCustomSubnet
verifies the docker/podman driver works with a custom subnet

## TestKicStaticIP
starts minikube with the static IP flag

## TestingKicBaseImage
will return true if the integraiton test is running against a passed --base-image flag

Expand Down
6 changes: 6 additions & 0 deletions site/content/en/docs/faq/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,9 @@ $env:MINIKUBE_HOME = "D:\.minikube"

minikube start
```

## Can I set a static IP for the minikube cluster?

Currently a static IP can only be set when using the Docker or Podman driver.

For more details see the [static IP tutorial]({{< ref "docs/tutorials/static_ip.md" >}}).
51 changes: 51 additions & 0 deletions site/content/en/docs/tutorials/static_ip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: "Setting a Static IP for a Cluster"
linkTitle: "Setting a Static IP for a Cluster"
weight: 1
date: 2023-01-04
---

## Overview

This tutorial will show you how to create a minikube cluster with a static IP.

## Prerequisites

- minikube v1.29.0 or higher
- Docker or Podman driver

## Selecting a static IP

The static IP must be IPv4, private, and the last octet must be between 2-254 (X.X.X.2 - X.X.X.254).

Valid static IPs:<br>
10.0.0.2 - 10.255.255.254<br>
172.16.0.2 - 172.31.255.254<br>
192.168.0.2 - 192.168.255.254

## Tutorial

Use the `--static-ip` flag on `minikube start` to set the static IP.

**Note:** You cannot add a static IP to an existing cluster, you have to delete and recreate the cluster with the flag.

```
$ minikube start --driver docker --static-ip 192.168.200.200
😄 minikube v1.28.0 on Darwin 13.1 (arm64)
✨ Using the docker driver based on user configuration
📌 Using Docker Desktop driver with root privileges
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔥 Creating docker container (CPUs=2, Memory=4000MB) ...
🐳 Preparing Kubernetes v1.25.3 on Docker 20.10.21 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

$ minikube ip
192.168.200.200
```
30 changes: 29 additions & 1 deletion test/integration/kic_custom_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestKicExistingNetwork(t *testing.T) {
}
// create custom network
networkName := "existing-network"
if _, err := oci.CreateNetwork(oci.Docker, networkName, ""); err != nil {
if _, err := oci.CreateNetwork(oci.Docker, networkName, "", ""); err != nil {
t.Fatalf("error creating network: %v", err)
}
defer func() {
Expand Down Expand Up @@ -117,6 +117,34 @@ func TestKicCustomSubnet(t *testing.T) {
verifySubnet(ctx, t, profile, subnet)
}

// TestKicStaticIP starts minikube with the static IP flag
func TestKicStaticIP(t *testing.T) {
if !KicDriver() {
t.Skip("only run with docker/podman driver")
}
profile := UniqueProfileName("static-ip")
ctx, cancel := context.WithTimeout(context.Background(), Minutes(5))
defer Cleanup(t, profile, cancel)

staticIP := "192.168.200.200"
startArgs := []string{"start", "-p", profile, fmt.Sprintf("--static-ip=%s", staticIP)}
c := exec.CommandContext(ctx, Target(), startArgs...)
rr, err := Run(t, c)
if err != nil {
t.Fatalf("%v failed: %v\n%v", rr.Command(), err, rr.Output())
}

c = exec.CommandContext(ctx, Target(), "-p", profile, "ip")
rr, err = Run(t, c)
if err != nil {
t.Fatalf("%s failed: %v\n%s", rr.Command(), err, rr.Output())
}

if !strings.Contains(rr.Output(), staticIP) {
t.Errorf("static IP (%s) not found in output %s", staticIP, rr.Output())
}
}

func verifyNetworkExists(ctx context.Context, t *testing.T, networkName string) {
c := exec.CommandContext(ctx, "docker", "network", "ls", "--format", "{{.Name}}")
rr, err := Run(t, c)
Expand Down
Loading