Skip to content

Commit

Permalink
Merge pull request #15553 from spowelljr/addStaticIP
Browse files Browse the repository at this point in the history
Implement --static-ip flag
  • Loading branch information
medyagh authored Jan 9, 2023
2 parents be430e3 + 6e2511d commit 0fee35b
Show file tree
Hide file tree
Showing 24 changed files with 254 additions and 6 deletions.
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

0 comments on commit 0fee35b

Please sign in to comment.