traefik支持socketio会话

场景

        公司业务出现使用一个使用websocket的会话需求,和常见的web聊天工具一样,如网页QQ。但是在研发在开发的时候没有做session会话的粘制,于是乎就是能在运维层面进行设置。

        socket.io单节点模式是很容易部署的,但是往往在生产环境一个节点不能满足业务需求,况且还要保证节点挂掉的情况仍能正常提供服务,所以多节点模式就成为了生成环境的一种必须的部署模式。

        将介绍如何在kubernetes 集群上部署多节点的socket.io服务,并使用traeifk 2.1 版本如何进行访问

问题

        在普通环境下,我们配置websocket会话使用nginx很简单,配置一个ip_hash,其他的和普通的location配置差不多,但在kubernetes集群下,生产环境一般部署多个POD 来提供服务,而socket.io服务并不是单纯的无状态应用,只需要将POD 部署成多个就可以正常提供服务了,因为其底层需要建立很多连接来保持长连接,但是这样的话上一个请求可能会被路由到一个POD,下一个请求则很有可能会被路由到另外一个POD 中去了,这样就会出现错误。
img

        上图中可以看出是有的请求找不到对应的Session ID,也证明了上面提到的引起错误的原因。

解决办法

        从socket.io 官方文档中可以看到对于多节点的介绍,其中通过Nginx的ip_hash 配置用得比较多,同一个ip 访问的请求通过hash 计算过后会被路由到相同的后端程序去,这样就不会出现上面的问题了。我们这里是部署在kubernetes集群上面的,通过traefik ingress来连接外部和集群内部间的请求的,所以这里中间就省略了Nginx这一层,当然你也可以多加上这一层,但是这样显然从架构上就冗余了,而且还有更好的解决方案的:sessionAffinity(也称会话亲和力)

什么是sessionAffinity? sessionAffinity是一个功能,将来自同一个客户端的请求总是被路由回服务器集群中的同一台服务器的能力。

        在kubernetes中启用sessionAffinity很简单,只需要简单的Service中配置即可:

1
service.spec.sessionAffinity = "ClientIP"

        默认情况下sessionAffinity=None,会随机选择一个后端进行路由转发的,设置成ClientIP后就和上面的ip_hash功能一样了,由于我们使用的是traefik ingress,这里还需要在Service中添加一个traefik的annotation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Service
metadata:
name: socket-demo
namespace: kube-apps
annotations:
traefik.backend.loadbalancer.stickiness: "true"
traefik.backend.loadbalancer.stickiness.cookieName: "socket"
labels:
k8s-app: socket-demo
spec:
sessionAffinity: "ClientIP"
ports:
- name: socketio
port: 80
protocol: TCP
targetPort: 3000
selector:
k8s-app: socket-demo

        在traefik 的路径下面建立一个socket.toml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
[http]
[http.routers]
[http.routers.socketio]
namespace = "kube-ops"
entryPoints = ["web"]
service = "socket-demo"
rule = "Host(`socket-demo.xxlaila.cn`)"
[http.services]
[http.services.socket-demo]
[http.services.socket-demo.loadBalancer]
passHostHeader = true
[[http.services.socket-demo.loadBalancer.servers]]
url = "http://socket-demo.kube-ops.svc.cluster.local:80"

        完成以后可以执行测试,打印出来的hostname是一样的,因为使用的所有的访问都来自一个ip地址。

部署在kubernetes集群上的yaml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: socket-demo
namespace: kube-apps
labels:
k8s-app: socket-demo
spec:
replicas: 3
template:
metadata:
labels:
k8s-app: socket-demo
spec:
containers:
- image: cnych/socketdemo:k8s
imagePullPolicy: Always
name: socketdemo
ports:
- containerPort: 3000
protocol: TCP
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 50m
memory: 50Mi

---
apiVersion: v1
kind: Service
metadata:
name: socket-demo
namespace: kube-apps
annotations:
traefik.backend.loadbalancer.stickiness: "true"
traefik.backend.loadbalancer.stickiness.cookieName: "socket"
labels:
k8s-app: socket-demo
spec:
sessionAffinity: None
ports:
- name: socketio
port: 80
protocol: TCP
targetPort: 3000
selector:
k8s-app: socket-demo

参考文献:
https://www.qikqiak.com/post/socketio-multiple-nodes-in-kubernetes/
https://docs.traefik.cn/toml#configuration-backends

坚持原创技术分享,您的支持将鼓励我继续创作!
0%