From b440187e2d4f61886972436b12b1e15f98e1c00d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 15 Jul 2026 13:19:01 +0100 Subject: [PATCH] =?UTF-8?q?Adds=20an=20opt-in=20flag/env=20var=20(=C2=A0--?= =?UTF-8?q?allowInterfaceNotUp=C2=A0=20/=20=C2=A0vip=5Fallow=5Finterface?= =?UTF-8?q?=5Fnot=5Fup=C2=A0)=20that=20lets=20kube-vip=20continue=20operat?= =?UTF-8?q?ing=20even=20when=20its=20bind=20interface=20is=20not=20up.=20T?= =?UTF-8?q?his=20supports=20assigning=20the=20VIP=20even=20when=20the=20in?= =?UTF-8?q?terface=20is=20down.=20Disabled=20by=20default;=20no=20change?= =?UTF-8?q?=20to=20existing=20behavior.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gabriel --- cmd/kube-vip.go | 1 + pkg/kubevip/config_environment.go | 10 ++++++++++ pkg/kubevip/config_envvar.go | 3 +++ pkg/kubevip/config_generator.go | 11 +++++++++++ pkg/kubevip/config_manager.go | 17 ++++++++++++++--- pkg/kubevip/config_types.go | 3 +++ 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/cmd/kube-vip.go b/cmd/kube-vip.go index 503dc421..4f86abab 100644 --- a/cmd/kube-vip.go +++ b/cmd/kube-vip.go @@ -60,6 +60,7 @@ func init() { // Basic flags kubeVipCmd.PersistentFlags().StringVar(&initConfig.Interface, "interface", "", "Name of the interface to bind to") kubeVipCmd.PersistentFlags().StringVar(&initConfig.ServicesInterface, "serviceInterface", "", "Name of the interface to bind to (for services)") + kubeVipCmd.PersistentFlags().BoolVar(&initConfig.AllowInterfaceNotUp, "allowInterfaceNotUp", false, "Allow kube-vip to start even if the interface is not up") kubeVipCmd.PersistentFlags().StringVar(&initConfig.VIP, "vip", "", "The Virtual IP address") kubeVipCmd.PersistentFlags().StringVar(&initConfig.VIPSubnet, "vipSubnet", "", "The Virtual IP address subnet e.g. /32 /24 /8 etc.. (Default to 32 for IPv4 and 128 for IPv6)") kubeVipCmd.PersistentFlags().StringVar(&initConfig.NodeName, "nodeName", "", "Name to be used for lease holder. Must be unique for each node/instance") diff --git a/pkg/kubevip/config_environment.go b/pkg/kubevip/config_environment.go index 1c8a56a5..ea88ea31 100644 --- a/pkg/kubevip/config_environment.go +++ b/pkg/kubevip/config_environment.go @@ -70,6 +70,16 @@ func ParseEnvironment(c *Config) error { c.ServicesInterface = env } + // Tolerate a down interface + env = os.Getenv(vipAllowInterfaceNotUp) + if env != "" { + b, err := strconv.ParseBool(env) + if err != nil { + return err + } + c.AllowInterfaceNotUp = b + } + // Find Kubernetes Leader Election configuration env = os.Getenv(vipLeaderElection) if env != "" { diff --git a/pkg/kubevip/config_envvar.go b/pkg/kubevip/config_envvar.go index a549bf30..3c4108b2 100644 --- a/pkg/kubevip/config_envvar.go +++ b/pkg/kubevip/config_envvar.go @@ -48,6 +48,9 @@ const ( // vipServicesInterface - defines the interface that the service vips should bind too vipServicesInterface = "vip_servicesinterface" + // vipAllowInterfaceNotUp - defines if kube-vip should tolerate a down interface + vipAllowInterfaceNotUp = "vip_allow_interface_not_up" + // vipSubnet - defines the subnet that the vip will use vipSubnet = "vip_subnet" diff --git a/pkg/kubevip/config_generator.go b/pkg/kubevip/config_generator.go index 344bab32..b9384879 100644 --- a/pkg/kubevip/config_generator.go +++ b/pkg/kubevip/config_generator.go @@ -233,6 +233,17 @@ func generatePodSpec(c *Config, image, imageVersion string, inCluster bool) (*co newEnvironment = append(newEnvironment, svcInterface...) } + // Tolerate a down interface + if c.AllowInterfaceNotUp { + allowIface := []corev1.EnvVar{ + { + Name: vipAllowInterfaceNotUp, + Value: strconv.FormatBool(c.AllowInterfaceNotUp), + }, + } + newEnvironment = append(newEnvironment, allowIface...) + } + // If a subnet is required for the VIP if c.VIPSubnet != "" { // build environment variables diff --git a/pkg/kubevip/config_manager.go b/pkg/kubevip/config_manager.go index ed130cf0..89c7b5d3 100644 --- a/pkg/kubevip/config_manager.go +++ b/pkg/kubevip/config_manager.go @@ -1,6 +1,7 @@ package kubevip import ( + "errors" "fmt" log "log/slog" @@ -12,6 +13,8 @@ const ( Auto = "auto" ) +var ErrInterfaceNotUp = errors.New("interface is not up") + func (c *Config) CheckSubnetExists() error { if c.VIPSubnet == "" && c.VIP != "" && c.Address == "" { return fmt.Errorf("vip_subnet must be set if using vip_address instead of address environment variable") @@ -23,13 +26,21 @@ func (c *Config) CheckSubnetExists() error { func (c *Config) CheckInterface() error { if c.Interface != "" { if err := isValidInterface(c.Interface); err != nil { - return fmt.Errorf("%s is not valid interface, reason: %w", c.Interface, err) + if errors.Is(err, ErrInterfaceNotUp) && c.AllowInterfaceNotUp { + log.Warn("interface is not up, continuing as allowInterfaceNotUp is set", "interface", c.Interface) + } else { + return fmt.Errorf("%s is not valid interface, reason: %w", c.Interface, err) + } } } if c.ServicesInterface != "" { if err := isValidInterface(c.ServicesInterface); err != nil { - return fmt.Errorf("%s is not valid interface, reason: %w", c.ServicesInterface, err) + if errors.Is(err, ErrInterfaceNotUp) && c.AllowInterfaceNotUp { + log.Warn("interface is not up, continuing as allowInterfaceNotUp is set", "interface", c.ServicesInterface) + } else { + return fmt.Errorf("%s is not valid interface, reason: %w", c.ServicesInterface, err) + } } } @@ -58,7 +69,7 @@ func isValidInterface(iface string) error { iface, ) } else if attrs.OperState != netlink.OperUp { - return fmt.Errorf("%s is not up", iface) + return fmt.Errorf("%s %w", iface, ErrInterfaceNotUp) } return nil diff --git a/pkg/kubevip/config_types.go b/pkg/kubevip/config_types.go index 6f4efb4a..c648bf85 100644 --- a/pkg/kubevip/config_types.go +++ b/pkg/kubevip/config_types.go @@ -112,6 +112,9 @@ type Config struct { // ServicesInterface is the network interface to bind to for services (optional) ServicesInterface string `yaml:"servicesInterface,omitempty"` + // AllowInterfaceNotUp allows kube-vip to start even when the interface is not up + AllowInterfaceNotUp bool `yaml:"allowInterfaceNotUp,omitempty"` + // EnableLoadBalancer, provides the flexibility to make the load-balancer optional EnableLoadBalancer bool `yaml:"enableLoadBalancer"`