Fix bgp_attach_ip_to_interface not applied to Service VIPs (#1744)

shouldAddServiceIP() checks BGPAttachIPToInterface on a per-service
config struct. NewInstance() builds that struct fresh for each Service,
copying over most fields from the global config, missing this one. As
a result the flag has no effect regardless of its value, and BGP-mode
Service VIPs are never bound to the interface.

Copy the field at both construction sites where it's built, and add
a regression test covering the propagation.

Signed-off-by: Justin Cichra <jrcichra@yahoo.com>
This commit is contained in:
Justin Cichra
2026-08-30 18:20:08 -04:00
committed by GitHub
parent b7f3379514
commit 2b126dceed
3 changed files with 65 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Propagate `bgp_attach_ip_to_interface` into per-service config so it attaches BGP-mode Service VIPs to the interface as configured.
- Add a configurable kube-vip instance name and use it to isolate internal nftables egress tables, persist table ownership on Services, and migrate per-Service chains without affecting other deployments. Fixes #1634.
- Retry on 403 Forbidden and 401 Unauthorized in `ServicesWatcher` at startup with exponential backoff. Fixes #1464.
- Reintroduce BGP config via node annotations. Fixes #1488.

View File

@@ -203,6 +203,7 @@ func NewInstance(ctx context.Context, svc *v1.Service, config *kubevip.Config,
SingleNode: true,
EnableARP: config.EnableARP,
EnableBGP: config.EnableBGP,
BGPAttachIPToInterface: config.BGPAttachIPToInterface,
VIPSubnet: subnet,
EnableRoutingTable: config.EnableRoutingTable,
RoutingTableID: config.RoutingTableID,
@@ -269,6 +270,7 @@ func NewInstance(ctx context.Context, svc *v1.Service, config *kubevip.Config,
SingleNode: true,
EnableARP: config.EnableARP,
EnableBGP: config.EnableBGP,
BGPAttachIPToInterface: config.BGPAttachIPToInterface,
VIPSubnet: config.VIPSubnet,
EnableRoutingTable: config.EnableRoutingTable,
RoutingTableID: config.RoutingTableID,

View File

@@ -0,0 +1,62 @@
package instance_test
import (
"context"
"sync"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/kube-vip/kube-vip/pkg/arp"
"github.com/kube-vip/kube-vip/pkg/instance"
"github.com/kube-vip/kube-vip/pkg/kubevip"
"github.com/kube-vip/kube-vip/pkg/networkinterface"
"github.com/kube-vip/kube-vip/pkg/route"
)
func TestNewInstance_PropagatesBGPAttachIPToInterface(t *testing.T) {
tests := []struct {
name string
attach bool
}{
{name: "attach enabled is propagated", attach: true},
{name: "attach disabled is propagated", attach: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
globalConfig := &kubevip.Config{
Interface: "lo",
VIPSubnet: "32",
EnableBGP: true,
BGPAttachIPToInterface: tt.attach,
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svc",
Namespace: "default",
Annotations: map[string]string{
kubevip.LoadbalancerIPAnnotation: "10.0.1.2",
},
},
}
inst, err := instance.NewInstance(context.Background(), svc, globalConfig,
networkinterface.NewManager(), arp.NewManager(globalConfig), route.NewManager(),
nil, &sync.WaitGroup{})
if err != nil {
t.Fatalf("NewInstance() error = %v", err)
}
if len(inst.VIPConfigs) != 1 {
t.Fatalf("VIPConfigs len = %d, want 1", len(inst.VIPConfigs))
}
if got := inst.VIPConfigs[0].BGPAttachIPToInterface; got != tt.attach {
t.Fatalf("BGPAttachIPToInterface = %t, want %t", got, tt.attach)
}
})
}
}