mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-30 17:58:14 +00:00 
			
		
		
		
	kube-proxy iptables expose number of rules metrics
add a new metric to kube-proxy iptables, so it exposes the number of rules programmed in each iteration.
This commit is contained in:
		| @@ -1604,6 +1604,11 @@ func (proxier *Proxier) syncProxyRules() { | |||||||
| 	proxier.iptablesData.Write(proxier.natChains.Bytes()) | 	proxier.iptablesData.Write(proxier.natChains.Bytes()) | ||||||
| 	proxier.iptablesData.Write(proxier.natRules.Bytes()) | 	proxier.iptablesData.Write(proxier.natRules.Bytes()) | ||||||
|  |  | ||||||
|  | 	numberFilterIptablesRules := utilproxy.CountBytesLines(proxier.filterRules.Bytes()) | ||||||
|  | 	metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableFilter)).Set(float64(numberFilterIptablesRules)) | ||||||
|  | 	numberNatIptablesRules := utilproxy.CountBytesLines(proxier.natRules.Bytes()) | ||||||
|  | 	metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableNAT)).Set(float64(numberNatIptablesRules)) | ||||||
|  |  | ||||||
| 	klog.V(5).InfoS("Restoring iptables", "rules", proxier.iptablesData.Bytes()) | 	klog.V(5).InfoS("Restoring iptables", "rules", proxier.iptablesData.Bytes()) | ||||||
| 	err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters) | 	err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -32,8 +32,11 @@ import ( | |||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	"k8s.io/apimachinery/pkg/util/intstr" | 	"k8s.io/apimachinery/pkg/util/intstr" | ||||||
|  | 	"k8s.io/component-base/metrics/testutil" | ||||||
| 	"k8s.io/klog/v2" | 	"k8s.io/klog/v2" | ||||||
| 	"k8s.io/kubernetes/pkg/proxy" | 	"k8s.io/kubernetes/pkg/proxy" | ||||||
|  | 	"k8s.io/kubernetes/pkg/proxy/metrics" | ||||||
|  |  | ||||||
| 	"k8s.io/kubernetes/pkg/proxy/healthcheck" | 	"k8s.io/kubernetes/pkg/proxy/healthcheck" | ||||||
| 	utilproxy "k8s.io/kubernetes/pkg/proxy/util" | 	utilproxy "k8s.io/kubernetes/pkg/proxy/util" | ||||||
| 	proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables" | 	proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables" | ||||||
| @@ -2932,4 +2935,124 @@ func TestProxierDeleteNodePortStaleUDP(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestProxierMetricsIptablesTotalRules(t *testing.T) { | ||||||
|  | 	ipt := iptablestest.NewFake() | ||||||
|  | 	fp := NewFakeProxier(ipt, false) | ||||||
|  |  | ||||||
|  | 	metrics.RegisterMetrics() | ||||||
|  |  | ||||||
|  | 	svcIP := "10.20.30.41" | ||||||
|  | 	svcPort := 80 | ||||||
|  | 	nodePort := 31201 | ||||||
|  | 	svcPortName := proxy.ServicePortName{ | ||||||
|  | 		NamespacedName: makeNSN("ns1", "svc1"), | ||||||
|  | 		Port:           "p80", | ||||||
|  | 		Protocol:       v1.ProtocolTCP, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	makeServiceMap(fp, | ||||||
|  | 		makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { | ||||||
|  | 			svc.Spec.ClusterIP = svcIP | ||||||
|  | 			svc.Spec.Ports = []v1.ServicePort{{ | ||||||
|  | 				Name:     svcPortName.Port, | ||||||
|  | 				Port:     int32(svcPort), | ||||||
|  | 				Protocol: v1.ProtocolTCP, | ||||||
|  | 				NodePort: int32(nodePort), | ||||||
|  | 			}} | ||||||
|  | 		}), | ||||||
|  | 	) | ||||||
|  | 	makeEndpointsMap(fp) | ||||||
|  |  | ||||||
|  | 	fp.syncProxyRules() | ||||||
|  |  | ||||||
|  | 	nFilterRules, err := testutil.GetGaugeMetricValue(metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableFilter))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("failed to get %s value, err: %v", metrics.IptablesRulesTotal.Name, err) | ||||||
|  | 	} | ||||||
|  | 	// -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 has no endpoints" -m udp -p udp -d 10.20.30.41/32 --dport 80 -j REJECT | ||||||
|  | 	// -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns1/svc1:p80 has no endpoints" -m addrtype --dst-type LOCAL -m udp -p udp --dport 31201 -j REJECT | ||||||
|  | 	// -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||||||
|  | 	// COMMIT | ||||||
|  |  | ||||||
|  | 	if nFilterRules != 7.0 { | ||||||
|  | 		t.Fatalf("Wrong number of filter rule: expected 7 received %f", nFilterRules) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nNatRules, err := testutil.GetGaugeMetricValue(metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableNAT))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("failed to get %s value, err: %v", metrics.IptablesRulesTotal.Name, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// rules -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN | ||||||
|  | 	// -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 | ||||||
|  | 	// -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE | ||||||
|  | 	// -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 | ||||||
|  | 	// -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS | ||||||
|  | 	// COMMIT | ||||||
|  | 	if nNatRules != 6.0 { | ||||||
|  | 		t.Fatalf("Wrong number of nat rules: expected 6 received %f", nNatRules) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	makeEndpointsMap(fp, | ||||||
|  | 		makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *v1.Endpoints) { | ||||||
|  | 			ept.Subsets = []v1.EndpointSubset{{ | ||||||
|  | 				Addresses: []v1.EndpointAddress{ | ||||||
|  | 					{ | ||||||
|  | 						IP: "10.0.0.2", | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						IP: "10.0.0.5", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Ports: []v1.EndpointPort{{ | ||||||
|  | 					Name:     svcPortName.Port, | ||||||
|  | 					Port:     int32(svcPort), | ||||||
|  | 					Protocol: v1.ProtocolTCP, | ||||||
|  | 				}}, | ||||||
|  | 			}} | ||||||
|  | 		}), | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	fp.syncProxyRules() | ||||||
|  |  | ||||||
|  | 	nFilterRules, err = testutil.GetGaugeMetricValue(metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableFilter))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("failed to get %s value, err: %v", metrics.IptablesRulesTotal.Name, err) | ||||||
|  | 	} | ||||||
|  | 	// -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||||||
|  | 	// -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||||||
|  | 	// COMMIT | ||||||
|  | 	if nFilterRules != 5.0 { | ||||||
|  | 		t.Fatalf("Wrong number of filter rule: expected 5 received %f", nFilterRules) | ||||||
|  | 	} | ||||||
|  | 	nNatRules, err = testutil.GetGaugeMetricValue(metrics.IptablesRulesTotal.WithLabelValues(string(utiliptables.TableNAT))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("failed to get %s value, err: %v", metrics.IptablesRulesTotal.Name, err) | ||||||
|  | 	} | ||||||
|  | 	// -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN | ||||||
|  | 	// -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 | ||||||
|  | 	// -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE | ||||||
|  | 	// -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 | ||||||
|  | 	// -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m udp -p udp -d 10.20.30.41/32 --dport 80 ! -s 10.0.0.0/24 -j KUBE-MARK-MASQ | ||||||
|  | 	// -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m udp -p udp -d 10.20.30.41/32 --dport 80 -j KUBE-SVC-OJWW7NSBVZTDHXNW | ||||||
|  | 	// -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m udp -p udp --dport 31201 -j KUBE-MARK-MASQ | ||||||
|  | 	// -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m udp -p udp --dport 31201 -j KUBE-SVC-OJWW7NSBVZTDHXNW | ||||||
|  | 	// -A KUBE-SVC-OJWW7NSBVZTDHXNW -m comment --comment ns1/svc1:p80 -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-AMT2SNW3YUNHJFJG | ||||||
|  | 	// -A KUBE-SEP-AMT2SNW3YUNHJFJG -m comment --comment ns1/svc1:p80 -s 10.0.0.2/32 -j KUBE-MARK-MASQ | ||||||
|  | 	// -A KUBE-SEP-AMT2SNW3YUNHJFJG -m comment --comment ns1/svc1:p80 -m udp -p udp -j DNAT --to-destination 10.0.0.2:80 | ||||||
|  | 	// -A KUBE-SVC-OJWW7NSBVZTDHXNW -m comment --comment ns1/svc1:p80 -j KUBE-SEP-OUFLBLJVR33W4FIZ | ||||||
|  | 	// -A KUBE-SEP-OUFLBLJVR33W4FIZ -m comment --comment ns1/svc1:p80 -s 10.0.0.5/32 -j KUBE-MARK-MASQ | ||||||
|  | 	// -A KUBE-SEP-OUFLBLJVR33W4FIZ -m comment --comment ns1/svc1:p80 -m udp -p udp -j DNAT --to-destination 10.0.0.5:80 | ||||||
|  | 	// -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS | ||||||
|  | 	// COMMIT | ||||||
|  | 	if nNatRules != 16.0 { | ||||||
|  | 		t.Fatalf("Wrong number of nat rules: expected 16 received %f", nNatRules) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // TODO(thockin): add *more* tests for syncProxyRules() or break it down further and test the pieces. | // TODO(thockin): add *more* tests for syncProxyRules() or break it down further and test the pieces. | ||||||
|   | |||||||
| @@ -126,6 +126,17 @@ var ( | |||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	// IptablesRulesTotal is the number of iptables rules that the iptables proxy installs. | ||||||
|  | 	IptablesRulesTotal = metrics.NewGaugeVec( | ||||||
|  | 		&metrics.GaugeOpts{ | ||||||
|  | 			Subsystem:      kubeProxySubsystem, | ||||||
|  | 			Name:           "sync_proxy_rules_iptables_total", | ||||||
|  | 			Help:           "Number of proxy iptables rules programmed", | ||||||
|  | 			StabilityLevel: metrics.ALPHA, | ||||||
|  | 		}, | ||||||
|  | 		[]string{"table"}, | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	// SyncProxyRulesLastQueuedTimestamp is the last time a proxy sync was | 	// SyncProxyRulesLastQueuedTimestamp is the last time a proxy sync was | ||||||
| 	// requested. If this is much larger than | 	// requested. If this is much larger than | ||||||
| 	// kubeproxy_sync_proxy_rules_last_timestamp_seconds, then something is hung. | 	// kubeproxy_sync_proxy_rules_last_timestamp_seconds, then something is hung. | ||||||
| @@ -151,6 +162,7 @@ func RegisterMetrics() { | |||||||
| 		legacyregistry.MustRegister(EndpointChangesTotal) | 		legacyregistry.MustRegister(EndpointChangesTotal) | ||||||
| 		legacyregistry.MustRegister(ServiceChangesPending) | 		legacyregistry.MustRegister(ServiceChangesPending) | ||||||
| 		legacyregistry.MustRegister(ServiceChangesTotal) | 		legacyregistry.MustRegister(ServiceChangesTotal) | ||||||
|  | 		legacyregistry.MustRegister(IptablesRulesTotal) | ||||||
| 		legacyregistry.MustRegister(IptablesRestoreFailuresTotal) | 		legacyregistry.MustRegister(IptablesRestoreFailuresTotal) | ||||||
| 		legacyregistry.MustRegister(SyncProxyRulesLastQueuedTimestamp) | 		legacyregistry.MustRegister(SyncProxyRulesLastQueuedTimestamp) | ||||||
| 	}) | 	}) | ||||||
|   | |||||||
| @@ -494,3 +494,8 @@ func RevertPorts(replacementPortsMap, originalPortsMap map[utilnet.LocalPort]uti | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CountBytesLines counts the number of lines in a bytes slice | ||||||
|  | func CountBytesLines(b []byte) int { | ||||||
|  | 	return bytes.Count(b, []byte{'\n'}) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"math/rand" | ||||||
| 	"net" | 	"net" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -1211,3 +1212,60 @@ func TestWriteBytesLine(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestWriteCountLines(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name     string | ||||||
|  | 		expected int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:     "write no line", | ||||||
|  | 			expected: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "write one line", | ||||||
|  | 			expected: 1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "write 100 lines", | ||||||
|  | 			expected: 100, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "write 1000 lines", | ||||||
|  | 			expected: 1000, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "write 10000 lines", | ||||||
|  | 			expected: 10000, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "write 100000 lines", | ||||||
|  | 			expected: 100000, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	testBuffer := bytes.NewBuffer(nil) | ||||||
|  | 	for _, testCase := range testCases { | ||||||
|  | 		t.Run(testCase.name, func(t *testing.T) { | ||||||
|  | 			testBuffer.Reset() | ||||||
|  | 			for i := 0; i < testCase.expected; i++ { | ||||||
|  | 				WriteLine(testBuffer, randSeq()) | ||||||
|  | 			} | ||||||
|  | 			n := CountBytesLines(testBuffer.Bytes()) | ||||||
|  | 			if n != testCase.expected { | ||||||
|  | 				t.Fatalf("lines expected: %d, got: %d", testCase.expected, n) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // obtained from https://stackoverflow.com/a/22892986 | ||||||
|  | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | ||||||
|  |  | ||||||
|  | func randSeq() string { | ||||||
|  | 	b := make([]rune, 30) | ||||||
|  | 	for i := range b { | ||||||
|  | 		b[i] = letters[rand.Intn(len(letters))] | ||||||
|  | 	} | ||||||
|  | 	return string(b) | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Antonio Ojea
					Antonio Ojea