kuberneteswebsocketgoogle-kubernetes-enginenginx-ingress

Websockets on GKE with Nginx Ingress


I am trying to get websockets to work on GKE. Seems very trivial, but I am failing to get this to work, I just continuously keep getting 400 at Nginx Ingress.

The manifest is like this:

apiVersion: v1
kind: Namespace
metadata:
  name: my-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-ws-backend
  namespace: my-test
  labels:
    app: my-ws-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-ws-backend
  template:
    metadata:
      labels:
        app: my-ws-backend
    spec:
      containers:
        - name: backend
          image: ksdn117/web-socket-test
          imagePullPolicy: Always
          ports:
            - containerPort: 8010
          env:
            - name: NODE_ENV
              value: production
            - name: DEBUG
              value: socket*
---
apiVersion: v1
kind: Service
metadata:
  name: my-ws-backend
  namespace: my-test
spec:
  selector:
    app: my-ws-backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8010
  sessionAffinity: ClientIP
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ws-ingress
  namespace: my-test
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
    nginx.ingress.kubernetes.io/upgrade-insecure-requests: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
      proxy_set_header Host $host;
    nginx.ingress.kubernetes.io/server-snippet: |
      error_log /var/log/nginx/error.log debug;
    cert-manager.io/cluster-issuer: letsencrypt-prod-nginx
spec:
  ingressClassName: nginx
  rules:
    - host: ws-my-test.myhost.com
      http:
        paths:
          - path: /socket.io
            pathType: Prefix
            backend:
              service:
                name: my-ws-backend
                port:
                  number: 80
  tls:
    - hosts:
        - ws-my-test.myhost.com
      secretName: ws-my-test-cert

I tried hitting the endpoint with wscat and a simplistic Node.js script shown below to test. What am I missing?

const { io } = require('socket.io-client');

const socket = io('wss://ws-my-test.myhost.com', {
  transports: ['websocket'],
  reconnection: false,
});

socket.on('connect', () => {
  console.log('Connected!');
  socket.disconnect();
});

socket.on('connect_error', (err) => {
  console.error('Connection error:', err);
});

Solution

  • Got this working in the end, these are the annotations in my Ingress

    cert-manager.io/cluster-issuer: letsencrypt-prod-nginx
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    

    I think the problem was including duplicates as my annotations below:

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    

    which were not required, and caused the header to have duplicate values set in the header, that caused rejection of the request with status 400.

    Ingress-NGINX controller already comes preconfigured with the required Upgrade/Connection headers set, so not needed to set them again.