使用 Socket.io、ExpressJS 和 Nginx Ingress 的 Kubernetes Websockets

Kubernetes Websockets using Socket.io, ExpressJS and Nginx Ingress

我想使用 Socket.io 将 React Native 应用程序连接到托管在 Google 云平台 (GKE) 上的 Kubernetes 集群内的服务器。

Nginx Ingress Controller 声明似乎有问题,但我找不到它。

我试过添加nginx.org/websocket-services;重写我的后端代码,以便它在端口 3004 上使用一个单独的 NodeJS 服务器(一个简单的 HTTP 服务器),然后通过 Ingress Controller 在与端口 3003 上不同的路径下公开它;以及来自其他 SO 问题和 Github 问题的多项其他建议。

可能有用的信息:

  1. 集群主版本:1.15.11-gke.15
  2. 我使用启用了 RBAC 的 Helm (stable/nginx-ingress) 管理的负载均衡器
  3. 所有部署和服务都在命名空间内 gitlab-managed-apps
  4. 我在尝试连接到 socket.io 时收到的错误是:Error: websocket error

前端部分,代码如下:

App.js

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

我希望上面的内容将我连接到名为 socketdns 的 socket.io 命名空间。

后端代码为:

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');
});

入口服务是:

ingress-service.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    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/?(.*)

只需将注释更改为

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

而不是

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

大多数情况下它会解决您的问题。

解决方案是删除 nginx.ingress.kubernetes.io/rewrite-target: / 注释。

这是一个有效的配置:(请注意 apiVersion 自问题被提出后已更改)

入口配置

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

服务上(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', () => { 
//...
});

global.NODE_ENV === 'development' ? '' : '/app-sockets' 位与开发中的一个问题有关。如果您在此处更改它,您还必须在下面的代码片段中进行更改。

正在开发中,服务在 http://localhost:3003 下(套接字端点是 http://localhost:3003/sockets)。

在生产中,服务在 https://example.com/app-sockets 下(套接字端点是 https://example.com/app-sockets/sockets)。

在前端

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);
};

注意:我无法在问题中提到的项目上执行此操作,但我在另一个托管在 EKS 而非 GKE[=51 上的项目上执行此操作=].请随时确认这是否也适用于 GKE。