diff --git a/cmd/kube-vip.go b/cmd/kube-vip.go index 1f6797bb..2cbbfc17 100644 --- a/cmd/kube-vip.go +++ b/cmd/kube-vip.go @@ -92,6 +92,7 @@ func init() { // BGP flags kubeVipCmd.PersistentFlags().BoolVar(&initConfig.EnableBGP, "bgp", false, "This will enable BGP support within kube-vip") + kubeVipCmd.PersistentFlags().BoolVar(&initConfig.BGPAttachIPToInterface, "bgpAttachIPToInterface", false, "Assign BGP service VIPs to the configured interface") kubeVipCmd.PersistentFlags().StringVar(&initConfig.BGPConfig.RouterID, "bgpRouterID", "", "The routerID for the bgp server") kubeVipCmd.PersistentFlags().StringVar(&initConfig.BGPConfig.SourceIF, "sourceIF", "", "The source interface for bgp peering (not to be used with sourceIP)") kubeVipCmd.PersistentFlags().StringVar(&initConfig.BGPConfig.SourceIP, "sourceIP", "", "The source address for bgp peering (not to be used with sourceIF)") diff --git a/pkg/cluster/service.go b/pkg/cluster/service.go index df1c95cb..ca13ebac 100644 --- a/pkg/cluster/service.go +++ b/pkg/cluster/service.go @@ -411,7 +411,7 @@ func (cluster *Cluster) StartLoadBalancerService(ctx context.Context, c *kubevip } } - if !c.EnableRoutingTable && !c.EnableBGP && !c.EnableWireguard { + if shouldAddServiceIP(c) { // Normal VIP addition, use skipDAD=false for normal DAD process // Note: When WireGuard is enabled, the VIP is added to the tunnel interface // instead of lo, so we skip adding it here. @@ -480,6 +480,10 @@ func (cluster *Cluster) StartLoadBalancerService(ctx context.Context, c *kubevip return nil } +func shouldAddServiceIP(c *kubevip.Config) bool { + return !c.EnableRoutingTable && (!c.EnableBGP || c.BGPAttachIPToInterface) && !c.EnableWireguard +} + // Layer2Update, handles the creation of the func (cluster *Cluster) layer2Update(ctx context.Context, network vip.Network, c *kubevip.Config) { var ndp *vip.NdpResponder diff --git a/pkg/cluster/service_config_test.go b/pkg/cluster/service_config_test.go new file mode 100644 index 00000000..8f9ab2e1 --- /dev/null +++ b/pkg/cluster/service_config_test.go @@ -0,0 +1,55 @@ +package cluster + +import ( + "testing" + + "github.com/kube-vip/kube-vip/pkg/kubevip" +) + +func TestShouldAddServiceIP(t *testing.T) { + tests := []struct { + name string + config *kubevip.Config + want bool + }{ + { + name: "BGP default does not attach IP", + config: &kubevip.Config{EnableBGP: true}, + want: false, + }, + { + name: "BGP opt-in attaches IP", + config: &kubevip.Config{ + EnableBGP: true, + BGPAttachIPToInterface: true, + }, + want: true, + }, + { + name: "routing table takes precedence", + config: &kubevip.Config{ + EnableBGP: true, + BGPAttachIPToInterface: true, + EnableRoutingTable: true, + }, + want: false, + }, + { + name: "WireGuard takes precedence", + config: &kubevip.Config{ + EnableBGP: true, + BGPAttachIPToInterface: true, + EnableWireguard: true, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := shouldAddServiceIP(tt.config); got != tt.want { + t.Fatalf("shouldAddServiceIP() = %t, want %t", got, tt.want) + } + }) + } +} diff --git a/pkg/kubevip/config_environment.go b/pkg/kubevip/config_environment.go index 2abed78d..f685d7f1 100644 --- a/pkg/kubevip/config_environment.go +++ b/pkg/kubevip/config_environment.go @@ -456,6 +456,15 @@ func ParseEnvironment(c *Config) error { c.EnableBGP = b } + env = os.Getenv(bgpAttachIPToInterface) + if env != "" { + b, err := strconv.ParseBool(env) + if err != nil { + return err + } + c.BGPAttachIPToInterface = b + } + // BGP Router interface determines an interface that we can use to find an address for env = os.Getenv(bgpRouterInterface) if env != "" { @@ -878,6 +887,9 @@ func mergeConfigValues(baseConfig, fileConfig *Config) { if !baseConfig.EnableBGP && fileConfig.EnableBGP { baseConfig.EnableBGP = fileConfig.EnableBGP } + if !baseConfig.BGPAttachIPToInterface && fileConfig.BGPAttachIPToInterface { + baseConfig.BGPAttachIPToInterface = fileConfig.BGPAttachIPToInterface + } if !baseConfig.EnableWireguard && fileConfig.EnableWireguard { baseConfig.EnableWireguard = fileConfig.EnableWireguard } diff --git a/pkg/kubevip/config_envvar.go b/pkg/kubevip/config_envvar.go index a17d40c6..f4935d1a 100644 --- a/pkg/kubevip/config_envvar.go +++ b/pkg/kubevip/config_envvar.go @@ -100,6 +100,8 @@ const ( // bgpEnable defines if BGP should be enabled bgpEnable = "bgp_enable" + // bgpAttachIPToInterface defines if BGP service VIPs should be assigned to the configured interface + bgpAttachIPToInterface = "bgp_attach_ip_to_interface" // bgpRouterID defines the routerID for the BGP server bgpRouterID = "bgp_routerid" // bgpRouterInterface defines the interface that we can find the address for diff --git a/pkg/kubevip/config_generator.go b/pkg/kubevip/config_generator.go index 0de06194..f08c914f 100644 --- a/pkg/kubevip/config_generator.go +++ b/pkg/kubevip/config_generator.go @@ -476,6 +476,12 @@ func generatePodSpec(c *Config, image, imageVersion string, inCluster bool) (*co Value: strconv.FormatBool(c.EnableBGP), }, } + if c.BGPAttachIPToInterface { + bgp = append(bgp, corev1.EnvVar{ + Name: bgpAttachIPToInterface, + Value: strconv.FormatBool(c.BGPAttachIPToInterface), + }) + } newEnvironment = append(newEnvironment, bgp...) } diff --git a/pkg/kubevip/config_generator_test.go b/pkg/kubevip/config_generator_test.go index 73324759..bcb82c8d 100644 --- a/pkg/kubevip/config_generator_test.go +++ b/pkg/kubevip/config_generator_test.go @@ -54,6 +54,35 @@ func TestParseEnvironmentInstanceName(t *testing.T) { } } +func TestParseEnvironmentBGPAttachIPToInterface(t *testing.T) { + t.Setenv(bgpAttachIPToInterface, "true") + + config := &Config{} + if err := ParseEnvironment(config); err != nil { + t.Fatalf("ParseEnvironment() error = %v", err) + } + if !config.BGPAttachIPToInterface { + t.Fatal("BGPAttachIPToInterface = false, want true") + } +} + +func TestGeneratePodSpecBGPAttachIPToInterface(t *testing.T) { + pod, err := generatePodSpec(&Config{ + EnableBGP: true, + BGPAttachIPToInterface: true, + }, "ghcr.io/kube-vip/kube-vip", "v0.0.0", true) + if err != nil { + t.Fatalf("generatePodSpec() error = %v", err) + } + + for _, env := range pod.Spec.Containers[0].Env { + if env.Name == bgpAttachIPToInterface && env.Value == "true" { + return + } + } + t.Fatalf("%s=true is missing from generated pod environment", bgpAttachIPToInterface) +} + func TestGeneratePodSpecInstanceName(t *testing.T) { tests := []struct { name string diff --git a/pkg/kubevip/config_types.go b/pkg/kubevip/config_types.go index 4e039d63..943c2a81 100644 --- a/pkg/kubevip/config_types.go +++ b/pkg/kubevip/config_types.go @@ -11,6 +11,9 @@ type Config struct { // EnableBGP, will use BGP to advertise the VIP address EnableBGP bool `yaml:"enableBGP"` + // BGPAttachIPToInterface assigns BGP-advertised service VIPs to the configured interface + BGPAttachIPToInterface bool `yaml:"bgpAttachIPToInterface"` + // EnableWireguard, will use wireguard to advertise the VIP address EnableWireguard bool `yaml:"enableWireguard"`