Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upSupport pod ip and service cluster ip lookups in the destination service #3595
Conversation
for listener, port := range ss.listeners { | ||
ss.endpoints.Unsubscribe(ss.id, port, "", listener) | ||
ss.endpoints.Unsubscribe(ss.service, port, "", listener) | ||
ss.endpoints.Subscribe(id, port, "", listener) |
This comment has been minimized.
This comment has been minimized.
zaharidichev
Oct 23, 2019
Member
Maybe this questions is quite silly, but just trying to understand things a little better. Why is this error not handled here. Is it because the Svc().Informer()
is guaranteed to not return services of type ExternalName
? What happens if this service changes to an external type right before you hit the k8s API?
This comment has been minimized.
This comment has been minimized.
adleong
Oct 23, 2019
Author
Member
This is not a silly question, it's actually a great point.
We should probably handle errors here by sending a NoEndpoints(exists = false)
message to the subscribers.
Looking good I was wondering about support for pods with Besides that, I just found a couple of nits described below, plus the comment of the |
This comment has been minimized.
This comment has been minimized.
To clarify over my previous comment, the log shows the "Pod IP conflict" at startup, and the Get call does actually return an answer, but not necessarily corresponding to the right pod. Note that |
This comment has been minimized.
This comment has been minimized.
Nice catch, @alpeb. I think the simplest solution would be to omit pods which have hostNetwork true. |
This comment has been minimized.
This comment has been minimized.
I've built and pushed an image from this branch, including the required proxy changes under |
This comment has been minimized.
This comment has been minimized.
Appears to work as expected; however it seemed that the emojivoto scrapes weren't discovered properly until emojivoto was reinjected...
|
This comment has been minimized.
This comment has been minimized.
Perhaps prometheus was reusing long lived connections? I would expect that restarting prometheus would also cause everything to get picked up properly. |
This comment has been minimized.
This comment has been minimized.
On Mon, Nov 4, 2019 at 14:01 Alex Leong ***@***.***> wrote:
Appears to work as expected; however it seemed that the emojivoto scrapes
weren't discovered properly until emojivoto was reinjected...
Perhaps prometheus was reusing long lived connections? I would expect that
restarting prometheus would also cause everything to get picked up properly.
This was following a control plane upgrade, so Prometheus should have been
restarted?
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#3595?email_source=notifications&email_token=AAB2YYW2PYDUGOEDFJJSGYDQSCLT5A5CNFSM4JBTSZX2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDA3XGQ#issuecomment-549567386>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAB2YYU5JJB4POXN37ITXLLQSCLT5ANCNFSM4JBTSZXQ>
.
--
Oliver Gould <ver@buoyant.io>
|
This enables discovery (and mTLS) for HTTP traffic to private-network IP addresses, on the assumption that they are more than likely part of the local cluster. The set of discoverable networks may be configured via the environment. This depends on linkerd/linkerd2#3595 returning resolutions for addresses.
This comment has been minimized.
This comment has been minimized.
To see this in action:
This will show metrics for requests from Prometheus to other pods in the cluster. Notice that these metrics have full dst metadata including pod name, etc, and have |
author Alex Leong <alex@buoyant.io> 1569519892 -0700 committer Alex Leong <alex@buoyant.io> 1573578432 -0800 checkpoint Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
Signed-off-by: Alex Leong <alex@buoyant.io>
I added some comments and questions regarding the new IP watcher. I'll take look at the unit test and do more testing tomorrow. |
return []string{""}, fmt.Errorf("object is not a service") | ||
}}) | ||
|
||
k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ |
This comment has been minimized.
This comment has been minimized.
ihcsim
Nov 26, 2019
Member
How does this work with the AddEventHandler
in the endpoint_watcher.go
? Will one override the other? Or is this a separate Informer
instance?
This comment has been minimized.
This comment has been minimized.
AddFunc: iw.addPod, | ||
DeleteFunc: iw.deletePod, | ||
UpdateFunc: func(_ interface{}, obj interface{}) { | ||
iw.addPod(obj) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
adleong
Dec 7, 2019
Author
Member
since pod IP addresses are immutable once assigned, I don't think so.
} | ||
} | ||
} | ||
objs, err = iw.k8sAPI.Pod().Informer().GetIndexer().ByIndex(podIPIndex, clusterIP) |
This comment has been minimized.
This comment has been minimized.
ihcsim
Nov 26, 2019
Member
Do we need to add the corresponding k8sAPI.Pod().Informer().AddIndexers()
to the NewAPIWatcher()
constructor, like what you did for Svc
?
Also, how come the pod informer knows about clusterIP
?
This comment has been minimized.
This comment has been minimized.
adleong
Dec 7, 2019
Author
Member
the endpoints watcher has already added the pod ip indexer so we don't need to do it.
I'm using the name clusterIP
a bit more broadly to mean the ip address of a resource in the cluster. This can be the ip address of a pod, or the cluster ip of a service.
|
||
// If the service doesn't yet exist, create a stub for it so the listener can | ||
// be registered. | ||
ss, ok := iw.publishers[clusterIP] |
This comment has been minimized.
This comment has been minimized.
ihcsim
Nov 26, 2019
Member
IIUC, the writer lock is only needed when if !ok
, right? Is this the same as:
ss, ok := iw.getServiceSubscription()
if ! ok {
iw.Lock()
defer iw.Unlock()
ss = &serviceSubscriptions{
clusterIP: clusterIP,
listeners: make(map[EndpointUpdateListener]Port),
endpoints: iw.endpoints,
log: iw.log.WithField("clusterIP", clusterIP),
}
...
}
return ss
This comment has been minimized.
This comment has been minimized.
adleong
Dec 7, 2019
Author
Member
I'm not sure, but we might need a read lock even in the ok
case. The way we have it is slightly more conservative, but as far as I can tell it's idiomatic and makes things easier to reason about.
} | ||
ss := iw.getOrNewServiceSubscriptions(pod.Status.PodIP) | ||
|
||
ownerKind, ownerName := iw.k8sAPI.GetOwnerKindAndName(pod, true) |
This comment has been minimized.
This comment has been minimized.
ihcsim
Nov 26, 2019
Member
Won't ss.pod
return the same thing? This seems like a repetition of what iw.getOrNewServiceSubscriptions()
did to initialize the PodSet
in the serviceSubscription
, with the exception that iw.getOrNewServiceSubscriptions()
also checks for pod.spec.HostNetwork
.
This comment has been minimized.
This comment has been minimized.
expectedNoEndpointsServiceExists bool | ||
expectedError bool | ||
}{ | ||
{ |
This comment has been minimized.
This comment has been minimized.
ihcsim
Nov 26, 2019
•
Member
These use test cases seem identical(-ish) to those defined in endpoints_watcher_test.go
. Any reasons why we need to re-run them here? Do the behaviour of the endpoints_watcher
changes after it's being wrapped?
This comment has been minimized.
This comment has been minimized.
adleong
Dec 7, 2019
Author
Member
the way the lookups works is different, with lookups by ip instead of by service name. I think it's worth testing these conditions.
This comment has been minimized.
This comment has been minimized.
ss = &serviceSubscriptions{ | ||
clusterIP: clusterIP, | ||
listeners: make(map[EndpointUpdateListener]Port), | ||
endpoints: iw.endpoints, | ||
log: iw.log.WithField("clusterIP", clusterIP), | ||
} |
This comment has been minimized.
This comment has been minimized.
alpeb
Dec 5, 2019
Member
It seems there's still an issue with pods with hostNetwork: true
. If clusterIP
corresponds to one or more than one such pods, even though they'll be ignored below, ss
still gets set and will get returned at the end of this method.
This comment has been minimized.
This comment has been minimized.
adleong
Dec 7, 2019
Author
Member
hmmm, what do you think the correct behavior should be here?
getOrNewServiceSubscriptions should always return a serviceSubscriptions even if the ip address doesn't correspond to any pods or services in the cluster. (or it only corresponds to hostNetwork pods). In either of these cases, the returned serviceSubscriptions should have ss.pod = PodSet{}
.
This causes updatePod
to send a listener.Add
with a size 0 pod set. Perhaps we simply need a check here to skip the add if the size of the pod set is 0.
Is there another issue that I'm missing?
adleong commentedOct 17, 2019
Fixes #3444
Fixes #3443
Background and Behavior
This change adds support for the destination service to resolve Get requests which contain a service clusterIP or pod ip as the
Path
parameter. It returns the stream of endpoints, just as ifGet
had been called with the service's authority. This lays the groundwork for allowing the proxy to TLS TCP connections by allowing the proxy to do destination lookups for the SO_ORIG_DST of tcp connections. When that ip address corresponds to a service cluster ip or pod ip, the destination service will return the endpoints stream, including the pod metadata required to establish identity.Prior to this change, attempting to look up an ip address in the destination service would result in a
InvalidArgument
error.Updating the
GetProfile
method to support ip address lookups is out of scope and attempts to look up an ip address with theGetProfile
method will result inInvalidArgument
.Implementation
We do this by creating a
IPWatcher
which wraps theEndpointsWatcher
and supports lookups by ip.IPWatcher
maintains a mapping up clusterIPs to service ids and translates subscriptions to an IP address into a subscription to the service id using the underlyingEndpointsWatcher
.Since the service name is no longer always infer-able directly from the input parameters, we restructure
EndpointTranslator
andPodSet
so that we propagate the service name from the endpoints API response.Testing
This can be tested by running the destination service locally, using the current kube context to connect to a Kubernetes cluster:
Then lookups can be issued using the destination client:
Service cluster ips and pod ips can be used as the
path
argument.