-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Adding logic to GCE ingress controller to handle multi cluster ingresses #1033
Conversation
Coverage decreased (-0.03%) to 43.994% when pulling e1642581de5208fcd38b8abf4a369f36333a3066 on nikhiljindal:reportIGName into 3495bfb on kubernetes:master. |
dd518d2
to
ab61605
Compare
Updated the code to add default backend service node port to the instance group and also add an annotation with the instance group names. Verified that the code works as expected with the following scenarios:
There is an existing issue that deleting the ingress does not delete the corresponding named ports which is being tracked in #695. Will add e2e tests in main repo to automate these tests. cc @nicksardo PTAL |
controllers/gce/controller/utils.go
Outdated
func addInstanceGroupsAnnotation(existing map[string]string, igs []*compute.InstanceGroup) (map[string]string, error) { | ||
if existing == nil { | ||
existing = map[string]string{} | ||
} |
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 prefer asserting that the map exists outside of this func. Changing this would mean we don't need the first return variable either.
On another note, a less overloaded term than add
would be nice, ie set
or apply
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. Renamed to set
ab61605
to
46efeb3
Compare
@@ -263,3 +265,20 @@ func addNodes(lbc *LoadBalancerController, zoneToNode map[string][]string) { | |||
func getProbePath(p *api_v1.Probe) string { | |||
return p.Handler.HTTPGet.Path | |||
} | |||
|
|||
func TestAddInstanceGroupsAnnotation(t *testing.T) { |
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.
Add a test case for multiple zones?
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
controllers/gce/instances/utils.go
Outdated
|
||
// Helper method to create instance groups. | ||
// This method exists to ensure that we are using the same logic at all places. | ||
func CreateInstanceGroups(nodePool NodePool, namer *utils.Namer, port int64) ([]*compute.InstanceGroup, *compute.NamedPort, error) { |
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.
This just wraps around the namer, right?
IIUC, the namer is actually the same one.
Is it really worthy?
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.
This is to ensure that the same logic is used at both places.
It is to prevent scenarios where one place is updated but not the other.
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.
Do you still need this?
you only need to expose it at ClusterManager, right?
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.
Yes still need it. Its also called from backends.go
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.
Why not just remove this and do nodePool.AddInstanceGroup(namer.IGName(), port)
in both places?
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.
Doing that risks someone changing the logic at one place without changing the other.
This is a simple wrapper method to ensure that the same code is used at both places.
controllers/gce/controller/utils.go
Outdated
// name and zone of instance groups created for the ingress. | ||
// This is read only for users. Controller will overrite any user updates. | ||
// This is only set for ingresses with ingressClass = "gce-multi" | ||
instanceGroupsKey = "ingress.gcp.kubernetes.io/instance-groups" |
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: instanceGroupsAnnotationKey
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.
Updated
controllers/gce/controller/utils.go
Outdated
gceIngressClass = "gce" | ||
ingressClassKey = "kubernetes.io/ingress.class" | ||
gceIngressClass = "gce" | ||
gceMultiIngressClass = "gce-multi" |
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 sure whether the class key is settled. Maybe gce-multi-cluster
is better?
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.
Updated
@@ -342,6 +349,39 @@ func (lbc *LoadBalancerController) sync(key string) (err error) { | |||
return syncError | |||
} | |||
|
|||
func (lbc *LoadBalancerController) syncMultiClusterIngress(ing *extensions.Ingress, nodeNames []string) error { |
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.
There is duplicate logic here with Checkpoint. Can you refactor Checkpoint to avoid having dupplicate logic?
Currently, CreateInstanceGroup
is done as part of syncing backend services. Extract create and sync instance group
, and set the annotation, if gce-multi
, then return early, otherwise, continue for the rest.
This requires more changes to existing logic though. I will let @nicksardo to have more input for this.
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.
yeah I dont want to call the whole Checkpoint method just to sync instance groups. I think its better to call that specific method directly here. This also ensures that the existing ingress code is independent and unchanged.
if ingExists { | ||
ing := obj.(*extensions.Ingress) | ||
if isGCEMultiClusterIngress(ing) { | ||
return lbc.syncMultiClusterIngress(ing, nodeNames) |
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.
current me if I am wrong, if an ingress is updated from gce
to gce-multi
. The rest of GCP resources will stay around?
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.
lbc.CloudClusterManager.GC
above in line 303 will still be called which should delete those resources.
Still, that is an interesting case I hadnt thought of. What if we say that users can not "upgrade" an existing gce ingress to gce-multi. They will always have to delete the existing gce ingress and create a new gce-multi ingress?
46efeb3
to
c9fbf24
Compare
Thanks for the review @freehan |
cc @csbell |
@freehan Pushed a new commit to refactor the code to merge multi cluster sync with single cluster sync. |
var igs []*compute.InstanceGroup | ||
var err error | ||
for _, p := range servicePorts { | ||
igs, _, err = instances.CreateInstanceGroups(c.instancePool, c.ClusterNamer, p.Port) |
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.
Shouldn't we be appending to igs?
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.
CreateInstanceGroups creates instance groups in all zones and then adds the given named port to it.
Each time CreateInstanceGroups is called for a port, it always returns the same set of all igs. So we dont need append, just return the output of any call.
Ideally, we should call CreateInstanceGroups only the first time and then call AddNamedPort subsequent times, but that interface is not exposed yet. Will add a TODO and a comment explaining this.
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 the comment and TODO
2e74c81
to
0f756ae
Compare
Rebased to resolve conflicts |
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.
LGTM overall, some nits. @nicksardo for a final review.
controllers/gce/instances/utils.go
Outdated
|
||
// Helper method to create instance groups. | ||
// This method exists to ensure that we are using the same logic at all places. | ||
func CreateInstanceGroups(nodePool NodePool, namer *utils.Namer, port int64) ([]*compute.InstanceGroup, *compute.NamedPort, error) { |
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.
Do you still need this?
you only need to expose it at ClusterManager, right?
controllers/gce/controller/utils.go
Outdated
@@ -289,6 +304,17 @@ func ListAll(store cache.Store, selector labels.Selector, appendFn cache.AppendF | |||
func (s *StoreToIngressLister) List() (ing extensions.IngressList, err error) { |
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.
let us refactor here a little.
Refactor the current StoreToIngressLister.List()
function into ListGCEIngress()
and ListGCEMultiClusterIngress
.
Change the return value from extensions.IngressList
to []*extensions.Ingress
.
Change the input value for GCETranslator.toNodePorts()
to use []*extensions.Ingress
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.
List is also used at other places. I dont want all callers to get gce and gceMulti ingresses first and then append, so retaining the List method.
ListGCEMultiIngresses will not be used anywhere so not required atm.
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.
List
is also used in ListRuntimeInfo
. Then lbs
is passed into Checkpoint
. Then you filter lbs to singleClusterLbs in Checkpoint.
Please just use ListGCEIngresses
in ListRuntimeInfo and remove the filtering.
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 would prefer changing List to something else. Like ListAll
. Next time, when someone tries to use it, it would not get confused and leave a bug that only triggers when there are a combination of gce and multi-cluster ingresses.
Also, change the comment to explicitly say GCE ingress and multi-cluster ingress
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.
Good catch.
Renamed List to ListAll and updated the comment to be explicit.
Also renamed ListRuntimeInfo to ListGCERuntimeInfo and added a comment stating that it returns runtimeinfo only for gce ingresses and not for multi cluster ingresses.
Also removed the IsMultiCluster field from RuntimeInfo and updated Checkpoint accordingly.
@@ -279,7 +280,13 @@ func (lbc *LoadBalancerController) sync(key string) (err error) { | |||
if err != nil { | |||
return err | |||
} | |||
singleClusterIngresses, err := lbc.ingLister.ListGCEIngresses() |
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.
gceIngresses, err := lbc.ingLister.LIstGCEIngress()
gceMutliIngresses, err := lbc.ingLister.ListGCEMultiClusterIngress()
combinedIngresses := append(gceIngresses, gceMutliIngresses)
gceIngressNodeports := lbc.tr.toNodePorts(gceIngresses)
combinedNodeports := lbc.tr.toNodePorts(combinedIngresses)
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.
renamed to gceIngresses, gceNodePorts and allIngresses, allNodePorts.
0f756ae
to
8cd2902
Compare
Thanks @freehan Updated code as per comments. |
Coverage increased (+0.2%) to 44.231% when pulling 8cd2902924e997c112fd18a35ee67174d5b9ab41 on nikhiljindal:reportIGName into 18ea2f7 on kubernetes:master. |
8cd2902
to
0f4f5c9
Compare
Updated code as per comments. |
LGTM |
Pushed a new commit to call CreateInstanceGroups only once instead of thrice before. Its an extra optimization, should not have any user visible impact (except maybe get a bit faster). |
controllers/gce/backends/backends.go
Outdated
if err != nil { | ||
return err | ||
var err error | ||
if igs == nil { |
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.
Suggest adding comment saying when igs
is nil.
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
func (c *ClusterManager) SyncNodesInInstanceGroups(nodeNames []string) error { | ||
if err := c.instancePool.Sync(nodeNames); err != nil { | ||
return err | ||
} |
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.
What's the point of this wrapper?
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.
Yes had added it when the same code was being called from 2 places. Not required now after the latest refactor. Removed.
lbNames := lbc.ingLister.Store.ListKeys() | ||
lbs, err := lbc.ListRuntimeInfo() | ||
lbs, err := lbc.ListGCERuntimeInfo() |
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.
IMO, another nice change would be to plumb gceIngresses
to lbc.ListGCERuntimeInfo()
and maybe rename it to lbc.ToGCERuntimeInfo(gceIngresses)
. There would be no performance increase from this change, but the code would be clearer to follow and the function would appear less magical. Up to you.
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.
Yes its definitely cleaner!
toRuntimeInfo
does not need any special comments or logic about getting gce ingresses only or multi cluster ingresses as well. It just converts the ingresses that it receives. So thats nice!
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.
Also named it toRuntimeInfo
instead of toGCERuntimeInfo
since it now has no single or multi cluster specific logic. Its a generic method that returns RuntimeInfo structs for the given ingress objests
// Record any errors during sync and throw a single error at the end. This | ||
// allows us to free up associated cloud resources ASAP. | ||
if err := lbc.CloudClusterManager.Checkpoint(lbs, nodeNames, nodePorts); err != nil { | ||
if igs, err = lbc.CloudClusterManager.Checkpoint(lbs, nodeNames, gceNodePorts, allNodePorts); err != nil { |
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.
Since igs
isn't being set anywhere else. I'd recommend killing the instantiation on ln 323 and breaking apart this line into two.
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
@@ -329,21 +337,36 @@ func (lbc *LoadBalancerController) sync(key string) (err error) { | |||
if !ingExists { | |||
return syncError | |||
} | |||
ing := obj.(*extensions.Ingress) |
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.
Hmm, the dereference copy may have been on purpose from the original author. Thoughts?
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.
hmm I dont see a reason why but am not confident enough yet. Reverted for now. Will look closer and probably change it in a different PR
controllers/gce/controller/utils.go
Outdated
continue | ||
} | ||
knownPorts = append(knownPorts, port) | ||
return knownPorts |
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.
Here and line 524: why early return? Original code had continues.
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 have extracted an ingressToNodePorts
method out that returns node ports for a single ingress.
toNodePorts
calls ingressToNodePorts
multiple times for each ingress.
Before this change, it was all a single method and hence we were using continue to move on to next ingress if there was an error with an ingress.
With the new method, ingressToNodePorts
returns early in case of error and toNodePorts
then calls it again with the next ingress.
So the behavior should continue to be same.
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.
The original behavior was a continue of the local loop, not the outer loop, which means it would continue to the next path or rule.
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.
of course, I feel silly!
Fixed it now.
@@ -640,3 +673,34 @@ func (o PodsByCreationTimestamp) Less(i, j int) bool { | |||
} | |||
return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) | |||
} | |||
|
|||
// setInstanceGroupsAnnotation sets the instance-groups annotation with names of the given instance groups. | |||
func setInstanceGroupsAnnotation(existing map[string]string, igs []*compute.InstanceGroup) error { |
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.
Recommend naming something multi-cluster specific
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.
The method does not have any multi cluster specific logic. Its a generic method that sets the instance groups annotation. Hence I am inclined to keep the name generic.
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.
Okay
controllers/gce/controller/utils.go
Outdated
Name string | ||
Zone string | ||
} | ||
instanceGroups := []Value{} |
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: var instanceGroups []Value
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.
updated
controllers/gce/controller/utils.go
Outdated
for _, p := range nodePorts { | ||
portMap[p.Port] = p | ||
} | ||
nodePorts = []backends.ServicePort{} |
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: either var nodePorts []backends.ServicePort
or nodePorts := make([]backends.ServicePort, 0 len(portMap))
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.
Changed to nodePorts = make([]backends.ServicePort, 0 len(portMap))
return igs, nil | ||
} | ||
|
||
func (c *ClusterManager) CreateInstanceGroups(servicePorts []backends.ServicePort) ([]*compute.InstanceGroup, error) { |
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.
Can we rename this and the other CreateInstanceGroups
.
EnsureInstanceGroupsAndPorts
?
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.
Sure, done.
@@ -63,8 +63,7 @@ func (i *Instances) Init(zl zoneLister) { | |||
// all of which have the exact same named port. | |||
func (i *Instances) AddInstanceGroup(name string, port int64) ([]*compute.InstanceGroup, *compute.NamedPort, error) { | |||
igs := []*compute.InstanceGroup{} | |||
// TODO: move port naming to namer | |||
namedPort := &compute.NamedPort{Name: fmt.Sprintf("port%v", port), Port: port} | |||
namedPort := utils.GetNamedPort(port) |
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.
In the next PR when passing all ports, can you please rename AddInstanceGroup
to something like SyncInstanceGroups
, please?
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.
Sure. How about EnsureInstanceGroupsAndPorts
? Same as the corresponding cluster_manager method name.
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.
Sounds good!
32f311d
to
e91ce15
Compare
Thanks for the detailed review @nicksardo! |
Coverage increased (+0.2%) to 44.21% when pulling e91ce1511d61cf9d381701220ba734a60c22d30e on nikhiljindal:reportIGName into 587a344 on kubernetes:master. |
e91ce15
to
937cde6
Compare
Awesome, everything looks good. Would you mind doing a round of testing for MC but also basic ingress in different states (multiple nodes, multiple ingresses, with default backend, no default backend, etc...)? |
Brought up glbc with this code and verified that a single cluster ingress continues to work as expected. |
/lgtm |
Ingress controller should manage instance groups and ports for multi cluster ingresses.
Things to do:
Sending it out now to get early feedback.
Update:
Verified that the code works as expected with the following scenarios:
Creating an ingress with gce-multi class, creates expected instance group and deleting the ingress, deletes the ig.
If there is an existing ingress, then creating gce-multi ingress, reuses the existing instance group rather than creating a new one.
There is an existing issue that deleting the ingress does not delete the corresponding named ports which is being tracked in #695.
Will add e2e tests in main repo to automate these tests.
cc @nicksardo