Conversation
Signed-off-by: Alex Leong <[email protected]>
Signed-off-by: Alex Leong <[email protected]>
|
Integration test results for f1b851c: success 🎉 |
Relates to linkerd/linkerd2#3113 Signed-off-by: Andrew Seigner <[email protected]>
siggy
left a comment
There was a problem hiding this comment.
Confirmed working with:
BuoyantIO/emojivoto#68
From linkerd-prometheus pod:
wget -O - web-0.web-svc.emojivoto.svc.cluster.local:80nit: rather than hostname, can we name this podName, or podHostname? when i see hostname in the code it's not obvious to me that's it's referencing a specific pod. (i realize that the k8s core field is named EndpointAddress.Hostname).
| suffix := []string{"svc", "cluster", "local"} | ||
| for i, subdomain := range domains[2:] { | ||
| n := len(domains) | ||
| if n < 5 { |
There was a problem hiding this comment.
should this also check for n > 6 ? that way we could ditch the the last return statement.
There was a problem hiding this comment.
Yes, but I think we'd need to make the n == 6 case into the else block. I think the current way is clearer.
| podIP: 172.17.0.20`, | ||
| }, | ||
| authority: "name1-3.name1.ns.svc.cluster.local:5959", | ||
| expectedAddresses: []string{"172.17.0.20:5959"}, |
| for _, subset := range endpoints.Subsets { | ||
| resolvedPort := pp.resolveTargetPort(subset) | ||
| for _, endpoint := range subset.Addresses { | ||
| if pp.hostname != "" && pp.hostname != endpoint.Hostname { |
There was a problem hiding this comment.
why the pp.hostname != "" check? if pp.hostname == "" and endpoint.Hostname == "foo" this check will fail, is that the intent?
There was a problem hiding this comment.
If the portPublisher's hostname is unset (empty string) that means that no hostname was specified. In other words, this is a regular service lookup and not a stable pod id lookup. Therefore, we do not want to skip any of the endpoints.
There was a problem hiding this comment.
So does that mean pp.hostname == "" && endpoint.Hostname == "foo" will never happen? And if it does, then there is a bug elsewhere? Or can we just do if pp.hostname != endpoint.Hostname here?
There was a problem hiding this comment.
pp.hostname corresponds to the subscriber's query whereas endpoint.Hostname corresponds to the hostname of the pod. So, for example, if we did a destination lookup for foo.default.svc.cluster.local where foo is a service which selects a stateful set, then pp.hostname would be empty (because we are querying the entire service, not a particular stable id) but each pod in the service would have a non-empty endpoint.Hostname.
In this case, we want to select all the pods, and therefore do not want to hit the continue.
Relates to linkerd/linkerd2#3113 Signed-off-by: Andrew Seigner <[email protected]>
|
They're referred to as hostnames in the Kubernetes documentation as well: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-network-id I agree that it's not the best name, but I'd prefer to match the kubernetes terminology. |
ihcsim
left a comment
There was a problem hiding this comment.
👍 Can confirm that this works with the example in #2266.
Also, the new metrics label shows up correctly:
# HELP endpoints_exists A gauge which is 1 if the endpoints exists and 0 if it does not.
# TYPE endpoints_exists gauge
endpoints_exists{hostname="",namespace="default",port="80",service="my-external-nginx"} 0
endpoints_exists{hostname="",namespace="linkerd",port="9090",service="linkerd-prometheus"} 1
endpoints_exists{hostname="nginx-0",namespace="default",port="80",service="nginx"} 1
endpoints_exists{hostname="nginx-1",namespace="default",port="80",service="nginx"} 1
endpoints_exists{hostname="nginx-2",namespace="default",port="80",service="nginx"} 1
# HELP endpoints_pods A gauge for the current number of pods in a endpoints.
# TYPE endpoints_pods gauge
endpoints_pods{hostname="",namespace="default",port="80",service="my-external-nginx"} 0
endpoints_pods{hostname="",namespace="linkerd",port="9090",service="linkerd-prometheus"} 1
endpoints_pods{hostname="nginx-0",namespace="default",port="80",service="nginx"} 1
endpoints_pods{hostname="nginx-1",namespace="default",port="80",service="nginx"} 1
endpoints_pods{hostname="nginx-2",namespace="default",port="80",service="nginx"} 1
# HELP endpoints_subscribers A gauge for the current number of subscribers to a endpoints.
# TYPE endpoints_subscribers gauge
endpoints_subscribers{hostname="",namespace="default",port="80",service="my-external-nginx"} 2
endpoints_subscribers{hostname="",namespace="linkerd",port="9090",service="linkerd-prometheus"} 1
endpoints_subscribers{hostname="nginx-0",namespace="default",port="80",service="nginx"} 2
endpoints_subscribers{hostname="nginx-1",namespace="default",port="80",service="nginx"} 2
endpoints_subscribers{hostname="nginx-2",namespace="default",port="80",service="nginx"} 1
# HELP endpoints_updates A counter for number of updates to a endpoints.
# TYPE endpoints_updates counter
endpoints_updates{hostname="",namespace="default",port="80",service="my-external-nginx"} 0
endpoints_updates{hostname="",namespace="linkerd",port="9090",service="linkerd-prometheus"} 2
endpoints_updates{hostname="nginx-0",namespace="default",port="80",service="nginx"} 2
endpoints_updates{hostname="nginx-1",namespace="default",port="80",service="nginx"} 2
endpoints_updates{hostname="nginx-2",namespace="default",port="80",service="nginx"} 1
…n server Signed-off-by: Alex Leong <[email protected]>
|
This conflicted non-trivially with #2950. Since authority parsing is now more complex and depends on the cluster domain, I have pulled that logic up out of the watchers and into the destination server. This required changing the watcher interfaces to accept service and profile IDs rather than raw authorities. @arminbuerkle: would you like to review this? |
|
Integration test results for 1ef8ed5: success 🎉 |
arminbuerkle
left a comment
There was a problem hiding this comment.
Thanks @adleong for mentioning me.
I looked through the PR and only found a few minor nit-picks.
The implementations itself looks solid though 👍
| log.Fatalf("Failed to listen on %s: %s", *addr, err) | ||
| } | ||
|
|
||
| global, err := config.Global(consts.MountPathGlobalConfig) |
There was a problem hiding this comment.
I'd probably move this into the destination package into the NewServer function.
We don't need to and don't want to set a different cluster domain than what's set in the config so calling it here give us no benefit.
By moving it you reduce the amount of parameters required for NewServer.
Since config.Global is needed for the trust domain too, i'd also move that into the destination package and pass *disableIdentity instead of trustDomain to NewServer
There was a problem hiding this comment.
I think I prefer to keep the config reading in the main and pass the specific values into NewServer. This reduces coupling between the server implementation and the config, and makes the server easier to test.
| // the HTTP default (80) is returned as the port number. If the authority | ||
| // is a pod DNS name then the pod hostname is also returned as the 3rd return | ||
| // value. See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/. | ||
| func GetServiceAndPort(authority string, clusterDomain string) (ServiceID, Port, string, error) { |
There was a problem hiding this comment.
GetServiceAndPort now returns a ServiceID, Port, string and error.
It looks inconsistent that the pod hostname does not have a type while for example port does
In addition returning a hostname from a GetServiceAndPort function seems confusing. IMO it's time to rename the function. Maybe ParseAuthority? Similar to go's url.Parse?
There was a problem hiding this comment.
Great suggestion, thanks.
| // Needs to have `".svc."` to be a valid k8s svc | ||
| if domains[2] != "svc" { | ||
| return ServiceID{}, 0, fmt.Errorf("Invalid k8s service %s", host) | ||
| for i, subdomain := range domains[n-len(suffix):] { |
There was a problem hiding this comment.
If i'm not missing anything you could rewrite this loop into:
// svc.{clusterDomain} != 'host' suffix
if !strings.HasSuffix(host, "svc."+clusterDomain) {
return ServiceID{}, 0, "", fmt.Errorf("Invalid k8s service %s", host)
}which is easier to understand.
| Namespace: domains[1], | ||
| } | ||
| return service, port, "", nil | ||
| } else if n == 3+len(suffix) { |
There was a problem hiding this comment.
There's no else if needed since you're returning in the previous if statement.
If you only use an if you can save on indentations and make it easier to understand.
Signed-off-by: Alex Leong <[email protected]>
|
Integration test results for 4896cc4: success 🎉 |
Continue of #2950. I decided to check for the `clusterDomain` in the config map in web server main for the same reasons as as pointed out here #3113 (comment) It decouples the server implementations from the config. Signed-off-by: Armin Buerkle <[email protected]>
We add support for looking up individual pods in a stateful set with the destination service. This allows Linkerd to correctly proxy requests which address individual pods. The authority structure for such a request is
<pod-name>.<service>.<namespace>.svc.cluster.local:<port>.Fixes #2266
Signed-off-by: Alex Leong [email protected]