-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Envoy v1.20.0 slower to re-initialize dynamic listeners #18616
Comments
We are seeing the same in Istio. Essentially it looks like when a listener drains, some (maybe 50%, but I didn't get a large sample size to say exactly 50/50) hang. netstat shows them stuck in Recv-Q. Suspecting the reuseport stuff, we disabled it via runtime flag. When we did this, we got a different error - NACKs from address already in use. |
Same for Contour, except set enable reuseport setting to false, rather than use runtime config |
Regarding enable reuse port: The underlying expectation is that kernel should requeue the socket of the dying passive socket(old listener) to another passive socket(new listener). I am not sure which piece went broken. |
Unfortunately this is not the way it works. Sockets are not re-queued. A lot of care was put into make sure that we don't wind up with dead sockets that are never accepted, and I'm surprised to hear this is happening. I'm happy to look into this if we can come up with a more self contained repro? |
I am interested in the behavior of reuse port disabled. Since 1.20 the underlying fd is duplicated anyway regardless the old listener's listen fd is closed or not. See also ListenerManagerImpl::setNewOrDrainingSocketFactory |
working on something smaller, was able to get a little spike to do what is happening with Contour, was able to reproduce earlier in this smaller setup but now its not reproducible consistently enough to share yet 😅 |
This is what I watched today. Ideally we want new listener fd always refers the underlying sock as fd=64 and fd=58. However, I do see below unexpected scenario.
|
I think what you are seeing here looks correct to me. Each worker as its own fd, which is duplicated. @lambdai if you have a repro, can you check the following. In the case of a listener update, we should be "cloning" the socket factory which goes through this code: envoy/source/server/listener_impl.cc Lines 118 to 132 in 5f7d6ef
Can you make sure this code is being invoked during the update and duplicating the socket for each worker? Assuming this is working, the only things I can think of are something like:
It's hard for me to tell in the above dump exactly what is going on (how many listeners, whether duplication occurred, etc.). If you can repro, perhaps if you add some more logging we can have a better idea of what is going on? |
This piece seems good. I may have found the root cause. During the in place update, a ListenerImpl is created and the ListenSocketFactory is cloned. The listen sockets are cloned, too. However, per in place update, the old listener is not entirely drained. The old ListenSocketFactory::closeAllSockets is not invoked immediately after the new listener is warmed up. The old listen sockets are closed in RAII style, which is after the drain_timeout. That says, OS will schedule accept sockets to the old listen fd and new listen fd, but no handle is watching and accept old listen fd. These sockets are lost. |
I have an experimental fix at local that takes ownership of the listener socket factory if the update is in place update. |
OK, this makes sense. I'm OK with a targeted fix, but I remember when we originally reviewed the in-place update changes, I commented on how it seemed strange that we were creating an entire new listener that we don't really use. Can we do additional cleanups here to prevent this type of issue? For an in-place update perhaps there should be no new socket factory at all? |
I transfer the ownership of listen socket factory to the in place updated new listener, all looks good until I hit the case updating an updating listener. Still fixing... |
OK lmk if you need help fixing. Just ping me on Slack. |
Description:
It appears Envoy v1.20.0 is a bit slower to re-initialize dynamic listeners for which configuration has changed.
As part of regular dependency bumps, when bumping Contour's dependency on Envoy from v1.19.1 to v1.20.0 (see PR) we have seen our end-to-end tests fail/take quite a lot longer to run to completion. These test repeatedly create/delete k8s Ingress/Contour HTTPProxy/Gateway API *Route objects that the Contour controller programs Envoy dynamically with over xDS. The tests update these k8s resources and then assert requests to Envoy succeed. We've seen the time taken for requests to succeed when config is updated increase substantially when bumping to v1.20.0 with the same Contour controller code.
With a
git bisect
of commits betwen v1.19.0 and v1.20.0, it seems that we can narrow this slowdown to this commit that for one changes the default for Envoy to use theSO_REUSPORT
socket option but also changes how listener updates work. Granted this is not the most exact/minimal test case but it appears this commit might be related.Repro steps:
I can try to work up a smaller repro but for now, running the Contour
e2e
tests has been how we have experimented, capturing logs and fiddling with timeouts etc.:kind
,go
, andginkgo
are installedmake e2e
make e2e
Theories/mitigations
The text was updated successfully, but these errors were encountered: