mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +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.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()) | ||||
| 	err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -32,8 +32,11 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/intstr" | ||||
| 	"k8s.io/component-base/metrics/testutil" | ||||
| 	"k8s.io/klog/v2" | ||||
| 	"k8s.io/kubernetes/pkg/proxy" | ||||
| 	"k8s.io/kubernetes/pkg/proxy/metrics" | ||||
|  | ||||
| 	"k8s.io/kubernetes/pkg/proxy/healthcheck" | ||||
| 	utilproxy "k8s.io/kubernetes/pkg/proxy/util" | ||||
| 	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. | ||||
|   | ||||
| @@ -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 | ||||
| 	// requested. If this is much larger than | ||||
| 	// kubeproxy_sync_proxy_rules_last_timestamp_seconds, then something is hung. | ||||
| @@ -151,6 +162,7 @@ func RegisterMetrics() { | ||||
| 		legacyregistry.MustRegister(EndpointChangesTotal) | ||||
| 		legacyregistry.MustRegister(ServiceChangesPending) | ||||
| 		legacyregistry.MustRegister(ServiceChangesTotal) | ||||
| 		legacyregistry.MustRegister(IptablesRulesTotal) | ||||
| 		legacyregistry.MustRegister(IptablesRestoreFailuresTotal) | ||||
| 		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" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"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