Skip to content
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

multicluster doesn't work on EKS #4582

Closed
adleong opened this issue Jun 9, 2020 · 4 comments · Fixed by #4588
Closed

multicluster doesn't work on EKS #4582

adleong opened this issue Jun 9, 2020 · 4 comments · Fixed by #4588

Comments

@adleong
Copy link
Member

adleong commented Jun 9, 2020

When attempting to link a source cluster to a target EKS cluster using the linkerd multicluster link command, the service mirror controller in the source cluster logs this error:

(will retry): Inner errors:\n\tEndpoints \"linkerd-gateway-[xxx]\" is invalid: [subsets[0].addresses[0].ip: Invalid value: \"\": must be a valid IP address, (e.g. 10.9.8.7), 
subsets[0].addresses[0].hostname: Invalid value [...]

and fails to create the endpoints object for the gateway mirror.

This happens because the service mirror controller attempts to copy the hostname and IP fields from the gateway service's ingress struct to the EndpointAddress struct of the gateway mirror's endpoints object. In EKS, the ingress struct of the linkerd-gateway service is populated with a hostname and no IP. However, the IP field of EndpointAddress is mandatory.

olix0r added a commit that referenced this issue Jun 9, 2020
Add a note warning users that `multicluster` does not yet work with on
Amazon EKS.
olix0r added a commit that referenced this issue Jun 9, 2020
Add a note warning users that `multicluster` does not yet work with on
Amazon EKS (#4582).
@olix0r
Copy link
Member

olix0r commented Jun 9, 2020

The problem is related to this code where we translate the remote service load balancer state to a local endpoint:

var gatewayEndpoints []corev1.EndpointAddress
for _, ingress := range gateway.Status.LoadBalancer.Ingress {
gatewayEndpoints = append(gatewayEndpoints, corev1.EndpointAddress{
IP: ingress.IP,
Hostname: ingress.Hostname,
})
}

There are at least two problems here:

  1. The IP field is required on the EndpointAddress but is optional on the LoadBalancer.Ingress.
  2. The Hostname field on the EndpointAddress must not be a FQDN, but it will almost always be a FQDN when set on the LoadBalancer.Ingress.

In order to handle this case, we'll need to resolve the remote Hostname via DNS (refreshing after the DNS record's TTL expires) to create an EndpointAddress based on the DNS results; or we can move to approach that avoids creating concrete EndpointAddress types in these cases (perhaps using ExternalService instead?).

@spaghettifunk
Copy link

In Istio they use the ExternalService (I guess is the externalName in the service deployment file) for tackling this issue.

@zaharidichev
Copy link
Member

I did a bit of thinking around this one and here is one potential solution. I might be way off here so correct me if I am wrong. Not sure how feasible it is. I was reviewing #4563 and it occurred to me that EndpointsSlices have all the bells to express what we want. How about the following.. In a scenario where we do not have to have endpoints separately defined on each mirror service (as described in #4535 ), we can have one gateway mirror service that has all the traffic flowing through it. This service mirror can have one endpoints slice that might look like this in the case where we have an LB with a concrete IPv4 address:

apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
  name: gateway-mirror-endpoints
  labels:
    kubernetes.io/service-name: gateway-mirror
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true

and like this in the case where we do not have an IP (EKS)

apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
  name: gateway-mirror-endpoints
  labels:
    kubernetes.io/service-name: gateway-mirror
addressType: FQDN
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "some.fqdn.com"
    conditions:
      ready: true

This way we have a perfectly valid way to express the either/or situation that is not valid with EndpointsSubsets. We need to change the destinations service for this purpose and its API. Namely we cna change WeightedAdd to incorporate the notion of returning fqdn instead of only IPv4/6 addresses and instead of:

type WeightedAddr struct {
	Addr                 *net.TcpAddress
	Weight               uint32           
	MetricLabels         map[string]string 
}
type WeightedAddr struct {
	Addr                 *Addr // <----- either *net.TcpAddress or string (fqdn)
	Weight               uint32           
	MetricLabels         map[string]string 
}

Now when the proxy receives a WeightedAddr that does not have a concrete net.TcpAddress but instead specifies a FQDN, it will do the DNS resolution much like it does DNS fallback when there are no endpoints coming from the destination service. This time however, it will have all the metrics labels, weight, auth overrides, etc, etc. Note that this should work with endpoints slices but if we instead want to do it with the current k8s primitives and not rely on such a new k8s feature, we can always have an EndpointsAddress that has the Hostname field set and the addresses populated with some dummy value. The latter feels like a hack.

@adleong @olix0r @grampelberg WDYT. Does that sound plausible?

@grampelberg
Copy link
Contributor

@zaharidichev you can't run a k8s version that has EndpointSlices in it yet =( I think the long term solution is the simplification that you've recommended with exernal name services. AFAICT the most optimal short term solution is going to be polling the hostname and updating the endpoint with the right ip address.

adleong added a commit that referenced this issue Jun 15, 2020
Fixes #4582 

When a target cluster gateway is exposed as a hostname rather than with a fixed IP address, the service mirror controller fails to create mirror services and gateway mirrors for that gateway.  This is because we only look at the IP field of the gateway service.

We make two changes to address this problem:
 
First, when extracting the gateway spec from a gateway that has a hostname instead of an IP address, we do a DNS lookup to resolve that hostname into an IP address to use in the mirror service endpoints and gateway mirror endpoints.

Second, we schedule a repair job on a regular (1 minute) to update these endpoint objects.  This has the effect of re-resolving the DNS names every minute to pick up any changes in DNS resolution.

Signed-off-by: Alex Leong <alex@buoyant.io>
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants