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  }