-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add support for remote discovery #11224
Conversation
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
@@ -247,21 +267,21 @@ func decodeK8sConfigFromSecret(data []byte, enableEndpointSlices bool) (*k8s.API | |||
ctx, | |||
cfg, | |||
true, | |||
k8s.ES, k8s.Pod, k8s.Svc, k8s.SP, k8s.Job, k8s.Srv, |
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.
@mateiidavid I couldn't find where we used ServiceProfile or Node data so I removed them. Do you know if these are actually used anywhere?
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.
No, think it was from bad copy&pasta on my end. I re-used the same resources as the destination service. AFAIK SPs are only used for GetProfile()
and Node resources are queried in the endpoint translator exclusively.
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 SP informer is still gonna get called via NewServer()->watcher.NewProfileWatcher()
and if not initialized it will generate a panic? As for Nodes, we still use the endpoint translator, no?
Another thing I don't understand is why we need Job (here and in the original destination API) (introduced in 03762cc).
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 was looking into the Job
remark and it does seem like it's not used in the endpoints watcher. We get the owner kind and name here:
linkerd2/controller/api/destination/watcher/endpoints_watcher.go
Lines 974 to 977 in 2d3f3d3
ownerKind, ownerName, err := pp.metadataAPI.GetOwnerKindAndName(context.Background(), pod, false) | |
if err != nil { | |
return Address{}, PodID{}, err | |
} |
Which in turn will use the metadata API to directly query the API server for Job/RS info.
linkerd2/controller/k8s/metadata_api.go
Lines 224 to 248 in 2d3f3d3
if retry { | |
parentObj, err = api.client. | |
Resource(batchv1.SchemeGroupVersion.WithResource("jobs")). | |
Namespace(pod.Namespace). | |
Get(ctx, parent.Name, metav1.GetOptions{}) | |
if err != nil { | |
log.Warnf("failed to retrieve job from direct API call %s/%s: %s", pod.Namespace, parent.Name, err) | |
} | |
} | |
} | |
case "ReplicaSet": | |
var rsObj *metav1.PartialObjectMetadata | |
rsObj, err = api.getByNamespace(RS, pod.Namespace, parent.Name) | |
if err != nil { | |
log.Warnf("failed to retrieve replicaset from indexer %s/%s: %s", pod.Namespace, parent.Name, err) | |
if retry { | |
rsObj, err = api.client. | |
Resource(appsv1.SchemeGroupVersion.WithResource("replicasets")). | |
Namespace(pod.Namespace). | |
Get(ctx, parent.Name, metav1.GetOptions{}) | |
if err != nil { | |
log.Warnf("failed to retrieve replicaset from direct API call %s/%s: %s", pod.Namespace, parent.Name, err) | |
} | |
} | |
} |
So while we need RBAC for Jobs
it seems not to be required as an informer. It would seem better for the metadata client to also look for these resources in its cache. I think those are out of scope here tho.
re: profile watcher, the watchers we build in the cluster store don't start a profile watcher, so we should be fine not to use it imo. In my case it didn't crash. Profile lookups are done "locally".
# Generate template for imported service
$ linkerd profile podinfo-target --template | k apply -f -
serviceprofile.linkerd.io/podinfo-target.default.svc.cluster.local created
$ linkerd dg endpoints podinfo-target.default.svc.cluster.local
NAMESPACE IP PORT POD SERVICE
default 10.24.0.23 9898 podinfo-7c887c7bd5-tvkw9 podinfo.default
# Test discovery resp.
$ go run controller/script/destination-client/main.go -token "{\"nodeName\": \"k3d-multiaz-agent-8\" }" -path podinfo-target.default.svc.cluster.local:80 -method getProfile
INFO[0000] fully_qualified_name:"podinfo-target.default.svc.cluster.local" routes:{condition:{all:{matches:{method:{registered:POST}} matches:{path:{regex:"/authors/\\d+"}}}} response_classes:{condition:{status:{min:500 max:599}} is_failure:true} metrics_labels:{key:"route" value:"/authors/{id}"}} retry_budget:{retry_ratio:0.2 min_retries_per_second:10 ttl:{seconds:10}}
INFO[0000]
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.
Thanks Matei. So we can get rid of k8s.Job
when instantiating the remoteAPI
but leave it in the instantiation of metadataAPI
.
It would seem better for the metadata client to also look for these resources in its cache
Don't we do this already? We call api.getByNamespace()
and if that errors out, we fallback to the direct call.
About SP, you're right! (and now I get Node is only required in the metadataAPI, so that's fine too).
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
secretsInformerFactory := informers.NewSharedInformerFactoryWithOptions(client, k8s.ResyncTime, informers.WithNamespace(namespace)) | ||
secrets := secretsInformerFactory.Core().V1().Secrets().Informer() |
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.
Perhaps it's worth going through k8s.NewNamespacedAPI()
to get the cache metric for free? You could also leverage the Sync
on the api method that returns.
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 considered that, but it would mean also passing in a dynamic client and a l5dCrd client which never get used. It seemed simpler to avoid all of that machinery and just build what we need.
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.
Actually those two clients can be passed as nils.
@@ -247,21 +267,21 @@ func decodeK8sConfigFromSecret(data []byte, enableEndpointSlices bool) (*k8s.API | |||
ctx, | |||
cfg, | |||
true, | |||
k8s.ES, k8s.Pod, k8s.Svc, k8s.SP, k8s.Job, k8s.Srv, |
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 SP informer is still gonna get called via NewServer()->watcher.NewProfileWatcher()
and if not initialized it will generate a panic? As for Nodes, we still use the endpoint translator, no?
Another thing I don't understand is why we need Job (here and in the original destination API) (introduced in 03762cc).
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 looks great! I tested this out in a local environment (2 clusters, one linked against the other). Traffic works well, service profiles were discovered correctly on the imported service, and authz policies on the exported service work well too.
I'll note that linkerd mc check
doesn't work at the moment; it will throw an error if the imported service doesn't have endpoints (being addressed in #11226) .The test seems to fail because there's a diff in linkerd mc allow
:
# > means it was introduced by this change
25c25,28
< resources: ["pods", "endpoints", "services"]
---
> resources: ["pods", "endpoints", "services", "nodes"]
> verbs: ["list", "get", "watch"]
> - apiGroups: ["linkerd.io"]
> resources: ["serviceprofiles"]
30,32d32
< - apiGroups: ["policy.linkerd.io"]
< resources: ["servers"]
< verbs: ["list", "get", "watch"]
36d35
< resourceNames: ["linkerd-config"]
39a39,41
> - apiGroups: ["policy.linkerd.io"]
> resources: ["authorizationpolicies", "httproutes", "meshtlsauthentications", "networkauthentications", "servers", "serverauthorizations"]
> verbs: ["list", "get", "watch"]
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
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 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.
🙌🏻 🚢
## edge-23.8.2 This edge release adds improvements to Linkerd's multi-cluster features as part of the [flat network support] planned for Linkerd stable-2.14.0. In addition, it fixes an issue ([#10764]) where warnings about an invalid metric were logged frequently by the Destination controller. * Added a new `remoteDiscoverySelector` field to the multicluster `Link` CRD, which enables a service mirroring mod where the control plane performs discovery for the mirrored service from the remote cluster, rather than creating Endpoints for the mirrored service in the source cluster ([#11190], [#11201], [#11220], and [#11224]) * Fixed missing "Services" menu item in the Spanish localization for the `linkerd-viz` web dashboard ([#11229]) (thanks @mclavel!) * Replaced `server_port_subscribers` Destination controller gauge metric with `server_port_subscribes` and `server_port_unsubscribes` counter metrics ([#11206]; fixes [#10764]) * Replaced deprecated `failure-domain.beta.kubernetes.io` labels in Helm charts with `topology.kubernetes.io` labels ([#11148]; fixes [#11114]) (thanks @piyushsingariya!) [#10764]: #10764 [#11114]: #11114 [#11148]: #11148 [#11190]: #11190 [#11201]: #11201 [#11206]: #11206 [#11220]: #11220 [#11224]: #11224 [#11229]: #11229 [flat network support]: https://linkerd.io/2023/07/20/enterprise-multi-cluster-at-scale-supporting-flat-networks-in-linkerd/
## edge-23.8.2 This edge release adds improvements to Linkerd's multi-cluster features as part of the [flat network support] planned for Linkerd stable-2.14.0. In addition, it fixes an issue ([#10764]) where warnings about an invalid metric were logged frequently by the Destination controller. * Added a new `remoteDiscoverySelector` field to the multicluster `Link` CRD, which enables a service mirroring mode where the control plane performs discovery for the mirrored service from the remote cluster, rather than creating Endpoints for the mirrored service in the source cluster ([#11190], [#11201], [#11220], and [#11224]) * Fixed missing "Services" menu item in the Spanish localization for the `linkerd-viz` web dashboard ([#11229]) (thanks @mclavel!) * Replaced `server_port_subscribers` Destination controller gauge metric with `server_port_subscribes` and `server_port_unsubscribes` counter metrics ([#11206]; fixes [#10764]) * Replaced deprecated `failure-domain.beta.kubernetes.io` labels in Helm charts with `topology.kubernetes.io` labels ([#11148]; fixes [#11114]) (thanks @piyushsingariya!) [#10764]: #10764 [#11114]: #11114 [#11148]: #11148 [#11190]: #11190 [#11201]: #11201 [#11206]: #11206 [#11220]: #11220 [#11224]: #11224 [#11229]: #11229 [flat network support]: https://linkerd.io/2023/07/20/enterprise-multi-cluster-at-scale-supporting-flat-networks-in-linkerd/
Adds support for remote discovery to the destination controller.
When the destination controller gets a
Get
request for a Service with themulticluster.linkerd.io/remote-discovery
label, this is an indication that the destination controller should discover the endpoints for this service from a remote cluster. The destination controller will look for a remote cluster which has been linked to it (using thelinkerd multicluster link
command) with that name. It will look at themulticluster.linkerd.io/remote-discovery
label for the service name to look up in that cluster. It then streams back the endpoint data for that remote service.Since we now have multiple client-go informers for the same resource types (one for the local cluster and one for each linked remote cluster) we add a
cluster
label onto the prometheus metrics for the informers and EndpointWatchers to ensure that each of these components' metrics are correctly tracked and don't overwrite each other.