Skip to content

Conversation

@csfmomo
Copy link
Contributor

@csfmomo csfmomo commented Jun 16, 2020

What this PR does / why we need it:
Ipset is an extension for iptable to store and look up ip blocks in an efficient hash way. Move ip cidr related rule to ipset will improve npm performance.

Which issue this PR fixes
It fixes perf issue when large amount of ip cidr rule got applied.

Special notes for your reviewer:
Examples of ipset and iptables rules with this change.

image

image

Release note:

@csfmomo csfmomo requested a review from jaer-tsun June 16, 2020 07:20
@codecov
Copy link

codecov bot commented Jun 16, 2020

Codecov Report

Merging #582 into master will increase coverage by 3.92%.
The diff coverage is 93.33%.

@@            Coverage Diff             @@
##           master     #582      +/-   ##
==========================================
+ Coverage   49.08%   53.00%   +3.92%     
==========================================
  Files          28       23       -5     
  Lines        3449     2858     -591     
==========================================
- Hits         1693     1515     -178     
+ Misses       1466     1071     -395     
+ Partials      290      272      -18     

func createCidrsRule(ingressOrEgress string, policyName string, ipsetEntries [][]string, ipsMgr *ipsm.IpsetManager) {
spec := append([]string{util.IpsetNetHashFlag, util.IpsetMaxelemName, util.IpsetMaxelemNum})
for i, ipCidrSet := range ipsetEntries {
if ipCidrSet == nil || len(ipCidrSet) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translatepolicy should already remove empty sets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it didn't.
For the existing return object, including sets, namedPorts, lists, they are []string type and called UniqueStrSlice to remove empty set. However, for ipsetEntries, it's [][]string type and didn't do drop empty entries before returning. I intentionally didn't do that to keep index order for simplicity for deleting ipset when network policy rule updates.


func removeCidrsRule(ingressOrEgress string, policyName string, ipsetEntries [][]string, ipsMgr *ipsm.IpsetManager) {
for i, ipCidrSet := range ipsetEntries {
if ipCidrSet == nil || len(ipCidrSet) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this func will work as intended since the length of the set includes all the entries to be deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I intend to do here is, delete ipset npm created when translate cidrs rule. Length of the set includes cidrs belong to this rule.
It works as intended. I test it by updating same network policy rules. It will remove and update the target ipset.
image

npm/nwpolicy.go Outdated
if ipCidrSet == nil || len(ipCidrSet) == 0 {
continue
}
setName := policyName + "-cidr" + strconv.Itoa(i) + ingressOrEgress
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setName should include namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll add ns but remove cidr for setName.

resultSets = append(resultSets, ingressSets...)
resultNamedPorts = append(resultNamedPorts, ingressNamedPorts...)
resultLists = append(resultLists, ingressLists...)
resultIngressIPCidrs = ingressIPCidrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we copy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to copy. Let me remove it.
Copied it following previous coding style but is't not needed here.

resultSets = append(resultSets, egressSets...)
resultNamedPorts = append(resultNamedPorts, egressNamedPorts...)
resultLists = append(resultLists, egressLists...)
resultEgressIPCidrs = egressIPCidrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we copy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as comment in line 1515.

resultSets = append(resultSets, ingressSets...)
resultNamedPorts = append(resultNamedPorts, ingressNamedPorts...)
resultLists = append(resultLists, ingressLists...)
resultIngressIPCidrs = ingressIPCidrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we copy?

Copy link
Contributor Author

@csfmomo csfmomo Jun 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ingressIPCidrs is a local variable which under for loop. If we want to return it outside the for loop, we have to copy it to a non-local variable. Copy it to resultIngressIPCidrs to keep consistency with existing code.

resultSets = append(resultSets, egressSets...)
resultNamedPorts = append(resultNamedPorts, egressNamedPorts...)
resultLists = append(resultLists, egressLists...)
resultEgressIPCidrs = egressIPCidrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we copy?

Copy link
Contributor Author

@csfmomo csfmomo Jun 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

egressIPCidrs is a local variable which under for loop. If we want to return it outside the for loop, we have to copy it to a non-local variable. Copy it to resultEgressIPCidrs to keep consistency with existing code.

if toRule.IPBlock != nil {
if len(toRule.IPBlock.CIDR) > 0 {
ipCidrs[i] = append(ipCidrs[i], toRule.IPBlock.CIDR)
cidrIpsetName := name + "-cidr" + strconv.Itoa(i) + "out"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for "cidr" in name to hash

Copy link
Contributor Author

@csfmomo csfmomo Jun 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

if fromRule.IPBlock != nil {
if len(fromRule.IPBlock.CIDR) > 0 {
ipCidrs[i] = append(ipCidrs[i], fromRule.IPBlock.CIDR)
cidrIpsetName := name + "-cidr" + strconv.Itoa(i) + "in"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for "-cidr", but add namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about keeping the cidr to be more explicit? We just want this name to be unique.
At least we have to worry other ipset formed by podSelector or namespaceSelector from the same networkPolicyPeer in the future.

type NetworkPolicyPeer struct {
	PodSelector *metav1.LabelSelector 

	NamespaceSelector *metav1.LabelSelector

	IPBlock *IPBlock 
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, keep cidr will make it more explicit but it's not a hard request for now. Ipset also has a limitation for the name string length.
Current NPM keeps ipset for pod and namespace already. For pod, npm uses pod label key, pod label key and value as ipset name. For namespace, upm uses the name of namespaces as ipset name. They are not conflicting with IPBlock ipset name.

cidrIpsetName := name + "-cidr" + strconv.Itoa(i) + "in"
if len(fromRule.IPBlock.Except) > 0 {
for _, except := range fromRule.IPBlock.Except {
ipCidrs[i] = append(ipCidrs[i], except + " " + util.IpsetNomatch)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't have no matches since we're moving to allow based style

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do it in next PR, let's have this PR focus on ip table to ip set change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a TODO comment can help move this work to our next PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me add TODO comment.

mainred
mainred previously approved these changes Jun 17, 2020
Copy link
Member

@mainred mainred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments, but to not block your work, I'll approve your work and leave you and Jaeryn make the last decision

npm/ipsm/ipsm.go Outdated
operationFlag: util.IpsetCreationFlag,
set: util.GetHashedName(listName),
spec: util.IpsetSetListFlag,
spec: append([]string{util.IpsetSetListFlag}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
spec: append([]string{util.IpsetSetListFlag}),
spec: []string{util.IpsetSetListFlag},

npm/ipsm/ipsm.go Outdated
operationFlag: util.IpsetAppendFlag,
set: util.GetHashedName(listName),
spec: util.GetHashedName(setName),
spec: append([]string{util.GetHashedName(setName)}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

npm/ipsm/ipsm.go Outdated
operationFlag: util.IpsetDeletionFlag,
set: hashedListName,
spec: hashedSetName,
spec: append([]string{hashedSetName}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

npm/nwpolicy.go Outdated
return nil
}

func createCidrsRule(ingressOrEgress string, policyName string, ipsetEntries [][]string, ipsMgr *ipsm.IpsetManager) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about moving createCidrsRule and removeCidrsRule to ipsm.IpsetManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about moving createCidrsRule and removeCidrsRule to ipsm.IpsetManager?

createCidrsRule and removeCidrsRule are only needed during AddPolicy function which locate at nwpolicy.go. They won't be reused any where else. I would like to keep them inside nwpolicy.go. They reuse the functions of CreateSet and AddToSet in ipsm.IpsetManager. ipsm keeps more generic functions.

if fromRule.IPBlock != nil {
if len(fromRule.IPBlock.CIDR) > 0 {
ipCidrs[i] = append(ipCidrs[i], fromRule.IPBlock.CIDR)
cidrIpsetName := name + "-cidr" + strconv.Itoa(i) + "in"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about keeping the cidr to be more explicit? We just want this name to be unique.
At least we have to worry other ipset formed by podSelector or namespaceSelector from the same networkPolicyPeer in the future.

type NetworkPolicyPeer struct {
	PodSelector *metav1.LabelSelector 

	NamespaceSelector *metav1.LabelSelector

	IPBlock *IPBlock 
}

cidrIpsetName := name + "-cidr" + strconv.Itoa(i) + "in"
if len(fromRule.IPBlock.Except) > 0 {
for _, except := range fromRule.IPBlock.Except {
ipCidrs[i] = append(ipCidrs[i], except + " " + util.IpsetNomatch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a TODO comment can help move this work to our next PR.

}

func translateIngress(ns string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyIngressRule) ([]string, []string, []string, []*iptm.IptEntry) {
func translateIngress(ns string, name string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyIngressRule) ([]string, []string, []string, [][]string, []*iptm.IptEntry) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

policyName to be more explicit?

}
addedIngressFromEntry = true
}
if j != 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides IPBLocks, we also have podselector and namespace selector in the following steps, so we cannot continue to next rule item.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may later refactor this lengthy function to be more clean and easy to maintain.
CC @jaer-tsun

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides IPBLocks, we also have podselector and namespace selector in the following steps, so we cannot continue to next rule item.

We can continue. Please note the continue condition is when j != 0 which means the podselector and namespace selector has already been added in iptable entry when j = 0. It's a key point to improve our performance. When j = 0 which means npm loop through the 1st item in ipBlock, npm will create a cidr ipset, create a iptable entry. When j > 0, npm just need to add cidr to ipset entry, no need to create iptable entries anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may later refactor this lengthy function to be more clean and easy to maintain.
CC @jaer-tsun

Agree, I drafted a PR to refactor translatePolicy.go to remove duplicated code between translateIngress and translateEgress and make it concise.

}

func translateEgress(ns string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyEgressRule) ([]string, []string, []string, []*iptm.IptEntry) {
func translateEgress(ns string, name string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyEgressRule) ([]string, []string, []string, [][]string, []*iptm.IptEntry) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func translateEgress(ns string, name string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyEgressRule) ([]string, []string, []string, [][]string, []*iptm.IptEntry) {
func translateEgress(ns string, policyName string, targetSelector metav1.LabelSelector, rules []networkingv1.NetworkPolicyEgressRule) ([]string, []string, []string, [][]string, []*iptm.IptEntry) {

Copy link
Member

@mainred mainred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gofmt is also needed.

@csfmomo
Copy link
Contributor Author

csfmomo commented Jun 18, 2020

gofmt

Let me open a separate PR for gofmt.

Copy link
Contributor

@jaer-tsun jaer-tsun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@csfmomo csfmomo merged commit 19fb3e6 into master Jun 22, 2020
@csfmomo csfmomo deleted the moveToIpsetRebased branch July 10, 2020 21:45
@csfmomo csfmomo restored the moveToIpsetRebased branch July 10, 2020 21:45
@csfmomo csfmomo deleted the moveToIpsetRebased branch July 10, 2020 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants