-
Notifications
You must be signed in to change notification settings - Fork 8
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
feat(ctrl): implements MeshFederation reconciliation #170
base: master
Are you sure you want to change the base?
feat(ctrl): implements MeshFederation reconciliation #170
Conversation
This PR contains two other, previously stacked PR:
|
e0d1ba7
to
326005f
Compare
dd49df9
to
204092f
Compare
This commit introduces the initial implementation of the MeshFederation controller. The controller is responsible for: - Managing the MeshFederation server lifecycle (openshift-service-mesh#152) - removes a need for a channel to trigger push - pushing directly instead - Configuring MeshFederation resources, including: - IngressGateway - PeerAuthentication - EnvoyFilter (for OpenShift Router) - Routes (for OpenShift Router) - Watching Kubernetes services to: - Push SotW updates to all connected peers (openshift-service-mesh#153) - Update MeshFederation cluster configuration - Support both label selectors and expressions (openshift-service-mesh#52 openshift-service-mesh#143) Basic EnvTest tests are included to verify the setup. Fixes openshift-service-mesh#152 openshift-service-mesh#52 openshift-service-mesh#143 openshift-service-mesh#153
204092f
to
3fba4e1
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.
Initial pass with a few comments/suggestions. I'll continue the review later.
internal/controller/meshfederation/meshfederation_controller.go
Outdated
Show resolved
Hide resolved
internal/controller/meshfederation/meshfederation_controller.go
Outdated
Show resolved
Hide resolved
internal/controller/meshfederation/meshfederation_controller.go
Outdated
Show resolved
Hide resolved
internal/controller/meshfederation/meshfederation_controller.go
Outdated
Show resolved
Hide resolved
internal/controller/meshfederation/meshfederation_controller.go
Outdated
Show resolved
Hide resolved
There is no point in stopping reconcile - we want to continue with bringing the cluster to the desired state. We should: - report on status - raise event
adds retry logic with delay
Lets not |
Owns(&routev1.Route{}). | ||
Watches( | ||
// TODO(design): initial reconcile will trigger a lot of requests - one for each service. This can become expensive. | ||
&corev1.Service{}, |
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.
FOLLOW-UP
We don't need to watch the corev1.Service
object, we can limit it only to what we are interested in at this point - labels. For this purpose watch on PartialObjectMetadata
is sufficient. This will also reduce the payload send from k8s api-server.
@eoinfennessy I think you can also think about it for the zones as you rely on namespace only IIRC. Thing I don't know yet is if that's the same cache as regular objects.
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.
Thank you! I actually wrote a TODO for this very thing! :) https://github.com/openshift-service-mesh/istio-zones/blob/main/internal/controller/zone_controller.go#L256-L257
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 saw that the other day and woke up with PartialObjectMetadata
thing in my head so I just dumped it here before first coffee :)
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.
Looks great! I just added a few small nits/suggestions.
return accResult, errors.Join(errs...) | ||
} | ||
|
||
func (r *Reconciler) handleServiceToExport(ctx context.Context, object client.Object) []reconcile.Request { |
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: I think the name could be a little clearer. Something like this:
func (r *Reconciler) handleServiceToExport(ctx context.Context, object client.Object) []reconcile.Request { | |
func (r *Reconciler) mapServiceToReconcileRequests(ctx context.Context, object client.Object) []reconcile.Request { |
meshFederations := &v1alpha1.MeshFederationList{} | ||
// TODO paginate? options? | ||
if errList := r.Client.List(ctx, meshFederations); errList != nil { | ||
logger.Error(errList, "failed mapping Service to MeshFederations", "service", object.GetName()+"/"+object.GetNamespace()) |
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.
logger.Error(errList, "failed mapping Service to MeshFederations", "service", object.GetName()+"/"+object.GetNamespace()) | |
logger.Error(errList, "failed to list MeshFederations when mapping Service to reconcile requests", "service", object.GetName()+"/"+object.GetNamespace()) |
createGateway := func() objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway] { | ||
result := &v1alpha3.Gateway{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "federation-ingress-gateway", | ||
Namespace: meshFederation.Spec.ControlPlaneNamespace, | ||
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | ||
}, | ||
} | ||
|
||
result.Spec = desiredSpec() | ||
|
||
return objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway]{ | ||
Obj: result, | ||
DesiredSpec: func() istionetv1alpha3.Gateway { | ||
return desiredSpec() | ||
}, | ||
} | ||
} |
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
createGateway := func() objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway] { | |
result := &v1alpha3.Gateway{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: "federation-ingress-gateway", | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | |
}, | |
} | |
result.Spec = desiredSpec() | |
return objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway]{ | |
Obj: result, | |
DesiredSpec: func() istionetv1alpha3.Gateway { | |
return desiredSpec() | |
}, | |
} | |
} | |
createGateway := func() objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway] { | |
result := &v1alpha3.Gateway{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: "federation-ingress-gateway", | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | |
}, | |
Spec: desiredSpec(), | |
} | |
return objReconciler[*v1alpha3.Gateway, istionetv1alpha3.Gateway]{ | |
Obj: result, | |
DesiredSpec: func() istionetv1alpha3.Gateway { | |
return desiredSpec() | |
}, | |
} | |
} |
envoyFilter := func(svcName, svcNamespace string, port int32) objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter] { | ||
result := &v1alpha3.EnvoyFilter{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: fmt.Sprintf("sni-%s-%s-%d", svcName, svcNamespace, port), | ||
Namespace: meshFederation.Spec.ControlPlaneNamespace, | ||
Labels: map[string]string{ | ||
"federation.openshift-service-mesh.io/peer": "todo", | ||
}, | ||
}, | ||
} | ||
|
||
result.Spec = desiredSpec(svcName, svcNamespace, port) | ||
|
||
return objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter]{ | ||
Obj: result, | ||
DesiredSpec: func() istionetv1alpha3.EnvoyFilter { | ||
return desiredSpec(svcName, svcNamespace, 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.
Nit
envoyFilter := func(svcName, svcNamespace string, port int32) objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter] { | |
result := &v1alpha3.EnvoyFilter{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: fmt.Sprintf("sni-%s-%s-%d", svcName, svcNamespace, port), | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{ | |
"federation.openshift-service-mesh.io/peer": "todo", | |
}, | |
}, | |
} | |
result.Spec = desiredSpec(svcName, svcNamespace, port) | |
return objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter]{ | |
Obj: result, | |
DesiredSpec: func() istionetv1alpha3.EnvoyFilter { | |
return desiredSpec(svcName, svcNamespace, port) | |
}, | |
} | |
} | |
envoyFilter := func(svcName, svcNamespace string, port int32) objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter] { | |
result := &v1alpha3.EnvoyFilter{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: fmt.Sprintf("sni-%s-%s-%d", svcName, svcNamespace, port), | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{ | |
"federation.openshift-service-mesh.io/peer": "todo", | |
}, | |
}, | |
Spec: desiredSpec(svcName, svcNamespace, port), | |
} | |
return objReconciler[*v1alpha3.EnvoyFilter, istionetv1alpha3.EnvoyFilter]{ | |
Obj: result, | |
DesiredSpec: func() istionetv1alpha3.EnvoyFilter { | |
return desiredSpec(svcName, svcNamespace, port) | |
}, | |
} | |
} |
createRoute := func(svcName, svcNamespace string, port int32) objReconciler[*routev1.Route, routev1.RouteSpec] { | ||
result := &routev1.Route{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: fmt.Sprintf("%s-%s-%d-to-federation-ingress-gateway", svcName, svcNamespace, port), | ||
Namespace: meshFederation.Spec.ControlPlaneNamespace, | ||
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | ||
}, | ||
} | ||
|
||
result.Spec = desiredSpec(svcName, svcNamespace, port) | ||
|
||
return objReconciler[*routev1.Route, routev1.RouteSpec]{ | ||
Obj: result, | ||
DesiredSpec: func() routev1.RouteSpec { | ||
return desiredSpec(svcName, svcNamespace, 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.
Nit
createRoute := func(svcName, svcNamespace string, port int32) objReconciler[*routev1.Route, routev1.RouteSpec] { | |
result := &routev1.Route{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: fmt.Sprintf("%s-%s-%d-to-federation-ingress-gateway", svcName, svcNamespace, port), | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | |
}, | |
} | |
result.Spec = desiredSpec(svcName, svcNamespace, port) | |
return objReconciler[*routev1.Route, routev1.RouteSpec]{ | |
Obj: result, | |
DesiredSpec: func() routev1.RouteSpec { | |
return desiredSpec(svcName, svcNamespace, port) | |
}, | |
} | |
} | |
createRoute := func(svcName, svcNamespace string, port int32) objReconciler[*routev1.Route, routev1.RouteSpec] { | |
result := &routev1.Route{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: fmt.Sprintf("%s-%s-%d-to-federation-ingress-gateway", svcName, svcNamespace, port), | |
Namespace: meshFederation.Spec.ControlPlaneNamespace, | |
Labels: map[string]string{"federation.openshift-service-mesh.io/peer": "todo"}, | |
}, | |
Spec: desiredSpec(svcName, svcNamespace, port), | |
} | |
return objReconciler[*routev1.Route, routev1.RouteSpec]{ | |
Obj: result, | |
DesiredSpec: func() routev1.RouteSpec { | |
return desiredSpec(svcName, svcNamespace, port) | |
}, | |
} | |
} |
resources := pushRequest.Resources | ||
if resources == nil { | ||
var err error | ||
resources, err = adss.generateResources(pushRequest.TypeUrl) | ||
if 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.
Readability suggestion avoiding variable reassignment:
resources := pushRequest.Resources | |
if resources == nil { | |
var err error | |
resources, err = adss.generateResources(pushRequest.TypeUrl) | |
if err != nil { | |
return err | |
} | |
} | |
var resources []*anypb.Any | |
if pushRequest.Resources == nil { | |
var err error | |
resources, err = adss.generateResources(pushRequest.TypeUrl) | |
if err != nil { | |
return err | |
} | |
} else { | |
resources = pushRequest.Resources | |
} |
// TODO(design): do we want to keep all the services? seems convenient, but shouldn't we be concerned about the size? | ||
saved.Status.ExportedServices = meshFederation.Status.ExportedServices |
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.
FOLLOW-UP
It may be possible to minimise the number of full SotW pushes by comparing the old list of services to the new list of services and only pushing if the list has changed. This could be achieved by storing a hash of the services in the MeshFederation's status (status.lastPushHash: 1234abcd
) and only pushing if this hash changes.
This commit introduces the initial implementation of the MeshFederation
controller. The controller is responsible for:
Gateway
PeerAuthentication
EnvoyFilter
(for OpenShift Router)Route
s (for OpenShift Router)Services
to:MeshFederation
cluster configurationSupport both label selectors and expressions (#52 #143) as export rules.
Basic EnvTest tests are included to verify the setup (happy paths for both
istio
andopenshift-router
ingress types).Introducing
MeshFederation.Status.ExportedServices
to keep track ofexported services in each reconcile.
Current limitations / random thoughts
There are loose ends I identified while implementing it. Most are in
TODO
s,but I thought keeping them in the PR description helps with getting initial context.
MeshFederation
instancesall which is hardcoded (
:15080
), some ideas:which makes it impossible to e.g. push SotW to only relevant peers on a given
MeshFederation
reconcile. Perhaps expanding FDS protocol can solve it.all remotes in a single federation instead of relying on the defaults
istio-system
) can cause issues when we create mesh-wide resources)MeshFederation
Fixes #152
Fixes #52
Fixes #143
Fixes #153