reverse-proxy icon indicating copy to clipboard operation
reverse-proxy copied to clipboard

Proxy SignalR -- document how

Open zqlovejyc opened this issue 2 years ago • 25 comments

Using Yarp proxy SignalR, I frequently encounter connection failures, about 10 requests out of 3 failed, but ocelot did not encounter this situation

Below is my routing configuration

{
    "Routes": {
        "SignalRRoute": {
            "ClusterId": "SignalRCluster",
            "CorsPolicy": "CorsPolicy",
            "AuthorizationPolicy": "JwtPolicy",
            "Match": {
                "Path": "/BaizeHub/{*any}"
            },
            "Transforms": [
                {
                    "PathRemovePrefix": "/BaizeHub"
                },
                {
                    "RequestHeadersCopy": "true"
                },
                {
                    "RequestHeaderOriginalHost": "true"
                },
                {
                    "RequestHeader": "Upgrade",
                    "Set": "WebSocket"
                },
                {
                    "RequestHeader": "Connection",
                    "Set": "Upgrade"
                },
                {
                    "X-Forwarded": "Set",
                    "For": "Append",
                    "Proto": "Append",
                    "Prefix": "Append",
                    "HeaderPrefix": "X-Forwarded-"
                }
            ]
        }
    },
    "Clusters": {
        "SignalRCluster": {
            "LoadBalancingPolicy": "PowerOfTwoChoices",
            "Destinations": {
                "Message/Destination1": {
                    "Address": "https://172.30.50.13:5400"
                },
                "Message/Destination2": {
                    "Address": "https://172.30.50.12:5400"
                }
            },
            "HttpClient": {
                "DangerousAcceptAnyServerCertificate": true
            },
            "HttpRequest": {
                "Version": "1.1",
                "Timeout": "00:10:00"
            },
            "HealthCheck": {
                "Active": {
                    "Enabled": "true",
                    "Interval": "00:00:10",
                    "Timeout": "00:00:10",
                    "Policy": "ConsecutiveFailures",
                    "Path": "/Health"
                },
                "Passive": {
                    "Enabled": true,
                    "Policy": "TransportFailureRate",
                    "ReactivationPeriod": "00:00:10"
                }
            }
        }
    }
}
  • Yarp.ReverseProxy 1.0.0-preview.12.21328.2
  • Linux

zqlovejyc avatar Jul 20 '21 08:07 zqlovejyc

Triage: What are the failures you're getting? Can you share server, client and YARP logs?

karelz avatar Jul 20 '21 18:07 karelz

Triage: What are the failures you're getting? Can you share server, client and YARP logs?

Yarp logs: 2021-07-21.log

Client: 1.Success image

2.Fail image

image

zqlovejyc avatar Jul 21 '21 01:07 zqlovejyc

What does the Notify request status translate to? Aborted, failed, etc.?

2021-07-21 09:12:22.9642 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.Proxying : Proxying to https://172.30.50.13:5400/Notify?id=llr5GncW9Me-mzNuWzMJFQ&access_token=***
2021-07-21 09:12:22.9993 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.Proxying : Proxying to https://172.30.50.12:5400/Notify?id=llr5GncW9Me-mzNuWzMJFQ
2021-07-21 09:12:28.8406 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.ErrorProxying : ResponseBodyCanceled: Copying the response body was canceled.

I'm suspicious that there are two calls to Notify, one with the access_token and one without. I can't tell from here which was cancelled.

That cancellation is the only failure reported in the YARP logs. You should only be getting that cancellation error if the client disconnected. If you enable full Debug Microsoft logs for the YARP process that may give more useful details.

Tratcher avatar Jul 21 '21 03:07 Tratcher

Are sticky sessions enabled?

davidfowl avatar Jul 21 '21 03:07 davidfowl

We should document how to make SignalR work with YARP on the main docs site after we resolve this.

davidfowl avatar Jul 21 '21 03:07 davidfowl

Are sticky sessions enabled?

not enabled

zqlovejyc avatar Jul 21 '21 05:07 zqlovejyc

You need sticky sessions to make SignalR work on multiple servers

davidfowl avatar Jul 21 '21 05:07 davidfowl

What does the Notify request status translate to? Aborted, failed, etc.?

2021-07-21 09:12:22.9642 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.Proxying : Proxying to https://172.30.50.13:5400/Notify?id=llr5GncW9Me-mzNuWzMJFQ&access_token=***
2021-07-21 09:12:22.9993 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.Proxying : Proxying to https://172.30.50.12:5400/Notify?id=llr5GncW9Me-mzNuWzMJFQ
2021-07-21 09:12:28.8406 [Info] [0] Yarp.ReverseProxy.Forwarder.HttpForwarder+Log.ErrorProxying : ResponseBodyCanceled: Copying the response body was canceled.

I'm suspicious that there are two calls to Notify, one with the access_token and one without. I can't tell from here which was cancelled.

That cancellation is the only failure reported in the YARP logs. You should only be getting that cancellation error if the client disconnected. If you enable full Debug Microsoft logs for the YARP process that may give more useful details.

The full logs: 2021-07-21.log

zqlovejyc avatar Jul 21 '21 05:07 zqlovejyc

You need sticky sessions to make SignalR work on multiple servers

Maybe I know the reason, is there an IpHash LoadBalancingPolicy?

zqlovejyc avatar Jul 21 '21 05:07 zqlovejyc

I use IpHashLoadBalancingPolicy solved this problem.

zqlovejyc avatar Jul 21 '21 07:07 zqlovejyc

Sticky sessions also solves this problem. you should try using it.

davidfowl avatar Jul 21 '21 07:07 davidfowl

Sticky sessions also solves this problem. you should try using it.

Let me have a try,thank you!

zqlovejyc avatar Jul 21 '21 08:07 zqlovejyc

Sticky sessions also solves this problem. you should try using it.

"SessionAffinity": { 
          "Enabled": true, 
          "Policy": "CustomHeader",
          "FailurePolicy": "Redistribute",
	  "AffinityKeyName": "Cookie"
  }

I configured it like this, but it doesn't work

zqlovejyc avatar Jul 21 '21 08:07 zqlovejyc

Try:

"SessionAffinity": { 
          "Enabled": true,
	  "AffinityKeyName": "Yarp.Session"
  }

Tratcher avatar Jul 21 '21 17:07 Tratcher

Triage: We agreed above to document how to do it -- changing title.

karelz avatar Jul 27 '21 18:07 karelz

@zqlovejyc - please leave this open as we are using it to track improving the documentation for this area.

samsp-msft avatar Aug 09 '21 17:08 samsp-msft

Is there any documentation on how to do this yet?

Lukejkw avatar Jan 13 '22 12:01 Lukejkw

@Lukejkw what issues are you hitting? Have you tried the session affinity setting shown above?

Tratcher avatar Jan 13 '22 19:01 Tratcher

@Lukejkw what issues are you hitting? Have you tried the session affinity setting shown above?

No issue per se.

I'm evaluating YARP for an internal gateway where SignalR will be used for real-time communication. I was looking for detailed documentation on if this would work - particularly in a distributed application with multiple SignalR server instances.

Lukejkw avatar Jan 24 '22 09:01 Lukejkw

There's not a lot of detail to give, enabling session affinity is the main requirement. WebSockets, Long Polling, and SSE should just work.

Tratcher avatar Jan 25 '22 21:01 Tratcher

Fair enough.

Are there any implications to the session affinity requirement if you are running multiple gateway and downstream signalr services instances?

Or does the cookie/header contain the necessary information to route to the correct downstream service regardless of which gateway instance is hit?

Lukejkw avatar Jan 26 '22 05:01 Lukejkw

The affinity cookie is encrypted so you'll have to have shared data protection keys across instances. See https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/web-farm?view=aspnetcore-6.0#data-protection

Otherwise, as long as the gateways all have the same config it should work. The destination id is used as the session affinity key, so that should be stable across instances.

Tratcher avatar Jan 26 '22 16:01 Tratcher

Great to know. Thanks.

Is the destination ID manually defined or determined dynamically based on the downstream service instance responding to the request?

In a kubernetes environment you would forward traffic from the gateway to a load balancer. So you would define one downstream destination. Would this pose a problem for session affinity and by extension SignalR?

Lukejkw avatar Jan 26 '22 17:01 Lukejkw

Is the destination ID manually defined or determined dynamically based on the downstream service instance responding to the request?

That depends on your config provider. For the Json config it's manual. Not sure about the Kubernetes provider. @MihaZupan?

In a kubernetes environment you would forward traffic from the gateway to a load balancer. So you would define one downstream destination. Would this pose a problem for session affinity and by extension SignalR?

You don't need session affinity if you only have one destination, it's the load balancer that needs to implement affinity. Not sure why you have both a gateway and a load balancer though, why not combine them?

Tratcher avatar Jan 26 '22 19:01 Tratcher

I’m referring to the kubernetes service which acts as a load balancer between pods. Session affinity can be set there too so that might be the ticket.

Thanks so much for the help.

Lukejkw avatar Jan 26 '22 19:01 Lukejkw