New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Egress Services #3135
Conversation
c1b7981
to
ddeabe4
Compare
ddeabe4
to
f8f4623
Compare
858d091
to
ef00529
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finished through the master side commit. Great work Ori. I'll review the last 2 commits tmrw.
@jcaamano if you also want to take a look. If you don't have a lot of time focus on the libovsdb portions please.
go-controller/pkg/ovn/controller/egress_services/egress_services_controller.go
Show resolved
Hide resolved
go-controller/pkg/ovn/controller/egress_services/egress_services_controller.go
Show resolved
Hide resolved
}() | ||
} | ||
|
||
go c.checkNodesReachability() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we are going to have more than 1 goroutine doing this same thing? We should only have 1 goroutine doing this and make a universal cache (sync map) that both controllers can query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed over slack that we should do it later as it might be a bit big and may impact egressip too much -
wrote TODOs in multiple places + will open an issue once this is merged so we won't forget addressing this.
in the meantime I added a commit that generalizes the current reachability functions a bit
and pass the controller a isReachable func when it is initialized.
also added a small test that verifies the controller is responding correctly to a node hosting a service becoming unreachable and reachable (which I should have added earlier 🙂).
go-controller/pkg/ovn/controller/egress_services/egress_services_controller.go
Outdated
Show resolved
Hide resolved
go-controller/pkg/ovn/controller/egress_services/egress_services_service.go
Show resolved
Hide resolved
return c.clearServiceResources(key, state) | ||
} | ||
|
||
// We check if the service has a local host endpoint to avoid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this. So if the service host networked endpoint, you want to avoid enabling the egress service? Why? We can simply have a NAT rule to SNAT that as well to the egress IP, as long as that endpoint is on a node matching the node selector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops
changed here and in the node's side to care only about the service's non-host networked endpoints (added IsHostEndpoint
func). is it better now?
@@ -64,7 +64,8 @@ const ( | |||
InterNodePolicyPriority = "1003" | |||
HybridOverlaySubnetPriority = 1002 | |||
HybridOverlayReroutePriority = 501 | |||
DefaultNoRereoutePriority = 101 | |||
DefaultNoRereoutePriority = 102 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you check to make sure on upgrade we handle updating the current acls from 101->102 that would be in use for egress IP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not yet, will update here once I do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verified now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// CreateOrUpdateLogicalRouterPolicyWithPredicateOps looks up a logical | ||
// router policy from the cache based on a given predicate. If it does not | ||
// exist, it creates the provided logical router policy. If it does, it | ||
// updates it. The logical router policy is added to the provided logical | ||
// router. Returns the corresponding ops | ||
func CreateOrUpdateLogicalRouterPolicyWithPredicateOps(nbClient libovsdbclient.Client, ops []libovsdb.Operation, | ||
routerName string, lrp *nbdb.LogicalRouterPolicy, p logicalRouterPolicyPredicate, fields ...interface{}) ([]libovsdb.Operation, error) { | ||
if len(fields) == 0 { | ||
fields = onModelUpdatesAllNonDefault() | ||
} | ||
router := &nbdb.LogicalRouter{ | ||
Name: routerName, | ||
} | ||
|
||
opModels := []operationModel{ | ||
{ | ||
Model: lrp, | ||
ModelPredicate: p, | ||
OnModelUpdates: fields, | ||
DoAfter: func() { router.Policies = []string{lrp.UUID} }, | ||
ErrNotFound: false, | ||
BulkOp: false, | ||
}, | ||
{ | ||
Model: router, | ||
ModelPredicate: func(item *nbdb.LogicalRouter) bool { return item.Name == router.Name }, | ||
OnModelMutations: []interface{}{&router.Policies}, | ||
ErrNotFound: true, | ||
BulkOp: false, | ||
}, | ||
} | ||
|
||
m := newModelClient(nbClient) | ||
return m.CreateOrUpdateOps(ops, opModels...) | ||
} | ||
|
||
// DeleteLogicalRouterPolicyWithPredicateOps looks up a logical | ||
// router policy from the cache based on a given predicate and returns the | ||
// corresponding ops to delete it and remove it from the provided router. | ||
func DeleteLogicalRouterPolicyWithPredicateOps(nbClient libovsdbclient.Client, ops []libovsdb.Operation, routerName string, p logicalRouterPolicyPredicate) ([]libovsdb.Operation, error) { | ||
router := &nbdb.LogicalRouter{ | ||
Name: routerName, | ||
} | ||
|
||
deleted := []*nbdb.LogicalRouterPolicy{} | ||
opModels := []operationModel{ | ||
{ | ||
ModelPredicate: p, | ||
ExistingResult: &deleted, | ||
DoAfter: func() { router.Policies = extractUUIDsFromModels(&deleted) }, | ||
ErrNotFound: false, | ||
BulkOp: true, | ||
}, | ||
{ | ||
Model: router, | ||
ModelPredicate: func(lr *nbdb.LogicalRouter) bool { return lr.Name == router.Name }, | ||
OnModelMutations: []interface{}{&router.Policies}, | ||
ErrNotFound: true, | ||
BulkOp: false, | ||
}, | ||
} | ||
|
||
m := newModelClient(nbClient) | ||
return m.DeleteOps(ops, opModels...) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please refactor the non Ops
version of these methods to call these and then transact; similar to what we are already doing with other methods that have both Ops
and non-Ops
variants in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
svcKey, found := item.ExternalIDs["EgressSVC"] | ||
if !found { | ||
klog.Infof("Egress service repair will delete lrp because it uses the egress service priority but does not belong to one: %v", item) | ||
return true | ||
} | ||
|
||
svc, found := c.services[svcKey] | ||
if !found { | ||
klog.Infof("Egress service repair will delete %s because it is no longer a valid egress service: %v", svcKey, item) | ||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I am not sure if we should be bothering with this but this is the perfect use case for adding a secondary index as client index so that looking up policies with ExternalIDs["EgressSVC"]==svcKey
is fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/assign @flavio-fernandes
@@ -0,0 +1,452 @@ | |||
package e2e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oribon have you considered what happens when egress-services is configured in a cluster together with egress IPs? I would suspect OVN rules to look a bit similar in that case. Making it predictable as possible would be a big win.
It may be useful to have that integration tested to ensure it behaves as we expect it to. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, in the enhancement we decided this will use lrps with a higher priority than the eip ones - it's a good idea to have it tested so I'll write something :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added Should validate egress service has higher priority than EgressIP
:)
69e0f6e
to
955088c
Compare
The no-reroute (allow) policies that ensure east<->west traffic does not break when steering a pod's traffic needs to be created the same for any controller that creates reroute policies (like the egress service controller). This change allows us to reuse the functions that create them in these controllers. Also, we add an ExternalID for the node policy so that a level driven controller can pass the node's key to the delete path as it can't send a deleted node's object. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
This allows us to share the reachability logic used for EgressIP nodes with any controller that cares about monitoring nodes but does not use the egressNode struct. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
Export these functions as they can be used in a controller that watches endpointslices/services like the egress service controller. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
Add the logic that handles ovnkube-master's side of implementing egress services, which watches LoadBalancer services with the "k8s.ovn.org/egress-service" annotation and primarily: * picks a node for the service that would handle all of its traffic. * creates logical router policies on the cluster router that steer the service's pods egress traffic towards the node's mgmt port. * adds the "k8s.ovn.org/egress-service-host" to the service with the node's name. * adds the "egress-service.k8s.ovn.org" label to the node with the service's namespace/name. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
Add the logic that handles ovnkube-node's side of implementing egress services, which watches LoadBalancer services with the "k8s.ovn.org/egress-service-host" annotation and creates the iptables rules that SNATs packets coming from the service's pods to its ingress IP before sending them out. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
Add initial E2E tests for egress services, verifying that the egress traffic of pods backing an egress service is SNATed to the service's ingress IP and that ingress traffic for the service is not broken. We also verify that the nodeSelector config option works correctly by updating it to pick a different node each time. Signed-off-by: Ori Braunshtein <obraunsh@redhat.com>
955088c
to
19d411a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add docs/egress-service.md in another PR
Thanks @oribon !
- What this PR does and why is it needed
This PR implements service egress traffic steering as described in openshift/enhancements#1200.
By annotating a LoadBalancer service with a valid
"k8s.ovn.org/egress-service"
annotation , users could request that the source IP of egress packets originating from all of the pods that are endpoints of it would be its ingress IP.This behavior requires that service access from outside the cluster will only work to a designated node and is contradictory to how default services work where a service may be accessed via any node.
Announcing the service externally (for service ingress traffic) would be handled by a separate LoadBalancer provider.
TODOs:Implement isReachable, for now it always returns true (waiting for Egress IP health monitoring over GRPC #3100)Add ETP=Local tests- Special notes for reviewers
The two commits that handle ovnkube-master/ovnkube-node can be reviewed separately.
- How to verify it
Running the unit tests contained in:
go-controller/pkg/ovn/egressservices_test.go
go-controller/pkg/node/gateway_localnet_linux_test.go
Running E2E tests:
test/e2e/egress_services.go
(Focusing on IPv4/IPv6 if the cluster does not support one)- Description for the changelog
Implement Egress Services