Rails 4 + Websocket-rails + Passenger + Nginx + Load balancer

670 Views Asked by At

I've added some features to a couple of our web apps that needs websocket-rails. Everything works fine in development, but I am not sure how to deploy all this in our production environment since it's a bit more complex.

The production setup:

  • 1 server used as a Load balancer (Nginx).
  • 2 servers used as web servers, where our rails apps run using Nginx and Passenger (both servers are identical).
  • Several other servers used by the app servers but I believe they are irrelevant for this question.
  • All sites are running on HTTPS.

Load balancer configs

Here's an example for one of the sites, the others have similar configs:

upstream example {
    ip_hash;
    server xx.xx.xx.xx:443;
    server xx.xx.xx.xx:443;
}

server {
  listen   80;
  listen 443 ssl;

  ssl on;
  ssl_certificate /etc/nginx/ssl/example.chained.crt;
  ssl_certificate_key /etc/nginx/ssl/example.key;

  server_name example.com;
  rewrite ^(.*) https://www.example.com$1 permanent;
}

server {
  listen   80;
  listen 443 ssl;

  ssl on;
  ssl_certificate /etc/nginx/ssl/example.chained.crt;
  ssl_certificate_key /etc/nginx/ssl/example.key;

  server_name www.example.com;
  if ($ssl_protocol = "") {
    rewrite     ^   https://$server_name$request_uri? permanent;
  }

  client_max_body_size 2000M;

  location /css { root /home/myuser/maintenance; }
  location /js { root /home/myuser/maintenance; }
  location /img { root /home/myuser/maintenance; }
  location /fonts { root /home/myuser/maintenance; }

  error_page 502 503 @maintenance;
  location @maintenance {
    root /home/myuser;
    if ($uri !~ ^/maintenance/) {
      rewrite ^(.*)$ /maintenance/example.html break;
    }
  }

  location / {
    proxy_pass https://example;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Web server configs

Again, here's an example for one of the sites, the others have similar configs:

server {
  server_name example.com;
  rewrite ^(.*) https://www.example.com$1 permanent;
}

server {
  listen   80;
  listen 443 ssl;

  ssl on;
  ssl_certificate /etc/nginx/ssl/example.chained.crt;
  ssl_certificate_key /etc/nginx/ssl/example.key;

  root /var/www/example/public;
  server_name www.example.com;
  if ($ssl_protocol = "") {
    rewrite     ^   https://$server_name$request_uri? permanent;
  }
  client_max_body_size 2000M;
  passenger_enabled on;
  rails_env production;
  passenger_env_var SECRET_KEY_BASE "SOME_SECRET";
}

What I've gathered so far:

My Questions:

  • Do I have to enable the passenger sticky sessions also in the load balancer's configs? I am guessing this is only for the web servers.
  • How would the location section for the websocket server look like?
  • Do I have to create the websocket location section also on the load balancer?
  • Having the sticky sessions is enough to keep the various apps and servers in synch?
  • I have various apps running on each server and they should all receive the same notifications (socket messages) so they should all connect to the same websocket server (I'm guessing). Now that websocket-rails is part of their gemsets, won't each app try to spawn their own websocket server? If so, how do I prevent that and make them spawn only one in case none is running yet?

As you can see I am quite confused about how websocket-rails works with passenger and nginx in production so even if you don't have all the answers, any input is greatly appreciated!

UPDATE

I've tried the following on the load balancer:

upstream websocket {
  server xx.xx.xx.xx:443;
  server xx.xx.xx.xx:443;
}

location /websocket {
    proxy_pass https://websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade websocket;
    proxy_set_header Connection Upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    #also tried with this:
    #proxy_set_header Upgrade $http_upgrade;
    #proxy_set_header Connection "upgrade";
}

and on the app servers:

location /websocket {
    proxy_pass https://www.example.com/websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade websocket;
    proxy_set_header Connection Upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    #also tried with this:
    #proxy_set_header Upgrade $http_upgrade;
    #proxy_set_header Connection "upgrade";
}

On the client side I connect to the url WebSocketRails('www.example.com/websocket'); and i get the following error:

WebSocket connection to 'wss://www.example.com/websocket' failed: Error during WebSocket handshake: Unexpected response code: 404

Any ideas?

1

There are 1 best solutions below

6
On
  1. I don't think you'll need passenger sticky sessions on the load balancer

  2. This blog covers relevant WebSocket config for NGINX. You need the WebSocket config on the load balancer, and also on the web server if you want to pass the Upgrade and Connection headers to the rails app.