Reputation: 103
I am currently developing an application deployed on a Kubernetes cluster hosted in Jelastic Cloud. My topology is as follows:
I'm using an NGINX Ingress Controller, and the API balancers are running HAProxy 2.9.0. The Kubernetes version is v1.28.0.
Problem:
I’m trying to establish a WebSocket connection between my frontend (Next.js) and my backend (Ruby on Rails using Puma 6.4.2). My setup looks like this:
Here's a snippet from my Ingress configuration in Kubernetes:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: swaplit-ingress-websocket
namespace: swaplit
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rewrite-target: /cable$2
nginx.ingress.kubernetes.io/proxy-read-timeout: '3600'
nginx.ingress.kubernetes.io/proxy-send-timeout: '3600'
spec:
tls:
- hosts:
- ws.test.com
secretName: test-websocket-tls
ingressClassName: nginx
rules:
- host: ws.test.com
http:
paths:
- pathType: Prefix
path: /cable(/|$)(.*)
backend:
service:
name: test
port:
number: 3001
When I check the logs of my backend server, I can see the following message:
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
But immediately after, I get this:
Finished "/cable" [WebSocket]
In the Network tab of the browser's dev tools, I notice that the connection status shows Finished, instead of the expected 101 Switching Protocols, so it seems like the WebSocket connection isn’t being persisted between my client and server.
What I suspect:
Due to the Jelastic production environment, my Kubernetes cluster is running behind an HAProxy API balancer. I suspect the issue might be due to a misconfiguration in HAProxy, so I modified the HAProxy configuration to support WebSockets as follows:
#### MAKE CHANGES HERE ONLY IF YOU REALLY KNOW WHAT YOU ARE DOING #####
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log 127.0.0.1 local0
user haproxy
group haproxy
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam.pem
pidfile /var/run/haproxy.pid
tune.ssl.default-dh-param 2048
maxconn 10000
daemon
stats timeout 2m
stats socket /var/run/haproxy.sock mode 660 level admin
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server "${PROXY_READ_TIMEOUT}"s
timeout http-keep-alive 10s
timeout check 10s
maxconn 10000
frontend ft_http
#bind :::80 v4v6
mode http
stats enable
stats auth xx:xxxx
stats refresh 30s
stats show-node
stats uri /haproxy_adm_panel
stats admin if TRUE
option forwardfor
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header HTTPS on if { ssl_fc }
http-request set-header Ssl-Offloaded 1 if { ssl_fc }
# Detect WebSocket requests
acl is_websocket hdr(Upgrade) -i websocket
use_backend ws_backend if is_websocket
default_backend default
backend default
mode http
errorfile 503 /etc/haproxy/welcome.http
backend bk_http ###HOSTS ARE ADDED TO THIS BACKEND BY DEFAULT
mode http
cookie SRVNAME insert
balance roundrobin
# Backend for WebSocket
backend ws_backend
mode http
option http-server-close
option forwardfor
balance roundrobin
option http-server-close
option forwardfor
# Set timeouts for WebSockets
timeout connect 5s
timeout client 3600s
timeout server 3600s
timeout tunnel 3600s # Allow WebSocket tunnel to stay open
## websocket protocol validation
acl hdr_connection_upgrade hdr(Connection) -i upgrade
acl hdr_upgrade_websocket hdr(Upgrade) -i websocket
acl hdr_websocket_key hdr_cnt(Sec-WebSocket-Key) eq 1
acl hdr_websocket_version hdr_cnt(Sec-WebSocket-Version) eq 1
http-request deny if ! hdr_connection_upgrade ! hdr_upgrade_websocket ! hdr_websocket_key ! hdr_websocket_version
## ensure our application protocol name is valid
## (don't forget to update the list each time you publish new applications)
acl ws_valid_protocol hdr(Sec-WebSocket-Protocol) echo-protocol
http-request deny if ! ws_valid_protocol
## websocket health checking
# option httpchk GET / HTTP/1.1rnHost:\ ws.swaplit.org\r\nConnection:\ Upgrade\r\nUpgrade:\ websocket\r\nSec-WebSocket-Key:\ haproxy\r\nSec-WebSocket-Version:\ 13\r\nSec-WebSocket-Protocol:\ echo-protocol
http-check send meth GET uri Host:\ ws.test.com\r\nConnection:\ Upgrade\r\nUpgrade:\ websocket\r\nSec-WebSocket-Key:\ haproxy\r\nSec-WebSocket-Version:\ 13\r\nSec-WebSocket-Protocol:\ echo-protocol ver HTTP/1.0
http-check expect status 101
server wk_1 xx.xxx.xx.xx
server wk_2 xx.xxx.xx.xx
server wk_3 xx.xxx.xx.xx
###TCP SECTION###
frontend tcp_CPlanebalancing_UAQJX
bind *:6443 v4v6
mode tcp
default_backend tcp_CPlanebalancing_UAQJX_backend
backend tcp_CPlanebalancing_UAQJX_backend
mode tcp
balance roundrobin
option tcp-check
server tcp_CPlanebalancing_UAQJX_1 xx.xxx.xx.xxx:xxxx check fall 3 rise 2
server tcp_CPlanebalancing_UAQJX_2 xx.xxx.xx.xxx:xxxx check fall 3 rise 2
server tcp_CPlanebalancing_UAQJX_3 xx.xxx.xx.xxx:xxxx check fall 3 rise 2
However, I’m still encountering the same issue. I followed this guide on configuring HAProxy for WebSockets, but I may have made a mistake in my config.
Request: Does anyone have experience setting up WebSockets (especially with ActionCable) on Kubernetes behind an HAProxy balancer? Any tips on how to properly configure the HAProxy or further debug this issue would be greatly appreciated, as I’ve been struggling with this problem for weeks.
Upvotes: 0
Views: 104