mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Merge pull request #110503 from aojea/iptables_rules
kube-proxy iptables test number of generated iptables rules
This commit is contained in:
commit
832c4d8cb7
438
pkg/proxy/iptables/number_generated_rules_test.go
Normal file
438
pkg/proxy/iptables/number_generated_rules_test.go
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing"
|
||||
netutils "k8s.io/utils/net"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// kube-proxy generates iptables rules to forward traffic from Services to Endpoints
|
||||
// kube-proxy uses iptables-restore to configure the rules atomically, however,
|
||||
// this has the downside that large number of rules take a long time to be processed,
|
||||
// causing disruption.
|
||||
// There are different parameters than influence the number of rules generated:
|
||||
// - ServiceType
|
||||
// - Number of Services
|
||||
// - Number of Endpoints per Service
|
||||
// This test will fail when the number of rules change, so the person
|
||||
// that is modifying the code can have feedback about the performance impact
|
||||
// on their changes. It also runs multiple number of rules test cases to check
|
||||
// if the number of rules grows linearly.
|
||||
func TestNumberIptablesRules(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
epsFunc func(eps *discovery.EndpointSlice)
|
||||
svcFunc func(svc *v1.Service)
|
||||
services int
|
||||
epPerService int
|
||||
expectedFilterRules int
|
||||
expectedNatRules int
|
||||
}{
|
||||
{
|
||||
name: "0 Services 0 EndpointsPerService - ClusterIP",
|
||||
services: 0,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "1 Services 0 EndpointPerService - ClusterIP",
|
||||
services: 1,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 4,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "1 Services 1 EndpointPerService - ClusterIP",
|
||||
services: 1,
|
||||
epPerService: 1,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 10,
|
||||
},
|
||||
{
|
||||
name: "1 Services 2 EndpointPerService - ClusterIP",
|
||||
services: 1,
|
||||
epPerService: 2,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 13,
|
||||
},
|
||||
{
|
||||
name: "1 Services 10 EndpointPerService - ClusterIP",
|
||||
services: 1,
|
||||
epPerService: 10,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 37,
|
||||
},
|
||||
{
|
||||
name: "10 Services 0 EndpointsPerService - ClusterIP",
|
||||
services: 10,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 13,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "10 Services 1 EndpointPerService - ClusterIP",
|
||||
services: 10,
|
||||
epPerService: 1,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 55,
|
||||
},
|
||||
{
|
||||
name: "10 Services 2 EndpointPerService - ClusterIP",
|
||||
services: 10,
|
||||
epPerService: 2,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 85,
|
||||
},
|
||||
{
|
||||
name: "10 Services 10 EndpointPerService - ClusterIP",
|
||||
services: 10,
|
||||
epPerService: 10,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 325,
|
||||
},
|
||||
|
||||
{
|
||||
name: "0 Services 0 EndpointsPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 0,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "1 Services 0 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 1,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 7,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "1 Services 1 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 1,
|
||||
epPerService: 1,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 17,
|
||||
},
|
||||
{
|
||||
name: "1 Services 2 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 1,
|
||||
epPerService: 2,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 20,
|
||||
},
|
||||
{
|
||||
name: "1 Services 10 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 1,
|
||||
epPerService: 10,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 44,
|
||||
},
|
||||
{
|
||||
name: "10 Services 0 EndpointsPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 10,
|
||||
epPerService: 0,
|
||||
expectedFilterRules: 43,
|
||||
expectedNatRules: 5,
|
||||
},
|
||||
{
|
||||
name: "10 Services 1 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 10,
|
||||
epPerService: 1,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 125,
|
||||
},
|
||||
{
|
||||
name: "10 Services 2 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 10,
|
||||
epPerService: 2,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 155,
|
||||
},
|
||||
{
|
||||
name: "10 Services 10 EndpointPerService - LoadBalancer",
|
||||
svcFunc: func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ExternalIPs = []string{"1.2.3.4"}
|
||||
svc.Spec.LoadBalancerSourceRanges = []string{" 1.2.3.4/28"}
|
||||
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{
|
||||
IP: "1.2.3.4",
|
||||
}}
|
||||
},
|
||||
services: 10,
|
||||
epPerService: 10,
|
||||
expectedFilterRules: 3,
|
||||
expectedNatRules: 395,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ipt := iptablestest.NewFake()
|
||||
fp := NewFakeProxier(ipt)
|
||||
|
||||
svcs, eps := generateServiceEndpoints(test.services, test.epPerService, test.epsFunc, test.svcFunc)
|
||||
|
||||
makeServiceMap(fp, svcs...)
|
||||
populateEndpointSlices(fp, eps...)
|
||||
|
||||
now := time.Now()
|
||||
fp.syncProxyRules()
|
||||
t.Logf("time to sync rule: %v", time.Since(now))
|
||||
t.Logf("iptables data size: %d bytes", fp.iptablesData.Len())
|
||||
|
||||
if fp.filterRules.Lines() != test.expectedFilterRules {
|
||||
t.Errorf("expected number of Filter rules: %d, got: %d", test.expectedFilterRules, fp.filterRules.Lines())
|
||||
}
|
||||
|
||||
if fp.natRules.Lines() != test.expectedNatRules {
|
||||
t.Errorf("expected number of NAT rules: %d, got: %d", test.expectedNatRules, fp.natRules.Lines())
|
||||
}
|
||||
|
||||
// print generated iptables data
|
||||
// t.Logf("Generated rules:\n %s", fp.iptablesData.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_generateServiceEndpoints(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
services int
|
||||
epPerService int
|
||||
svcType v1.ServiceType
|
||||
}{
|
||||
{
|
||||
name: "Generate 10 Services with 10 Endpoints per Service and LoadBalancer Type",
|
||||
services: 10,
|
||||
epPerService: 10,
|
||||
svcType: v1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
{
|
||||
name: "Generate 10 Services with 20 Endpoints per Service and NodePort Type",
|
||||
services: 10,
|
||||
epPerService: 20,
|
||||
svcType: v1.ServiceTypeNodePort,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// test the function to mutate services
|
||||
svcFunc := func(svc *v1.Service) {
|
||||
svc.Spec.Type = test.svcType
|
||||
}
|
||||
// test the function to mutate endpoint slices
|
||||
epsFunc := func(eps *discovery.EndpointSlice) {
|
||||
for i := range eps.Endpoints {
|
||||
nodeName := fmt.Sprintf("node-%d", i)
|
||||
eps.Endpoints[i].NodeName = &nodeName
|
||||
}
|
||||
}
|
||||
|
||||
svcs, eps := generateServiceEndpoints(test.services, test.epPerService, epsFunc, svcFunc)
|
||||
|
||||
if len(svcs) != test.services {
|
||||
t.Fatalf("expected %d service, received %d", test.services, len(svcs))
|
||||
}
|
||||
if len(eps) != test.services {
|
||||
t.Fatalf("expected %d endpoint slice , received %d", test.services, len(eps))
|
||||
}
|
||||
|
||||
for i := 0; i < test.services; i++ {
|
||||
if svcs[i].Spec.Type != test.svcType {
|
||||
t.Fatalf("expected Service Type %s, got %s", test.svcType, svcs[i].Spec.Type)
|
||||
}
|
||||
if eps[i].ObjectMeta.Labels[discovery.LabelServiceName] != svcs[i].Name {
|
||||
t.Fatalf("endpoint slice reference %s instead of Service %s", eps[i].ObjectMeta.Labels[discovery.LabelServiceName], svcs[i].Name)
|
||||
}
|
||||
if len(eps[i].Endpoints) != test.epPerService {
|
||||
t.Fatalf("expected %d endpoints per slice , received %d", test.epPerService, len(eps[i].Endpoints))
|
||||
}
|
||||
for j := 0; j < test.epPerService; j++ {
|
||||
nodeName := fmt.Sprintf("node-%d", j)
|
||||
if *eps[i].Endpoints[j].NodeName != nodeName {
|
||||
t.Errorf("Endpoint %d on EndpointSlice %d expected Nodename %s, got %s", j, i, nodeName, *eps[i].Endpoints[j].NodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// generateServiceEndpoints generate Services with the Type specified and it creates N Endpoints per Service
|
||||
func generateServiceEndpoints(nServices, nEndpoints int, epsFunc func(eps *discovery.EndpointSlice), svcFunc func(svc *v1.Service)) ([]*v1.Service, []*discovery.EndpointSlice) {
|
||||
services := make([]*v1.Service, nServices)
|
||||
endpointSlices := make([]*discovery.EndpointSlice, nServices)
|
||||
|
||||
// base parameters
|
||||
basePort := 80
|
||||
base := netutils.BigForIP(netutils.ParseIPSloppy("10.0.0.1"))
|
||||
|
||||
// generate a base endpoint slice object
|
||||
baseEp := netutils.BigForIP(netutils.ParseIPSloppy("172.16.0.1"))
|
||||
epPort := 8080
|
||||
|
||||
tcpProtocol := v1.ProtocolTCP
|
||||
|
||||
eps := &discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ep",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
Ports: []discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr(fmt.Sprintf("%d", epPort)),
|
||||
Port: utilpointer.Int32(int32(epPort)),
|
||||
Protocol: &tcpProtocol,
|
||||
}},
|
||||
}
|
||||
|
||||
for j := 0; j < nEndpoints; j++ {
|
||||
ipEp := netutils.AddIPOffset(baseEp, j)
|
||||
eps.Endpoints = append(eps.Endpoints, discovery.Endpoint{
|
||||
Addresses: []string{ipEp.String()},
|
||||
})
|
||||
}
|
||||
|
||||
if epsFunc != nil {
|
||||
epsFunc(eps)
|
||||
}
|
||||
|
||||
// generate a base service object
|
||||
svc := &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
},
|
||||
}
|
||||
|
||||
if svcFunc != nil {
|
||||
svcFunc(svc)
|
||||
}
|
||||
|
||||
// Create the Services and associate and endpoint slice object to each one
|
||||
for i := 0; i < nServices; i++ {
|
||||
ip := netutils.AddIPOffset(base, i)
|
||||
services[i] = svc.DeepCopy()
|
||||
services[i].Name = fmt.Sprintf("svc%d", i)
|
||||
services[i].Spec.ClusterIP = ip.String()
|
||||
services[i].Spec.Ports = []v1.ServicePort{
|
||||
{
|
||||
Name: fmt.Sprintf("%d", epPort),
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: int32(basePort + i),
|
||||
TargetPort: intstr.FromInt(epPort),
|
||||
},
|
||||
}
|
||||
|
||||
if svc.Spec.Type == v1.ServiceTypeNodePort || svc.Spec.Type == v1.ServiceTypeLoadBalancer {
|
||||
services[i].Spec.Ports[0].NodePort = int32(30000 + i)
|
||||
|
||||
}
|
||||
if svc.Spec.Type == v1.ServiceTypeLoadBalancer {
|
||||
services[i].Spec.HealthCheckNodePort = int32(32000 + nServices + i)
|
||||
}
|
||||
|
||||
endpointSlices[i] = eps.DeepCopy()
|
||||
endpointSlices[i].Name = services[i].Name
|
||||
endpointSlices[i].ObjectMeta.Labels = map[string]string{
|
||||
discovery.LabelServiceName: services[i].Name,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return services, endpointSlices
|
||||
}
|
Loading…
Reference in New Issue
Block a user