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 ipvs metaproxier endpointslice support #84089
Conversation
@khenidak Thanks for taking this on! Could you add some tests that cover the combination of ipvs, dualstack, and endpointslices? |
@robscott the metaproxy is just a thin shell over actual proxy. there is no dualstackness per se, it calls what actual proxies do. A test covering meta proxy itself specially the part it selects which proxy to call might be a good idea, but beyond that is really up to proxy implementation, and is tested separately |
@@ -153,25 +153,30 @@ func (proxier *metaProxier) OnEndpointsSynced() { | |||
// OnEndpointSliceAdd is called whenever creation of a new endpoint slice object | |||
// is observed. | |||
func (proxier *metaProxier) OnEndpointSliceAdd(endpointSlice *discovery.EndpointSlice) { | |||
// noop | |||
proxier.ipv4Proxier.OnEndpointSliceAdd(endpointSlice) |
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.
Does the IP family need to be checked first similar to the OnEndpoints*
methods?
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.
Even if EndpointSlice can contain address in both families should we still check if it contains only v4 or only v6 first before calling both event handlers??
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.
Would metaProxier be used if the dual stack feature gate hadn't been enabled? If not, it seems likely that EndpointSlices would consistently contain both v4 and v6 addresses consistently. Are there other use cases where there might only be v4 or v6 addresses in an EndpointSlice?
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 think this might be over optimization, giving that a slice can grow to a 1000 entry we may actually get opposite results. I understand that you are trying to avoid the call to syncProxyRules()
Let us talk tom, i want to understand more.
/assign |
f0da611
to
9b490e1
Compare
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: khenidak The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
@andrewsykim the filtering is now done at the poxy level. it should work, though i am not totally comfortable with this change. |
I thought we were goign to check if an EndpointSlice is v4 or v6 (by checking it's endpoints) and then call the appropriate proxier from there. That can happen at the meta proxier level. |
@andrewsykim EndpointSlices can contain both v4 and v6 addresses in the addresses field for each Endpoint. This is different than the approach with Endpoints where addresses are separated in different v4 and v6 resources. |
9b490e1
to
8788385
Compare
Gotcha, thanks for clarifying. The problem is that func (proxier *metaProxier) OnEndpointSliceUpdate(old, endpointSlice *discovery.EndpointSlice) {
hasIPv4, hasIPv6 := getIPFamilies(endpointSlice)
if hasIPv4{
proxier.ipv4Proxier.OnEndpointSliceUpdate(old, endpointSlice)
}
if hasIPv6{
proxier.ipv6Proxier.OnEndpointSliceUpdate(old, endpointSlice)
}
} |
Wouldn't that still require skipping in |
Skipping |
// (khenidak) the previous assumption is valid only for single stack | ||
// for dual stack, we have no way for endpointslice (or general endpoint processing) | ||
// to know if this endpoint is being processed for an IPv6 or IPv4 proxier | ||
// TODO: Find a way where the endpoints/slice can be loaded with knowledge | ||
// of proxy/ipfamily and perform the filtering there. |
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.
Maybe our caching structures (endpointsMap and/or endpointSliceCache?) could be updated in the future to make this easier?
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.
We need a strategy covering the both proxies and endpoint processing. But that strategy can not be implemented now it needs a deeper look at the stack. For example, there is a proxy under development that does not care about ipfamily (nftables iirc). That strategy also needs to be ready to handle dualstack services (as in dual cluster IPs). that said, this strategy is way beyond the scope of this PR.
What i am trying to say this PR is a stop gap until this strategy is made, and applied.
@@ -153,25 +153,30 @@ func (proxier *metaProxier) OnEndpointsSynced() { | |||
// OnEndpointSliceAdd is called whenever creation of a new endpoint slice object | |||
// is observed. | |||
func (proxier *metaProxier) OnEndpointSliceAdd(endpointSlice *discovery.EndpointSlice) { | |||
// noop | |||
proxier.ipv4Proxier.OnEndpointSliceAdd(endpointSlice) |
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.
Would metaProxier be used if the dual stack feature gate hadn't been enabled? If not, it seems likely that EndpointSlices would consistently contain both v4 and v6 addresses consistently. Are there other use cases where there might only be v4 or v6 addresses in an EndpointSlice?
// ignore stale services that does not match ipfamily of the proxier | ||
isIpv6Svc := utilnet.IsIPv6(svcInfo.ClusterIP()) | ||
if proxier.isIPv6 != isIpv6Svc { | ||
klog.V(10).Infof("ipvs proxier (ipv6:%v) is ignoring a stale service:%v (ipv6:%v) because it is not of same ipfamily", proxier.isIPv6, svcPortName, isIpv6Svc) | ||
continue | ||
} | ||
|
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.
Is this something we could do in the caches instead of syncProxyRules
? As I understand it, right now a change to a v4 address would be considered a change by the v6 proxier since both addresses would be stored in both caches. If on the other hand we filtered addresses before storing them in the cache, changes would be more accurate by the time they got here.
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 think this would help with what @andrewsykim is asking about. I think https://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/endpoints.go#L129 and https://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/endpoints.go#L175 would be the starting point for that.
skipping/ignoring is what we thought is the best approach the last time we discussed this. Because EndPointSlices carry mixed ip families. I do believe skipping/ignoring is ok not great. but ok. Please do note pods in a dualstack clusters carry dualstack IPs (v4/v6 or v6/v4). As in, a change in pod (create, update, delete) will always result into a change in two EndPoints. That means max of two EndpointSlices will be affected when a pod ips change (only one EndpointSlice if the two endpoints landed in the same slice, which what the controller always attempt to do). So For single stack, no skipping will happen since all endpoints will carry ips from the same family. |
Thanks for the explanation @khenidak! Since EndpointSlice endpoints are tied directly to a Pod, a change affecting a Pod should only change 1 EndpointSlice. So unless there's some kind of case where a v4 or v6 address could change independently of each other for the same Pod, this does seem like a good approach. |
:-) max of two, if you need to overflow into another endpointslice. I will reword. Thanks. |
So to clarify - if I set the |
No. |
Gotcha, so doesn't that mean there are EndpointSlice updates where the updates are exclusively IPv4 or exclusively IPv6 (re: #84089 (comment))? Thinking of a case where pods are dual-stack but some user of the cluster just care about one IP family still. That seems like a pretty likely scenario. |
Thanks! I need to/let us push this forward because EndpointSlice BETA stage has dependency on supporting dualstack. |
serviceUpdateResult := proxy.UpdateServiceMap(proxier.serviceMap, proxier.serviceChanges) | ||
endpointUpdateResult := proxier.endpointsMap.Update(proxier.endpointsChanges) | ||
|
||
staleServices := serviceUpdateResult.UDPStaleClusterIP | ||
// merge stale services gathered from updateEndpointsMap | ||
for _, svcPortName := range endpointUpdateResult.StaleServiceNames { | ||
if svcInfo, ok := proxier.serviceMap[svcPortName]; ok && svcInfo != nil && svcInfo.Protocol() == v1.ProtocolUDP { | ||
// ignore stale services that does not match ipfamily of the proxier | ||
isIpv6Svc := utilnet.IsIPv6(svcInfo.ClusterIP()) |
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.
What about ExternalIP?
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.
A service that has ExternalIPs
must have ClusterIP
AFAIK. Do you know of any case where a service can carry ExternalIPs
but ClusterIP
is empty?
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.
Headless Services (ClusterIP: None) with external IPs?
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.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None
externalIPs:
- 1.1.1.1
selector:
app: nginx
ports:
- port: 80
protocol: TCP
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None 1.1.1.1 80/TCP 62s
@@ -1023,6 +1040,14 @@ func (proxier *Proxier) syncProxyRules() { | |||
klog.Errorf("Failed to cast serviceInfo %q", svcName.String()) | |||
continue | |||
} | |||
|
|||
// ignore services that does not match proxier ipfamily | |||
isIpv6Svc := utilnet.IsIPv6(svcInfo.ClusterIP()) |
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.
Same here, what about ExternalIP?
/milestone v1.17 |
I think @khenidak is pretty busy so I'm going to work on a separate PR that integrates the EndpointSlice address type changes. /hold |
closing in favor of #85246 |
What type of PR is this?
/kind feature
What this PR does / why we need it:
Adds endpointSlice support metaproxier (ipv6 dualstack).
Which issue(s) this PR fixes:
n/a
Special notes for your reviewer:
n/a
Does this PR introduce a user-facing change?:
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:
@robscott FYI..