From 1c88f6d23ea396bf85affe6630893acb8f160428 Mon Sep 17 00:00:00 2001 From: Kartik Joshi Date: Wed, 26 Nov 2025 10:28:35 +0000 Subject: [PATCH] minikube: add ip-family flags and core IPv6 config plumbing --- cmd/minikube/cmd/start_flags.go | 143 +++++++++++++++++++++++++++- pkg/minikube/config/types.go | 8 ++ pkg/minikube/constants/constants.go | 4 + 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/cmd/minikube/cmd/start_flags.go b/cmd/minikube/cmd/start_flags.go index c7627f0468..ebc82e77a1 100644 --- a/cmd/minikube/cmd/start_flags.go +++ b/cmd/minikube/cmd/start_flags.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" + "net" "runtime" "strings" "time" @@ -61,6 +62,7 @@ const ( kubernetesVersion = "kubernetes-version" noKubernetes = "no-kubernetes" hostOnlyCIDR = "host-only-cidr" + hostOnlyCIDRv6 = "host-only-cidr-v6" containerRuntime = "container-runtime" criSocket = "cri-socket" networkPlugin = "network-plugin" // deprecated, use --cni instead @@ -84,6 +86,8 @@ const ( apiServerPort = "apiserver-port" dnsDomain = "dns-domain" serviceCIDR = "service-cluster-ip-range" + serviceCIDRv6 = "service-cluster-ip-range-v6" + ipFamily = "ip-family" imageRepository = "image-repository" imageMirrorCountry = "image-mirror-country" mountString = "mount-string" @@ -144,8 +148,12 @@ const ( socketVMnetClientPath = "socket-vmnet-client-path" socketVMnetPath = "socket-vmnet-path" staticIP = "static-ip" + staticIPv6 = "static-ipv6" gpus = "gpus" autoPauseInterval = "auto-pause-interval" + subnetv6 = "subnet-v6" + podCIDR = "pod-cidr" + podCIDRv6 = "pod-cidr-v6" ) var ( @@ -209,6 +217,7 @@ func initMinikubeFlags() { startCmd.Flags().Bool(disableMetrics, false, "If set, disables metrics reporting (CPU and memory usage), this can improve CPU usage. Defaults to false.") startCmd.Flags().Bool(disableCoreDNSLog, false, "If set, disable CoreDNS verbose logging. 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)") + startCmd.Flags().String(staticIPv6, "", "Set a static IPv6 address for the minikube cluster, for example fd00::100 (Docker and Podman drivers only)") startCmd.Flags().StringP(gpus, "g", "", "Allow pods to use your GPUs. Options include: [all,nvidia,amd] (Docker driver with Docker container-runtime only)") startCmd.Flags().Duration(autoPauseInterval, time.Minute*1, "Duration of inactivity before the minikube VM is paused (default 1m0s)") } @@ -260,6 +269,7 @@ func initDriverFlags() { // virtualbox startCmd.Flags().String(hostOnlyCIDR, "192.168.59.1/24", "The CIDR to be used for the minikube VM (virtualbox driver only)") + startCmd.Flags().String(hostOnlyCIDRv6, "fd00::1/64", "The IPv6 CIDR to be used for the minikube VM (virtualbox driver only)") startCmd.Flags().Bool(dnsProxy, false, "Enable proxy for NAT DNS requests (virtualbox driver only)") startCmd.Flags().Bool(hostDNSResolver, true, "Enable host resolver for NAT DNS requests (virtualbox driver only)") startCmd.Flags().Bool(noVTXCheck, false, "Disable checking for the availability of hardware virtualization before the vm is started (virtualbox driver only)") @@ -282,6 +292,9 @@ func initDriverFlags() { startCmd.Flags().String(listenAddress, "", "IP Address to use to expose ports (docker and podman driver only)") startCmd.Flags().StringSlice(ports, []string{}, "List of ports that should be exposed (docker and podman driver only)") startCmd.Flags().String(subnet, "", "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)") + startCmd.Flags().String(subnetv6, "", "IPv6 subnet (CIDR) for the Docker/Podman network. If empty, minikube picks an internal ULA. (docker and podman driver only)") + startCmd.Flags().String(podCIDR, "", "IPv4 CIDR to use for pod IPs (bridge CNI).") + startCmd.Flags().String(podCIDRv6, "", "IPv6 CIDR to use for pod IPs (bridge CNI).") // qemu startCmd.Flags().String(qemuFirmwarePath, "", "Path to the qemu firmware file. Defaults: For Linux, the default firmware location. For macOS, the brew installation location. For Windows, C:\\Program Files\\qemu\\share") @@ -293,7 +306,9 @@ func initNetworkingFlags() { startCmd.Flags().StringSliceVar(®istryMirror, "registry-mirror", nil, "Registry mirrors to pass to the Docker daemon") startCmd.Flags().String(imageRepository, "", "Alternative image repository to pull docker images from. This can be used when you have limited access to gcr.io. Set it to \"auto\" to let minikube decide one for you. For Chinese mainland users, you may use local gcr.io mirrors such as registry.cn-hangzhou.aliyuncs.com/google_containers") startCmd.Flags().String(imageMirrorCountry, "", "Country code of the image mirror to be used. Leave empty to use the global one. For Chinese mainland users, set it to cn.") - startCmd.Flags().String(serviceCIDR, constants.DefaultServiceCIDR, "The CIDR to be used for service cluster IPs.") + startCmd.Flags().String(serviceCIDR, constants.DefaultServiceCIDR, "The IPv4 CIDR to be used for service cluster IPs.") + startCmd.Flags().String(serviceCIDRv6, constants.DefaultServiceCIDRv6, "The IPv6 CIDR to be used for service cluster IPs.") + startCmd.Flags().String(ipFamily, "ipv4", "Cluster IP family mode: one of 'ipv4' (default), 'ipv6', or 'dual'.") startCmd.Flags().StringArrayVar(&config.DockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)") startCmd.Flags().StringArrayVar(&config.DockerOpt, "docker-opt", nil, "Specify arbitrary flags to pass to the Docker daemon. (format: key=value)") @@ -494,6 +509,114 @@ func getNetwork(driverName string, options *run.CommandOptions) string { return n } +// normalizeAndValidateIPFamily sets defaults, validates CIDRs, and adds +// guardrails for IPv6/dual-stack on Docker. +func normalizeAndValidateIPFamily(cc *config.ClusterConfig) { + fam := strings.ToLower(strings.TrimSpace(cc.KubernetesConfig.IPFamily)) + switch fam { + case "", "ipv4", "ipv6", "dual": + // ok + default: + exit.Message(reason.Usage, + "Invalid --ip-family {{.fam}}. Must be one of: ipv4, ipv6, dual.", + out.V{"fam": cc.KubernetesConfig.IPFamily}) + } + + // Default to ipv4 to keep existing behavior for old configs. + if fam == "" { + fam = "ipv4" + cc.KubernetesConfig.IPFamily = fam + } + + // IPv6-capable families: ensure v6 defaults. + if fam != "ipv4" { + if cc.KubernetesConfig.ServiceCIDRv6 == "" { + cc.KubernetesConfig.ServiceCIDRv6 = constants.DefaultServiceCIDRv6 + } + if cc.KubernetesConfig.PodCIDRv6 == "" { + cc.KubernetesConfig.PodCIDRv6 = constants.DefaultPodCIDRv6 + } + } + + // For ipv4 or dual-stack, keep the usual IPv4 pod CIDR default if none was set. + if fam != "ipv6" && cc.KubernetesConfig.PodCIDR == "" { + cc.KubernetesConfig.PodCIDR = cni.DefaultPodCIDR + } + + // --- CIDR / address validation --- + + // Helper: ensure a CIDR is v4 or v6 as expected. + checkCIDR := func(flag string, cidr string, wantV6 bool) { + ip, _, err := net.ParseCIDR(cidr) + if err != nil { + exit.Message(reason.Usage, "{{.flag}} must be a valid CIDR: {{.e}}", + out.V{"flag": flag, "e": err}) + } + isV4 := ip.To4() != nil + if wantV6 && isV4 { + exit.Message(reason.Usage, "{{.flag}} must be a valid IPv6 CIDR: {{.e}}", + out.V{"flag": flag, "e": err}) + } + if !wantV6 && !isV4 { + exit.Message(reason.Usage, "{{.flag}} must be a valid IPv4 CIDR: {{.e}}", + out.V{"flag": flag, "e": err}) + } + } + + // IPv6-only CIDR flags. + if cidr := cc.Subnetv6; cidr != "" { + checkCIDR("--subnet-v6", cidr, true) + } + if cidr := cc.KubernetesConfig.ServiceCIDRv6; cidr != "" { + checkCIDR("--service-cluster-ip-range-v6", cidr, true) + } + if cidr := cc.KubernetesConfig.PodCIDRv6; cidr != "" { + checkCIDR("--pod-cidr-v6", cidr, true) + } + + // IPv4-only pod CIDR. + if cidr := cc.KubernetesConfig.PodCIDR; cidr != "" { + checkCIDR("--pod-cidr", cidr, false) + } + + // validate static IPv6 if provided + if s := cc.StaticIPv6; s != "" { + ip := net.ParseIP(s) + if ip == nil || ip.To4() != nil { + exit.Message(reason.Usage, "--static-ipv6 must be a valid IPv6 address") + } + } + + // Docker driver guardrails: Linux daemon for IPv6, warn on Desktop. + if driver.IsDocker(cc.Driver) && fam != "ipv4" { + // Desktop vs Linux daemon hint (we can't reliably detect IPv6 enabled here). + si, err := oci.CachedDaemonInfo(cc.Driver) + if err != nil { + si, err = oci.DaemonInfo(cc.Driver) + if err != nil { + exit.Message(reason.Usage, "Failed to query Docker daemon info: {{.e}}", out.V{"e": err}) + } + } + + // On non-Linux hosts we assume Docker Desktop; on Linux it's a native Engine + // unless DockerOS explicitly says "Docker Desktop". + isLinuxDaemon := runtime.GOOS == "linux" && si.DockerOS != "Docker Desktop" + if !isLinuxDaemon { + if fam == "ipv6" { + exit.Message(reason.Usage, + "IPv6 clusters require a Linux Docker daemon (Desktop is not supported). "+ + "Use a Linux/WSL2 daemon or set --ip-family=ipv4.") + } + out.WarningT("Dual-stack on Docker Desktop may be limited. For full IPv6 support, use a Linux Docker daemon.") + } + + // Friendly reminder about enabling daemon IPv6 (actual failure will occur during + // network create if the daemon/network really blocks IPv6 bridge networks). + out.Styled(style.Tip, + "If your Docker daemon/network blocks IPv6 bridge networks, enable IPv6 in /etc/docker/daemon.json and restart:\n {\"ipv6\": true, \"fixed-cidr-v6\": \"fd00:55:66::/64\"}") + } +} + func validateQemuNetwork(n string) string { switch n { case "socket_vmnet": @@ -576,6 +699,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str KicBaseImage: viper.GetString(kicBaseImage), Network: getNetwork(drvName, options), Subnet: viper.GetString(subnet), + Subnetv6: viper.GetString(subnetv6), Memory: getMemorySize(cmd, drvName), CPUs: getCPUCount(drvName), DiskSize: getDiskSize(), @@ -590,6 +714,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str InsecureRegistry: insecureRegistry, RegistryMirror: registryMirror, HostOnlyCIDR: viper.GetString(hostOnlyCIDR), + HostOnlyCIDRv6: viper.GetString(hostOnlyCIDRv6), HypervVirtualSwitch: viper.GetString(hypervVirtualSwitch), HypervUseExternalSwitch: viper.GetBool(hypervUseExternalSwitch), HypervExternalAdapter: viper.GetString(hypervExternalAdapter), @@ -631,6 +756,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str SocketVMnetClientPath: detect.SocketVMNetClientPath(), SocketVMnetPath: detect.SocketVMNetPath(), StaticIP: viper.GetString(staticIP), + StaticIPv6: viper.GetString(staticIPv6), KubernetesConfig: config.KubernetesConfig{ KubernetesVersion: k8sVersion, ClusterName: ClusterFlagValue(), @@ -644,6 +770,10 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str CRISocket: viper.GetString(criSocket), NetworkPlugin: chosenNetworkPlugin, ServiceCIDR: viper.GetString(serviceCIDR), + ServiceCIDRv6: viper.GetString(serviceCIDRv6), + PodCIDR: viper.GetString(podCIDR), + PodCIDRv6: viper.GetString(podCIDRv6), + IPFamily: viper.GetString(ipFamily), ImageRepository: getRepository(cmd, k8sVersion), ExtraOptions: getExtraOptions(), ShouldLoadCachedImages: viper.GetBool(cacheImages), @@ -697,6 +827,8 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str } } + normalizeAndValidateIPFamily(&cc) + return cc } @@ -831,11 +963,15 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC updateStringFromFlag(cmd, &cc.MinikubeISO, isoURL) updateStringFromFlag(cmd, &cc.KicBaseImage, kicBaseImage) updateStringFromFlag(cmd, &cc.Network, network) + updateStringFromFlag(cmd, &cc.Subnetv6, subnetv6) + updateStringFromFlag(cmd, &cc.KubernetesConfig.PodCIDR, podCIDR) + updateStringFromFlag(cmd, &cc.KubernetesConfig.PodCIDRv6, podCIDRv6) updateStringFromFlag(cmd, &cc.HyperkitVpnKitSock, vpnkitSock) updateStringSliceFromFlag(cmd, &cc.HyperkitVSockPorts, vsockPorts) updateStringSliceFromFlag(cmd, &cc.NFSShare, nfsShare) updateStringFromFlag(cmd, &cc.NFSSharesRoot, nfsSharesRoot) updateStringFromFlag(cmd, &cc.HostOnlyCIDR, hostOnlyCIDR) + updateStringFromFlag(cmd, &cc.HostOnlyCIDRv6, hostOnlyCIDRv6) updateStringFromFlag(cmd, &cc.HypervVirtualSwitch, hypervVirtualSwitch) updateBoolFromFlag(cmd, &cc.HypervUseExternalSwitch, hypervUseExternalSwitch) updateStringFromFlag(cmd, &cc.HypervExternalAdapter, hypervExternalAdapter) @@ -853,6 +989,7 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC updateDurationFromFlag(cmd, &cc.StartHostTimeout, waitTimeout) updateStringSliceFromFlag(cmd, &cc.ExposedPorts, ports) updateStringFromFlag(cmd, &cc.SSHIPAddress, sshIPAddress) + updateStringFromFlag(cmd, &cc.StaticIPv6, staticIPv6) updateStringFromFlag(cmd, &cc.SSHUser, sshSSHUser) updateStringFromFlag(cmd, &cc.SSHKey, sshSSHKey) updateIntFromFlag(cmd, &cc.SSHPort, sshSSHPort) @@ -865,6 +1002,8 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC updateStringFromFlag(cmd, &cc.KubernetesConfig.CRISocket, criSocket) updateStringFromFlag(cmd, &cc.KubernetesConfig.NetworkPlugin, networkPlugin) updateStringFromFlag(cmd, &cc.KubernetesConfig.ServiceCIDR, serviceCIDR) + updateStringFromFlag(cmd, &cc.KubernetesConfig.ServiceCIDRv6, serviceCIDRv6) + updateStringFromFlag(cmd, &cc.KubernetesConfig.IPFamily, ipFamily) updateBoolFromFlag(cmd, &cc.KubernetesConfig.ShouldLoadCachedImages, cacheImages) updateDurationFromFlag(cmd, &cc.CertExpiration, certExpiration) updateStringFromFlag(cmd, &cc.MountString, mountString) @@ -921,7 +1060,7 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC if cc.ScheduledStop != nil && time.Until(time.Unix(cc.ScheduledStop.InitiationTime, 0).Add(cc.ScheduledStop.Duration)) <= 0 { cc.ScheduledStop = nil } - + normalizeAndValidateIPFamily(&cc) return cc } diff --git a/pkg/minikube/config/types.go b/pkg/minikube/config/types.go index 1077ac3ca2..7372ecdd3e 100644 --- a/pkg/minikube/config/types.go +++ b/pkg/minikube/config/types.go @@ -50,6 +50,7 @@ type ClusterConfig struct { InsecureRegistry []string RegistryMirror []string HostOnlyCIDR string // Only used by the virtualbox driver + HostOnlyCIDRv6 string // IPv6 CIDR for the virtualbox driver HypervVirtualSwitch string HypervUseExternalSwitch bool HypervExternalAdapter string @@ -85,6 +86,7 @@ type ClusterConfig struct { ListenAddress string // Only used by the docker and podman driver Network string // only used by docker driver Subnet string // only used by the docker and podman driver + Subnetv6 string // IPv6 subnet for docker and podman driver MultiNodeRequested bool ExtraDisks int // currently only implemented for hyperkit and kvm2 CertExpiration time.Duration @@ -105,6 +107,7 @@ type ClusterConfig struct { SocketVMnetClientPath string SocketVMnetPath string StaticIP string + StaticIPv6 string // Static IPv6 address for the cluster SSHAuthSock string SSHAgentPID int GPUs string @@ -126,6 +129,10 @@ type KubernetesConfig struct { NetworkPlugin string FeatureGates string // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ ServiceCIDR string // the subnet which Kubernetes services will be deployed to + ServiceCIDRv6 string // the IPv6 subnet which Kubernetes services will be deployed to + PodCIDR string // the IPv4 subnet which Kubernetes pods will be deployed to + PodCIDRv6 string // the IPv6 subnet which Kubernetes pods will be deployed to + IPFamily string // IP family mode: ipv4, ipv6, or dual ImageRepository string LoadBalancerStartIP string // currently only used by MetalLB addon LoadBalancerEndIP string // currently only used by MetalLB addon @@ -143,6 +150,7 @@ type KubernetesConfig struct { type Node struct { Name string IP string + IPv6 string // IPv6 address of the node Port int KubernetesVersion string ContainerRuntime string diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index 90e19ee411..a75eaea506 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -80,6 +80,10 @@ const ( ClusterDNSDomain = "cluster.local" // DefaultServiceCIDR is The CIDR to be used for service cluster IPs DefaultServiceCIDR = "10.96.0.0/12" + // DefaultServiceCIDRv6 is The IPv6 CIDR to be used for service cluster IPs + DefaultServiceCIDRv6 = "fd00::/108" + // DefaultPodCIDRv6 is The IPv6 CIDR to be used for pod IPs + DefaultPodCIDRv6 = "fd01::/64" // HostAlias is a DNS alias to the container/VM host IP HostAlias = "host.minikube.internal" // ControlPlaneAlias is a DNS alias pointing to the apiserver frontend