k8s.io/kubernetes@v1.29.3/pkg/proxy/iptables/number_generated_rules_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package iptables 18 19 import ( 20 "fmt" 21 "testing" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 discovery "k8s.io/api/discovery/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/intstr" 28 iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing" 29 netutils "k8s.io/utils/net" 30 "k8s.io/utils/ptr" 31 ) 32 33 // kube-proxy generates iptables rules to forward traffic from Services to Endpoints 34 // kube-proxy uses iptables-restore to configure the rules atomically, however, 35 // this has the downside that large number of rules take a long time to be processed, 36 // causing disruption. 37 // There are different parameters than influence the number of rules generated: 38 // - ServiceType 39 // - Number of Services 40 // - Number of Endpoints per Service 41 // This test will fail when the number of rules change, so the person 42 // that is modifying the code can have feedback about the performance impact 43 // on their changes. It also runs multiple number of rules test cases to check 44 // if the number of rules grows linearly. 45 func TestNumberIptablesRules(t *testing.T) { 46 testCases := []struct { 47 name string 48 epsFunc func(eps *discovery.EndpointSlice) 49 svcFunc func(svc *v1.Service) 50 services int 51 epPerService int 52 expectedFilterRules int 53 expectedNatRules int 54 }{ 55 { 56 name: "0 Services 0 EndpointsPerService - ClusterIP", 57 services: 0, 58 epPerService: 0, 59 expectedFilterRules: 4, 60 expectedNatRules: 5, 61 }, 62 { 63 name: "1 Services 0 EndpointPerService - ClusterIP", 64 services: 1, 65 epPerService: 0, 66 expectedFilterRules: 5, 67 expectedNatRules: 5, 68 }, 69 { 70 name: "1 Services 1 EndpointPerService - ClusterIP", 71 services: 1, 72 epPerService: 1, 73 expectedFilterRules: 4, 74 expectedNatRules: 10, 75 }, 76 { 77 name: "1 Services 2 EndpointPerService - ClusterIP", 78 services: 1, 79 epPerService: 2, 80 expectedFilterRules: 4, 81 expectedNatRules: 13, 82 }, 83 { 84 name: "1 Services 10 EndpointPerService - ClusterIP", 85 services: 1, 86 epPerService: 10, 87 expectedFilterRules: 4, 88 expectedNatRules: 37, 89 }, 90 { 91 name: "10 Services 0 EndpointsPerService - ClusterIP", 92 services: 10, 93 epPerService: 0, 94 expectedFilterRules: 14, 95 expectedNatRules: 5, 96 }, 97 { 98 name: "10 Services 1 EndpointPerService - ClusterIP", 99 services: 10, 100 epPerService: 1, 101 expectedFilterRules: 4, 102 expectedNatRules: 55, 103 }, 104 { 105 name: "10 Services 2 EndpointPerService - ClusterIP", 106 services: 10, 107 epPerService: 2, 108 expectedFilterRules: 4, 109 expectedNatRules: 85, 110 }, 111 { 112 name: "10 Services 10 EndpointPerService - ClusterIP", 113 services: 10, 114 epPerService: 10, 115 expectedFilterRules: 4, 116 expectedNatRules: 325, 117 }, 118 119 { 120 name: "0 Services 0 EndpointsPerService - LoadBalancer", 121 svcFunc: func(svc *v1.Service) { 122 svc.Spec.Type = v1.ServiceTypeLoadBalancer 123 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 124 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 125 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 126 IP: "1.2.3.4", 127 }} 128 }, 129 services: 0, 130 epPerService: 0, 131 expectedFilterRules: 4, 132 expectedNatRules: 5, 133 }, 134 { 135 name: "1 Services 0 EndpointPerService - LoadBalancer", 136 svcFunc: func(svc *v1.Service) { 137 svc.Spec.Type = v1.ServiceTypeLoadBalancer 138 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 139 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 140 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 141 IP: "1.2.3.4", 142 }} 143 }, 144 services: 1, 145 epPerService: 0, 146 expectedFilterRules: 8, 147 expectedNatRules: 5, 148 }, 149 { 150 name: "1 Services 1 EndpointPerService - LoadBalancer", 151 svcFunc: func(svc *v1.Service) { 152 svc.Spec.Type = v1.ServiceTypeLoadBalancer 153 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 154 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 155 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 156 IP: "1.2.3.4", 157 }} 158 }, 159 services: 1, 160 epPerService: 1, 161 expectedFilterRules: 5, 162 expectedNatRules: 17, 163 }, 164 { 165 name: "1 Services 2 EndpointPerService - LoadBalancer", 166 svcFunc: func(svc *v1.Service) { 167 svc.Spec.Type = v1.ServiceTypeLoadBalancer 168 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 169 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 170 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 171 IP: "1.2.3.4", 172 }} 173 }, 174 services: 1, 175 epPerService: 2, 176 expectedFilterRules: 5, 177 expectedNatRules: 20, 178 }, 179 { 180 name: "1 Services 10 EndpointPerService - LoadBalancer", 181 svcFunc: func(svc *v1.Service) { 182 svc.Spec.Type = v1.ServiceTypeLoadBalancer 183 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 184 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 185 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 186 IP: "1.2.3.4", 187 }} 188 }, 189 services: 1, 190 epPerService: 10, 191 expectedFilterRules: 5, 192 expectedNatRules: 44, 193 }, 194 { 195 name: "10 Services 0 EndpointsPerService - LoadBalancer", 196 svcFunc: func(svc *v1.Service) { 197 svc.Spec.Type = v1.ServiceTypeLoadBalancer 198 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 199 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 200 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 201 IP: "1.2.3.4", 202 }} 203 }, 204 services: 10, 205 epPerService: 0, 206 expectedFilterRules: 44, 207 expectedNatRules: 5, 208 }, 209 { 210 name: "10 Services 1 EndpointPerService - LoadBalancer", 211 svcFunc: func(svc *v1.Service) { 212 svc.Spec.Type = v1.ServiceTypeLoadBalancer 213 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 214 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 215 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 216 IP: "1.2.3.4", 217 }} 218 }, 219 services: 10, 220 epPerService: 1, 221 expectedFilterRules: 14, 222 expectedNatRules: 125, 223 }, 224 { 225 name: "10 Services 2 EndpointPerService - LoadBalancer", 226 svcFunc: func(svc *v1.Service) { 227 svc.Spec.Type = v1.ServiceTypeLoadBalancer 228 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 229 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 230 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 231 IP: "1.2.3.4", 232 }} 233 }, 234 services: 10, 235 epPerService: 2, 236 expectedFilterRules: 14, 237 expectedNatRules: 155, 238 }, 239 { 240 name: "10 Services 10 EndpointPerService - LoadBalancer", 241 svcFunc: func(svc *v1.Service) { 242 svc.Spec.Type = v1.ServiceTypeLoadBalancer 243 svc.Spec.ExternalIPs = []string{"1.2.3.4"} 244 svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"} 245 svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{ 246 IP: "1.2.3.4", 247 }} 248 }, 249 services: 10, 250 epPerService: 10, 251 expectedFilterRules: 14, 252 expectedNatRules: 395, 253 }, 254 } 255 256 for _, test := range testCases { 257 t.Run(test.name, func(t *testing.T) { 258 ipt := iptablestest.NewFake() 259 fp := NewFakeProxier(ipt) 260 261 svcs, eps := generateServiceEndpoints(test.services, test.epPerService, test.epsFunc, test.svcFunc) 262 263 makeServiceMap(fp, svcs...) 264 populateEndpointSlices(fp, eps...) 265 266 now := time.Now() 267 fp.syncProxyRules() 268 t.Logf("time to sync rule: %v", time.Since(now)) 269 t.Logf("iptables data size: %d bytes", fp.iptablesData.Len()) 270 271 if fp.filterRules.Lines() != test.expectedFilterRules { 272 t.Errorf("expected number of Filter rules: %d, got: %d", test.expectedFilterRules, fp.filterRules.Lines()) 273 } 274 275 if fp.natRules.Lines() != test.expectedNatRules { 276 t.Errorf("expected number of NAT rules: %d, got: %d", test.expectedNatRules, fp.natRules.Lines()) 277 } 278 279 // print generated iptables data 280 // t.Logf("Generated rules:\n %s", fp.iptablesData.String()) 281 }) 282 } 283 } 284 285 func Test_generateServiceEndpoints(t *testing.T) { 286 testCases := []struct { 287 name string 288 services int 289 epPerService int 290 svcType v1.ServiceType 291 }{ 292 { 293 name: "Generate 10 Services with 10 Endpoints per Service and LoadBalancer Type", 294 services: 10, 295 epPerService: 10, 296 svcType: v1.ServiceTypeLoadBalancer, 297 }, 298 { 299 name: "Generate 10 Services with 20 Endpoints per Service and NodePort Type", 300 services: 10, 301 epPerService: 20, 302 svcType: v1.ServiceTypeNodePort, 303 }, 304 } 305 306 for _, test := range testCases { 307 t.Run(test.name, func(t *testing.T) { 308 // test the function to mutate services 309 svcFunc := func(svc *v1.Service) { 310 svc.Spec.Type = test.svcType 311 } 312 // test the function to mutate endpoint slices 313 epsFunc := func(eps *discovery.EndpointSlice) { 314 for i := range eps.Endpoints { 315 nodeName := fmt.Sprintf("node-%d", i) 316 eps.Endpoints[i].NodeName = &nodeName 317 } 318 } 319 320 svcs, eps := generateServiceEndpoints(test.services, test.epPerService, epsFunc, svcFunc) 321 322 if len(svcs) != test.services { 323 t.Fatalf("expected %d service, received %d", test.services, len(svcs)) 324 } 325 if len(eps) != test.services { 326 t.Fatalf("expected %d endpoint slice , received %d", test.services, len(eps)) 327 } 328 329 for i := 0; i < test.services; i++ { 330 if svcs[i].Spec.Type != test.svcType { 331 t.Fatalf("expected Service Type %s, got %s", test.svcType, svcs[i].Spec.Type) 332 } 333 if eps[i].ObjectMeta.Labels[discovery.LabelServiceName] != svcs[i].Name { 334 t.Fatalf("endpoint slice reference %s instead of Service %s", eps[i].ObjectMeta.Labels[discovery.LabelServiceName], svcs[i].Name) 335 } 336 if len(eps[i].Endpoints) != test.epPerService { 337 t.Fatalf("expected %d endpoints per slice , received %d", test.epPerService, len(eps[i].Endpoints)) 338 } 339 for j := 0; j < test.epPerService; j++ { 340 nodeName := fmt.Sprintf("node-%d", j) 341 if *eps[i].Endpoints[j].NodeName != nodeName { 342 t.Errorf("Endpoint %d on EndpointSlice %d expected Nodename %s, got %s", j, i, nodeName, *eps[i].Endpoints[j].NodeName) 343 } 344 } 345 } 346 }) 347 } 348 349 } 350 351 // generateServiceEndpoints generate Services with the Type specified and it creates N Endpoints per Service 352 func generateServiceEndpoints(nServices, nEndpoints int, epsFunc func(eps *discovery.EndpointSlice), svcFunc func(svc *v1.Service)) ([]*v1.Service, []*discovery.EndpointSlice) { 353 services := make([]*v1.Service, nServices) 354 endpointSlices := make([]*discovery.EndpointSlice, nServices) 355 356 // base parameters 357 basePort := 80 358 base := netutils.BigForIP(netutils.ParseIPSloppy("10.0.0.1")) 359 360 // generate a base endpoint slice object 361 baseEp := netutils.BigForIP(netutils.ParseIPSloppy("172.16.0.1")) 362 epPort := 8080 363 364 eps := &discovery.EndpointSlice{ 365 ObjectMeta: metav1.ObjectMeta{ 366 Name: "ep", 367 Namespace: "namespace", 368 }, 369 AddressType: discovery.AddressTypeIPv4, 370 Endpoints: []discovery.Endpoint{}, 371 Ports: []discovery.EndpointPort{{ 372 Name: ptr.To(fmt.Sprintf("%d", epPort)), 373 Port: ptr.To(int32(epPort)), 374 Protocol: ptr.To(v1.ProtocolTCP), 375 }}, 376 } 377 378 for j := 0; j < nEndpoints; j++ { 379 ipEp := netutils.AddIPOffset(baseEp, j) 380 eps.Endpoints = append(eps.Endpoints, discovery.Endpoint{ 381 Addresses: []string{ipEp.String()}, 382 }) 383 } 384 385 if epsFunc != nil { 386 epsFunc(eps) 387 } 388 389 // generate a base service object 390 svc := &v1.Service{ 391 ObjectMeta: metav1.ObjectMeta{ 392 Name: "svc", 393 Namespace: "namespace", 394 }, 395 Spec: v1.ServiceSpec{ 396 Type: v1.ServiceTypeClusterIP, 397 }, 398 } 399 400 if svcFunc != nil { 401 svcFunc(svc) 402 } 403 404 // Create the Services and associate and endpoint slice object to each one 405 for i := 0; i < nServices; i++ { 406 ip := netutils.AddIPOffset(base, i) 407 services[i] = svc.DeepCopy() 408 services[i].Name = fmt.Sprintf("svc%d", i) 409 services[i].Spec.ClusterIP = ip.String() 410 services[i].Spec.Ports = []v1.ServicePort{ 411 { 412 Name: fmt.Sprintf("%d", epPort), 413 Protocol: v1.ProtocolTCP, 414 Port: int32(basePort + i), 415 TargetPort: intstr.FromInt32(int32(epPort)), 416 }, 417 } 418 419 if svc.Spec.Type == v1.ServiceTypeNodePort || svc.Spec.Type == v1.ServiceTypeLoadBalancer { 420 services[i].Spec.Ports[0].NodePort = int32(30000 + i) 421 422 } 423 if svc.Spec.Type == v1.ServiceTypeLoadBalancer { 424 services[i].Spec.HealthCheckNodePort = int32(32000 + nServices + i) 425 } 426 427 endpointSlices[i] = eps.DeepCopy() 428 endpointSlices[i].Name = services[i].Name 429 endpointSlices[i].ObjectMeta.Labels = map[string]string{ 430 discovery.LabelServiceName: services[i].Name, 431 } 432 433 } 434 435 return services, endpointSlices 436 }