503 Service Unavailable from proxy with large number of connections

After upgrading linkerd-control-plane from version 1.9.3 (stable-2.12.1) to version 1.12.3 (stable-2.13.3), I started getting “503 service unavailable” responses when a large number of connections are used (~100 connections or higher).
When I remove the linkerd-proxy sidecar, everything works without any errors.

The network topology is a pod-to-pod HTTP communication. Both the client and the server pods have only one pod instance.
I am using fortio as a load-generation tool.

Please note that this issue started only after upgrading the linkerd-control-plane chart.

I configured the log level of the linkerd-proxy sidecar to debug and found the following logs that I think may be relevant:

DEBUG ThreadId(01) inbound:accept{client.addr=}:server{port=8080}:http: linkerd_proxy_http::server: The client is shutting down the connection res=Err(hyper::Error(Io, Custom { kind: NotConnected, error: "server: Transport endpoint is not connected (os error 107)" }))
DEBUG ThreadId(01) inbound:accept{client.addr=}: linkerd_app_core::serve: Connection closed reason=connection error: server: Transport endpoint is not connected (os error 107)
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_tls::server: Peeked bytes from TCP stream sz=0
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_tls::server: Attempting to buffer TLS ClientHello after incomplete peek
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_tls::server: Reading bytes from TCP stream buf.capacity=8192
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_tls::server: Read bytes from TCP stream buf.len=108
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_detect: Detected protocol protocol=Some(HTTP/1) elapsed=3.17µs
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_proxy_http::server: Creating HTTP service version=HTTP/1
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_proxy_http::server: Handling as HTTP version=HTTP/1
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}:http: linkerd_app_inbound::policy::http: Request authorized server.group= server.kind=default server.name=all-unauthenticated route.group= route.kind=default route.name=probe authz.group= authz.kind=default authz.name=probe client.tls=None(NoClientHello) client.ip=
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}:http: linkerd_proxy_http::server: The client is shutting down the connection res=Ok(())
DEBUG ThreadId(02) daemon:admin{listen.addr=}:accept{client.addr=}: linkerd_app_core::serve: Connection closed


DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}: linkerd_detect: Detected protocol protocol=Some(HTTP/1) elapsed=4.51µs
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}: linkerd_proxy_http::server: Creating HTTP service version=HTTP/1
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}: linkerd_app_outbound::sidecar: Using ClientPolicy routes
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}: linkerd_proxy_http::server: Handling as HTTP version=HTTP/1
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http: linkerd_app_outbound::http::logical::policy::router: Selected route meta=RouteRef(Default { name: "http" })
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http: linkerd_stack::loadshed: Service has become unavailable
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http: linkerd_stack::loadshed: Service shedding load
INFO ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http:rescue{client.addr=}: linkerd_app_core::errors::respond: HTTP/1.1 request failed error=logical service service unavailable error.sources=[service unavailable]
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http: linkerd_app_core::errors::respond: Handling error on HTTP connection status=503 Service Unavailable version=HTTP/1.1 close=true
DEBUG ThreadId(01) outbound:accept{client.addr=}:proxy{addr=}:http: linkerd_proxy_http::server: The client is shutting down the connection res=Ok(())
DEBUG ThreadId(01) outbound:accept{client.addr=}: linkerd_app_core::serve: Connection closed

I’ll appreciate your assistance with this issue.

Can you check how does the CPU consumption of the proxy containers look like? If that’s the bottleneck, you can play with the proxy.resources.cpu and proxy.cores settings.

Anything particular about the server main container we should know about? Are you able to reproduce this with a simple image like nginx?

Hi, thanks for your reply.
I don’t think it’s a resource (CPU/Memory) consumption issue. The containers (application and the proxies) don’t have any limits defined, and they are hosted on a Node with enough CPU (8 cores).

The server is just a simple HTTP server written in GO for testing purposes. If you are interested, its implementation can be found here.

I suspect the number of open connections between the client and server causes this issue. Even with a low number of requests per second but a large number of open connections, the client receives the 503’s responses.
Is there a configuration that limits the number of open connections? At first, I suspected the new Circuit Breaking feature that was introduced in version 2.13 but I didn’t explicitly enable it.

The exact same setup works perfectly with linkerd-control-plane version 1.9.3 (stable-2.12.1) but fail with version 1.12.3 (stable-2.13.3)