Skip to content

Commit 33d02d9

Browse files
committed
feat(services): support ipMode Proxy for LoadBalancer ingresses
This implements support for KEP-1860. When a LoadBalancer ingress has ipMode set to 'Proxy', kube-router will skip adding the IP to the local IPVS table and will not hijack the traffic. If ipMode is 'VIP' or unset, the current behavior is maintained. Fixes #2014
1 parent 9904bd3 commit 33d02d9

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

pkg/controllers/proxy/network_services_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ func (nsc *NetworkServicesController) buildServicesInfo() serviceInfoMap {
915915
}
916916
copy(svcInfo.clusterIPs, svc.Spec.ClusterIPs)
917917
for _, lbIngress := range svc.Status.LoadBalancer.Ingress {
918-
if len(lbIngress.IP) > 0 {
918+
if len(lbIngress.IP) > 0 && (lbIngress.IPMode == nil || *lbIngress.IPMode != v1.LoadBalancerIPModeProxy) {
919919
svcInfo.loadBalancerIPs = append(svcInfo.loadBalancerIPs, lbIngress.IP)
920920
}
921921
}

pkg/controllers/proxy/network_services_controller_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func TestNetworkServicesController_syncIpvsServices(t *testing.T) {
4747
// Default traffic policies used in tests
4848
intTrafficPolicyCluster := v1core.ServiceInternalTrafficPolicyCluster
4949
extTrafficPolicyCluster := v1core.ServiceExternalTrafficPolicyCluster
50+
lbIPModeProxy := v1core.LoadBalancerIPModeProxy
51+
lbIPModeVIP := v1core.LoadBalancerIPModeVIP
5052

5153
tests := []struct {
5254
name string
@@ -184,6 +186,69 @@ func TestNetworkServicesController_syncIpvsServices(t *testing.T) {
184186
"2.2.2.2:6:8080:false:rr",
185187
},
186188
},
189+
{
190+
name: "service with loadbalancer IPs where ipMode is Proxy (should be skipped)",
191+
service: &v1core.Service{
192+
ObjectMeta: metav1.ObjectMeta{Name: "svc-1"},
193+
Spec: v1core.ServiceSpec{
194+
Type: "LoadBalancer",
195+
ClusterIP: "10.0.0.1",
196+
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
197+
InternalTrafficPolicy: &intTrafficPolicyCluster,
198+
ExternalTrafficPolicy: extTrafficPolicyCluster,
199+
Ports: []v1core.ServicePort{
200+
{Name: "port-1", Protocol: "TCP", Port: 8080},
201+
},
202+
},
203+
Status: v1core.ServiceStatus{
204+
LoadBalancer: v1core.LoadBalancerStatus{
205+
Ingress: []v1core.LoadBalancerIngress{
206+
{IP: "10.255.0.1", IPMode: &lbIPModeProxy},
207+
{IP: "10.255.0.2", IPMode: &lbIPModeProxy},
208+
},
209+
},
210+
},
211+
},
212+
endpointSlice: &discoveryv1.EndpointSlice{},
213+
expectedIPs: []string{"10.0.0.1", "1.1.1.1", "2.2.2.2"},
214+
expectedServices: []string{
215+
"10.0.0.1:6:8080:false:rr",
216+
"1.1.1.1:6:8080:false:rr",
217+
"2.2.2.2:6:8080:false:rr",
218+
},
219+
},
220+
{
221+
name: "service with mixed loadbalancer IPs (one Proxy, one VIP)",
222+
service: &v1core.Service{
223+
ObjectMeta: metav1.ObjectMeta{Name: "svc-1"},
224+
Spec: v1core.ServiceSpec{
225+
Type: "LoadBalancer",
226+
ClusterIP: "10.0.0.1",
227+
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
228+
InternalTrafficPolicy: &intTrafficPolicyCluster,
229+
ExternalTrafficPolicy: extTrafficPolicyCluster,
230+
Ports: []v1core.ServicePort{
231+
{Name: "port-1", Protocol: "TCP", Port: 8080},
232+
},
233+
},
234+
Status: v1core.ServiceStatus{
235+
LoadBalancer: v1core.LoadBalancerStatus{
236+
Ingress: []v1core.LoadBalancerIngress{
237+
{IP: "10.255.0.1", IPMode: &lbIPModeProxy},
238+
{IP: "10.255.0.2", IPMode: &lbIPModeVIP},
239+
},
240+
},
241+
},
242+
},
243+
endpointSlice: &discoveryv1.EndpointSlice{},
244+
expectedIPs: []string{"10.0.0.1", "1.1.1.1", "2.2.2.2", "10.255.0.2"},
245+
expectedServices: []string{
246+
"10.0.0.1:6:8080:false:rr",
247+
"1.1.1.1:6:8080:false:rr",
248+
"2.2.2.2:6:8080:false:rr",
249+
"10.255.0.2:6:8080:false:rr",
250+
},
251+
},
187252
{
188253
name: "node has endpoints for service",
189254
service: &v1core.Service{

pkg/controllers/routing/ecmp_vip.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ func (nrc *NetworkRoutingController) getLoadBalancerIPs(svc *v1core.Service) []s
341341
// skip headless services
342342
if !utils.ClusterIPIsNoneOrBlank(svc.Spec.ClusterIP) {
343343
for _, lbIngress := range svc.Status.LoadBalancer.Ingress {
344-
if len(lbIngress.IP) > 0 {
344+
if len(lbIngress.IP) > 0 && (lbIngress.IPMode == nil || *lbIngress.IPMode != v1core.LoadBalancerIPModeProxy) {
345345
loadBalancerIPList = append(loadBalancerIPList, lbIngress.IP)
346346
}
347347
}

pkg/controllers/routing/ecmp_vip_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ var (
3232
testClusterExtTrafPol = v1core.ServiceExternalTrafficPolicyCluster
3333
testLocalIntTrafPol = v1core.ServiceInternalTrafficPolicyLocal
3434
testClusterIntTrafPol = v1core.ServiceInternalTrafficPolicyCluster
35+
testIpModeProxy = v1core.LoadBalancerIPModeProxy
36+
testIpModeVIP = v1core.LoadBalancerIPModeVIP
3537
testNodeIPv4 = "10.1.0.1"
3638
testNodeIPv6 = "2001:db8:42:2::1"
3739
)
@@ -901,6 +903,36 @@ func Test_getVIPsForService(t *testing.T) {
901903
},
902904
},
903905
},
906+
{
907+
name: "skip loadbalancer IPs with ipMode Proxy",
908+
nrc: &NetworkRoutingController{
909+
advertiseClusterIP: true,
910+
advertiseExternalIP: true,
911+
advertiseLoadBalancerIP: true,
912+
krNode: &utils.LocalKRNode{
913+
KRNode: utils.KRNode{
914+
PrimaryIP: net.ParseIP(testNodeIPv4),
915+
NodeIPv4Addrs: map[v1core.NodeAddressType][]net.IP{v1core.NodeInternalIP: {net.ParseIP(testNodeIPv4)}},
916+
},
917+
},
918+
},
919+
serviceAdvertisedIPs: []*ServiceAdvertisedIPs{
920+
{
921+
service: getLoadBalancerProxySvc(),
922+
endpoints: getContainsLocalIPv4EPs(),
923+
advertisedIPs: []string{"10.0.0.1", "1.1.1.1"},
924+
withdrawnIPs: []string{},
925+
annotations: nil,
926+
},
927+
{
928+
service: getLoadBalancerMixedIPModeSvc(),
929+
endpoints: getContainsLocalIPv4EPs(),
930+
advertisedIPs: []string{"10.0.0.1", "1.1.1.1", "10.0.255.2"},
931+
withdrawnIPs: []string{},
932+
annotations: nil,
933+
},
934+
},
935+
},
904936
}
905937

906938
for _, test := range tests {
@@ -1031,6 +1063,54 @@ func getLoadBalancerSvc() *v1core.Service {
10311063
}
10321064
}
10331065

1066+
func getLoadBalancerProxySvc() *v1core.Service {
1067+
return &v1core.Service{
1068+
ObjectMeta: metav1.ObjectMeta{
1069+
Name: "svc-loadbalancer-proxy",
1070+
Namespace: "default",
1071+
},
1072+
Spec: v1core.ServiceSpec{
1073+
Type: LoadBalancerST,
1074+
ClusterIP: "10.0.0.1",
1075+
ExternalIPs: []string{"1.1.1.1"},
1076+
InternalTrafficPolicy: &testClusterIntTrafPol,
1077+
ExternalTrafficPolicy: testClusterExtTrafPol,
1078+
},
1079+
Status: v1core.ServiceStatus{
1080+
LoadBalancer: v1core.LoadBalancerStatus{
1081+
Ingress: []v1core.LoadBalancerIngress{
1082+
{IP: "10.0.255.1", IPMode: &testIpModeProxy},
1083+
{IP: "10.0.255.2", IPMode: &testIpModeProxy},
1084+
},
1085+
},
1086+
},
1087+
}
1088+
}
1089+
1090+
func getLoadBalancerMixedIPModeSvc() *v1core.Service {
1091+
return &v1core.Service{
1092+
ObjectMeta: metav1.ObjectMeta{
1093+
Name: "svc-loadbalancer-mixed",
1094+
Namespace: "default",
1095+
},
1096+
Spec: v1core.ServiceSpec{
1097+
Type: LoadBalancerST,
1098+
ClusterIP: "10.0.0.1",
1099+
ExternalIPs: []string{"1.1.1.1"},
1100+
InternalTrafficPolicy: &testClusterIntTrafPol,
1101+
ExternalTrafficPolicy: testClusterExtTrafPol,
1102+
},
1103+
Status: v1core.ServiceStatus{
1104+
LoadBalancer: v1core.LoadBalancerStatus{
1105+
Ingress: []v1core.LoadBalancerIngress{
1106+
{IP: "10.0.255.1", IPMode: &testIpModeProxy},
1107+
{IP: "10.0.255.2", IPMode: &testIpModeVIP},
1108+
},
1109+
},
1110+
},
1111+
}
1112+
}
1113+
10341114
func getContainsLocalIPv4EPs() *discoveryv1.EndpointSlice {
10351115
return &discoveryv1.EndpointSlice{
10361116
Endpoints: []discoveryv1.Endpoint{

0 commit comments

Comments
 (0)