fix: optionally attach BGP service VIPs to interface

Signed-off-by: Marijn Ritzen <marijnritzen@outlook.com>
This commit is contained in:
Marijn Ritzen
2026-07-26 22:04:35 +02:00
parent dfcd6cdf9e
commit fd6006bb8b
8 changed files with 113 additions and 1 deletions

View File

@@ -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)")

View File

@@ -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

View File

@@ -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)
}
})
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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...)
}

View File

@@ -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

View File

@@ -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"`