SignalR example

Hi guys,

I'm porting all our projects from nginx to traefik because of... You know... It's better :slight_smile:

All API's works like a charm and really appreciate what you provide to community.

However, I have an issue with setting up signalR.

Basically, it's project consisted of API and WebSocket server based on Microsoft technology signalR into docker-swarm image.

So, the question is... Is there anybody in this community who has the same use case so he/she can share example with us.

In case mentioned is not possible I would like you to dig a bit in my config.

I can confirm I've made my docker-compose by following these instructions: WebSockets not working after migrating to Traefik 2.0 and How to make WebSockets work with Traefik 2.0 (setting up Rancher)
but without success.

Here is also a bit modified content of my docker-compose

services:
    nexus:
      image: organization/image:version
      environment:
        - redis=true
        - ASPNETCORE_ENVIRONMENT=Production
      deploy:
        placement:
          constraints: [node.role == manager]
        labels:
          - "traefik.enable=true"
          - "traefik.http.services.nexus.loadbalancer.server.port=5000"
          - "traefik.http.services.nexus.loadbalancer.passhostheader=true"
          - "traefik.http.routers.nexus.rule=Host(`{my-host-goes-here}`)"
          - "traefik.http.routers.nexus.entrypoints=web"
          - "traefik.http.routers.nexus.middlewares=traefik-https-redirect"
          - "traefik.http.routers.nexus-secure.rule=Host(`{my-host-goes-here}`)"
          - "traefik.http.routers.nexus-secure.entrypoints=websecure"
          - "traefik.http.routers.nexus-secure.tls=true"
          - "traefik.docker.network=lab"
          - "traefik.http.services.nexus.loadbalancer.sticky=true"
          - "traefik.http.routers.nexus.middlewares: redirect@file"
          - "traefik.http.middlewares.nexus.headers.customrequestheaders.X-Forwarded-Proto=https"
          - "traefik.http.middlewares.nexus-secure.headers.customrequestheaders.X-Forwarded-Proto=https"
    redis:
      image: redis:latest
  
  networks:
    default:
      external:
        name: lab

In case you would need more decent playground just let me know and I can provide public docker image where I'm going to cut all security info from docker-compose.

Thank you in advance.

Appreciate a lot because of we already loose like 100ish hours as a small team on making this works but without success. So, this is last try before we start figuring out backup plans with nginx.

I had the same problem with Traefik too.

I have been using Traefik v1 and WebSockets worked like a charm. I didn't need to set up things, except to add a label for WSS to use HTTPS.

I have tried various ways, I even tried to add Nginx as the middle layer between Traefik and SignalR
Traefik --> Nginx --> Redis --> SignalR but I couldn't manage it.

I had to move back to v1 but I really want to move to new Traefik..

It would be also helpful if someone can share working example with pure ws or even socket.io.

@almird What is your problem with signalr ?
What doesn't work, Error message ? It's not clear in your post.

I get it working with traefik 1.x and 2.1, but with kubernetes so my configuration will not help you.
For me the difficulties was related to multi replicate which need specific configuration.
It need two things :

  1. Use a backplane (Azure Signalr or Redis), see [this link].(https://docs.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-3.1)
  2. If redis backplane, you also need to enable sticky session
    And in this case you only want the sticky session to be applied to the signalr part.

For kubernetes users, an exemple configuration for separate signalr configuration and enable sticky session on it.
You will need traefik 2.1 or greater as sticky session definition for kubernetes has been introduced in this version.

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: example-ingress
spec:
  entryPoints:
  - secure
  routes:
  - kind: Rule
    match: Host(`myapi.mydomain.app`) && PathPrefix(`/signalr`)
    services:
    - kind: Service
      name: api-signalr
      port: 80
      sticky:
        cookie: {}
  - kind: Rule
    match: Host(`myapi.mydomain.app`) && PathPrefix(`/`)
    services:
    - kind: Service
      name: api
      port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: api
---
apiVersion: v1
kind: Service
metadata:
  name: api-signalr
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: api

Hello there,

here are some logs:

2019-12-23T12:57:59.733621082Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    | info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
2019-12-23T12:57:59.733654041Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    |       Request starting HTTP/1.1 GET http://dev-nexus.klinkplatform.com/messages?id=qNmUjCHPHJDDas5LnzH2Dg
2019-12-23T12:57:59.733663125Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    | info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
2019-12-23T12:57:59.733684766Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    |       CORS policy execution successful.
2019-12-23T12:57:59.828288784Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    | fail: Microsoft.AspNetCore.SignalR.HubConnectionContext[5]
2019-12-23T12:57:59.828323607Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    |       Failed connection handshake.
2019-12-23T12:57:59.828595095Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    | info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
2019-12-23T12:57:59.828635620Z nexus_nexus.1.mn06lkil33sf@vmi277066.contaboserver.net    |       Request finished in 94.7462ms 101

Also, here is configuration:

version: '3.3'

services:
  nexus:
    image: {my-private-signalr-docker-image-in-dotnet-core-2.2}
    networks:
        - lab
        - signalr
    environment:
        - redis=true
        - ASPNETCORE_ENVIRONMENT=Production
    deploy:
      placement:
        constraints: [node.role == manager]
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.nexus.loadbalancer.server.port=5000"
        - "traefik.http.services.nexus.loadbalancer.passhostheader=true"
        - "traefik.http.routers.nexus.rule=Host(`dev-nexus.klinkplatform.com`)"
        - "traefik.http.routers.nexus.entrypoints=web"
        - "traefik.http.routers.nexus.middlewares=traefik-https-redirect"
        - "traefik.http.routers.nexus-secure.rule=Host(`dev-nexus.klinkplatform.com`)"
        - "traefik.http.routers.nexus-secure.entrypoints=websecure"
        - "traefik.http.routers.nexus-secure.tls=true"
        - "traefik.docker.network=lab"
        - "traefik.http.services.nexus.loadbalancer.sticky=true"
        - "traefik.http.middlewares.nexus.headers.customrequestheaders.X-Forwarded-Proto=https"
  redis:
    image: redis:latest
    networks:
        - signalr
    deploy:
      placement:
        constraints: [node.role == manager]
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.nexus.loadbalancer.server.port=6379"
        - "traefik.http.services.redis.loadbalancer.sticky=true"
networks:
  lab:
    external: true
  signalr:
    external: true

Since signalR connection is contained of: http POST request for negotiation and WS connection. I can see that http POST request passes correctly but WS is constantly repeating which leads me to the conclusion that something isn't OK with redis sticky sessions.

So, please you would help me a lot if you take a look into my redis docker image labels. Thanks in advance.

Bellow you could see this weird behavior. The thing is: It constantly retry to negotiate and to establish ws connection with sucess but it repeat it again again and again.

I haven't used signalR, but had a similar issue with a self-contained backend that uses websockets. I resolved my issue by explicitly configuring the upstream server to accept proxy protocol.

In the end of the day... When I finally found some time I figured out that it was not due to signalR or WS specifics, but it's as usual "human error" :smile:
I was laughing when I figured out real reason why it broke...

Didn't create signalr dedicated network in traefik yaml config but used it in service yaml config for docker swarm.

All in all when I putted it in traefik yaml config everything works like a charm

do you mind share the yaml file? thx