4

I want to connect a React Native application using Socket.io to a server that is inside a Kubernetes Cluster hosted on Google Cloud Platform (GKE).

There seems to be an issue with the Nginx Ingress Controller declaration but I cannot find it.

I have tried adding nginx.org/websocket-services; rewriting my backend code so that it uses a separate NodeJS server (a simple HTTP server) on port 3004, then exposing it via the Ingress Controller under a different path than the one on port 3003; and multiple other suggestions from other SO questions and Github issues.

Information that might be useful:

  1. Cluster master version: 1.15.11-gke.15
  2. I use a Load Balancer managed with Helm (stable/nginx-ingress) with RBAC enabled
  3. All deployments and services are within the namespace gitlab-managed-apps
  4. The error I receive when trying to connect to socket.io is: Error: websocket error

For the front-end part, the code is as follows:

App.js

const socket = io('https://example.com/app-sockets/socketns', {
    reconnect: true,
    secure: true,
    transports: ['websocket', 'polling']
});

I expect the above to connect me to a socket.io namespace called socketdns.

The backend code is:

app.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const redis = require('socket.io-redis');

io.set('transports', ['websocket', 'polling']);
io.adapter(redis({
    host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis-cluster-ip-service.gitlab-managed-apps.svc.cluster.local',
    port: 6379
}));
io.of('/').adapter.on('error', function(err) { console.log('Redis Adapter error! ', err); });

const nsp = io.of('/socketns');

nsp.on('connection', function(socket) {
    console.log('connected!');
});

server.listen(3003, () => {
    console.log('App listening to 3003');
});

The ingress service is:

ingress-service.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    certmanager.k8s.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "7200"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "7200"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "7200"
    nginx.org/websocket-services: "app-sockets-cluster-ip-service"
  name: ingress-service
  namespace: gitlab-managed-apps
spec:
  tls:
  - hosts:
    - example.com
    secretName: letsencrypt-prod
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          serviceName: app-cms-cluster-ip-service
          servicePort: 3000
        path: /?(.*)
      - backend:
          serviceName: app-users-cluster-ip-service
          servicePort: 3001
        path: /app-users/?(.*)
      - backend:
          serviceName: app-sockets-cluster-ip-service
          servicePort: 3003
        path: /app-sockets/?(.*)
      - backend:
          serviceName: app-sockets-cluster-ip-service
          servicePort: 3003
        path: /app-sockets/socketns/?(.*)
Rico
  • 58,485
  • 12
  • 111
  • 141
Valentin Constanda
  • 139
  • 1
  • 2
  • 13
  • Where do you see the error? backend logs? – Rico Jul 21 '20 at 01:24
  • There's two main flavours of nginx ingress controller. `nginx.org` annotations are _NOT_ for [`ingress-nginx`](https://github.com/kubernetes/ingress-nginx) which you are using. – Matt Jul 21 '20 at 02:25
  • Rico, I see the error in the React Native debugger. – Valentin Constanda Jul 21 '20 at 05:56
  • Matt, I know the difference between the community and commercial version, but I have tried every suggestion to make it work. Deleting the annotation for the community version does not solve the connection issues – Valentin Constanda Jul 21 '20 at 05:58
  • This might be helpful: https://gist.github.com/jsdevtom/7045c03c021ce46b08cb3f41db0d76da#file-ingress-service-yaml – Amit Baranes Jul 21 '20 at 08:03
  • 1
    Could you share your backend image, or another one with just an exmaple to reproduce? – Mr.KoopaKiller Jul 21 '20 at 09:08
  • @KoopaKiller here is an example repository: `valentingdm/so-ws-nginx-ingress-example` Edit: let me know if you need anything else. – Valentin Constanda Jul 21 '20 at 09:54
  • @AmitBaranes I have already tried that gist unfortunately :( – Valentin Constanda Jul 21 '20 at 09:54
  • 1
    I have tried to solve a very similar issue: https://stackoverflow.com/questions/61730357/websocket-returns-500-on-client-and-101-on-server no luck so far – Amit Baranes Jul 21 '20 at 09:56
  • 1
    I don't think this is an issue with the LB, the error sounds like it is reaching the backend. Do you have a cert configured on your backend since you have your connection set to secure? – Patrick W Jul 21 '20 at 13:31
  • @PatrickW no, I don't have an SSL setup on the Express server. (The SSL is used on Nginx) I have tried removing the `secure: true` from `App.js` but that doesn't solve the issue. – Valentin Constanda Jul 21 '20 at 13:51
  • Can you set up a websocket connection directly.to the backend, bypassing the ingress? – Patrick W Jul 21 '20 at 13:52
  • @PatrickW here is the websocket connection ip: http://34.89.165.135:3003/ - this is the service I'm trying to connect to. – Valentin Constanda Jul 21 '20 at 14:15
  • That looks like it is using the load balancer, try using the nodeport instead – Patrick W Jul 21 '20 at 14:16
  • @PatrickW here it is: http://34.107.38.118:30206/ – Valentin Constanda Jul 21 '20 at 14:22
  • in both cases, my connection works and shows the service is up, though I am just testing through firefox – Patrick W Jul 21 '20 at 14:24
  • @PatrickW indeed, the problem lies within the Nginx Ingress, I cannot connect to it when using https://example.com/app-sockets – Valentin Constanda Jul 21 '20 at 14:35
  • @ValentinConstanda I can't pull your image, is it in dockerhub? – Mr.KoopaKiller Jul 23 '20 at 10:19
  • @KoopaKiller yes, here is the public repository link: https://hub.docker.com/r/valentingdm/so-ws-nginx-ingress-example – Valentin Constanda Jul 23 '20 at 10:39
  • I can't use your image (some redis errors). But i'll try to figure out with another example using your ingress spec. – Mr.KoopaKiller Aug 03 '20 at 12:35
  • So, after some tests using simple ws image as example, i can't get the same error as you. Somethings you could test: 1. Try to reach the service directly instead ingress and verify if it works. 2 Try to use [this simple image](https://hub.docker.com/r/ksdn117/web-socket-test) 3. Verify the nginx-ingress-controller pod logs and see what is the return code from your request. I can share my example files if you think it will help you. Please let me know the results. I'm testing the connections using websocat. – Mr.KoopaKiller Aug 12 '20 at 09:40

2 Answers2

3

The solution is to remove the nginx.ingress.kubernetes.io/rewrite-target: /$1 annotation.

Here is a working configuration: (please note that apiVersion has changed since the question has been asked)

Ingress configuration

ingress-service.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "64m"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
  name: ingress-service
  namespace: default
spec:
  tls:
  - hosts:
    - example.com
    secretName: letsencrypt-prod
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          service:
            name: app-sockets-cluster-ip-service
            port:
              number: 3003
        path: /app-sockets/?(.*)
        pathType: Prefix

On the service (Express.js):

app.js

const redisAdapter = require('socket.io-redis');

const io = require('socket.io')(server, {
    path: `${ global.NODE_ENV === 'development' ? '' : '/app-sockets' }/sockets/`,
    cors: {
        origin: '*',
        methods: ['GET', 'POST'],
    },
});

io.adapter(redisAdapter({
    host: global.REDIS_HOST,
    port: 6379,
}));

io.of('/').adapter.on('error', err => console.log('Redis Adapter error! ', err));

io.on('connection', () => { 
//...
});

The global.NODE_ENV === 'development' ? '' : '/app-sockets' bit is related to an issue in development. If you change it here, you must also change it in the snippet below.

In development the service is under http://localhost:3003 (sockets endpoint is http://localhost:3003/sockets).

In production the service is under https://example.com/app-sockets (sockets endpoint is https://example.com/app-sockets/sockets).

On frontend

connectToWebsocketsService.js

/**
 * Connect to a websockets service
 * @param tokens {Object}
 * @param successCallback {Function}
 * @param failureCallback {Function}
 */
export const connectToWebsocketsService = (tokens, successCallback, failureCallback) => {
    //SOCKETS_URL = NODE_ENV === 'development' ? 'http://localhost:3003' : 'https://example.com/app-sockets'
    const socket = io(`${ SOCKETS_URL.replace('/app-sockets', '') }`, {
        path: `${ NODE_ENV === 'development' ? '' : '/app-sockets' }/sockets/`,
        reconnect: true,
        secure: true,
        transports: ['polling', 'websocket'], //required
        query: {
            // optional
        },
        auth: {
            ...generateAuthorizationHeaders(tokens), //optional
        },
    });

    socket.on('connect', successCallback(socket));
    socket.on('reconnect', successCallback(socket));
    socket.on('connect_error', failureCallback);
};

Note: I wasn't able to do it on the project mentioned in the question, but I have on another project which is hosted on EKS, not GKE. Feel free to confirm if this works for you on GKE as well.

Valentin Constanda
  • 139
  • 1
  • 2
  • 13
0

Just change annotations to

nginx.ingress.kubernetes.io/websocket-services: "app-sockets-cluster-ip-service"

instead of

nginx.org/websocket-services: "app-sockets-cluster-ip-service" 

Mostly it will resolve your issue.

fcdt
  • 2,371
  • 5
  • 14
  • 26
Kalpit Lad
  • 11
  • 1
  • @Valentin Constanda Does this answer your question ? Do you still have this problem ? – matt_j Jan 18 '21 at 11:11
  • Unfortunately not, the solution I went with is expose the deployment with a NodePort and use the IP address to connect to the websockets. I know it's not the best solution, but it's what I went with temporarily. – Valentin Constanda Feb 01 '21 at 09:31
  • @ValentinConstanda Can you confirm whether you found a permanent solution for your problem or working on temporary solutions? – Priya Gaikwad Jul 21 '21 at 03:47
  • @PriyaGaikwad I have, but not on this project, therefore not on Google Cloud Platform. I was able to use wss on Amazon Web Services. I will post an answer. – Valentin Constanda Jul 22 '21 at 06:46