Kubernetes (K8S)中Traefik的服务发布

简介

traefik 的路由规则就可以实现 4 层和 7 层的基本负载均衡操作,使用 IngressRoute IngressRouteTCP IngressRouteUDP 资源即可。但是如果想要实现 加权轮询、流量复制 等高级操作,traefik抽象出了一个 TraefikService 资源。此时整体流量走向为:外部流量先通过 entryPoints 端口进入 traefik,然后由 IngressRoute/IngressRouteTCP/IngressRouteUDP 匹配后进入 TraefikService,在 TraefikService 这一层实现加权轮循和流量复制,最后将请求转发至kubernetes的service。

除此之外traefik还支持7层的粘性会话、健康检查、传递请求头、响应转发、故障转移等操作。

灰度发布

官方文档

Traefik2.0 的一个更强大的功能就是灰度发布,灰度发布也称为金丝雀发布(Canary),主要就是让一部分测试的服务也参与到线上去,经过测试观察看是否符号上线要求。

canary deployment

比如现在我们有两个名为 appv1appv2 的服务,我们希望通过 Traefik 来控制我们的流量,将 3⁄4 的流量路由到 appv1,1/4 的流量路由到 appv2 去,这个时候就可以利用 Traefik2.0 中提供的带权重的轮询(WRR)来实现该功能,首先在 Kubernetes 集群中部署上面的两个服务。为了对比结果我们这里提供的两个Nginx服务不同页面,方便测试。

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
cat > appv1.yml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: appv1
spec:
selector:
matchLabels:
app: appv1
template:
metadata:
labels:
use: test
app: appv1
spec:
containers:
- image: nginx:alpine
name: appv1
command: ["/bin/sh", "-c", "echo '你好, 这是(王先森)APP-v1服务中心'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
ports:
- containerPort: 80
name: portv1
---
apiVersion: v1
kind: Service
metadata:
name: appv1
spec:
selector:
app: appv1
ports:
- name: http
port: 80
targetPort: portv1
EOF
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
cat > appv2.yml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: appv2
spec:
selector:
matchLabels:
app: appv2
template:
metadata:
labels:
use: test
app: appv2
spec:
containers:
- name: appv2
image: nginx:alpine
command: ["/bin/sh", "-c", "echo '你好, 这是(王先森)APP-v2服务中心'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
ports:
- containerPort: 80
name: portv2
---
apiVersion: v1
kind: Service
metadata:
name: appv2
spec:
selector:
app: appv2
ports:
- name: http
port: 80
targetPort: portv2
EOF

直接创建上面两个服务:

1
2
3
4
$ kubectl apply -f appv1.yml
$ kubectl apply -f appv2.yml
# 通过下面的命令可以查看服务是否运行成功
$ kubectl get pods -l use=test

在 Traefik2.1 中新增了一个 TraefikService 的 CRD 资源,我们可以直接利用这个对象来配置 WRR,之前的版本需要通过 File Provider,比较麻烦,新建一个描述 WRR 的资源清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat > wrr.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: app-wrr
spec:
weighted:
services:
- name: appv1
weight: 3 # 定义权重
port: 80
kind: Service # 可选,默认就是 Service
- name: appv2
weight: 1
port: 80
EOF

然后为我们的灰度发布的服务创建一个 IngressRoute 资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat > wrr-ingressroute.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: wrr-ingressroute
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(\`wrr.od.com\`)
kind: Rule
services:
- name: app-wrr
kind: TraefikService
EOF

在浏览器中连续访问 4 次,我们可以观察到 appv1 这应用会收到 3 次请求,而 appv2 这个应用只收到 1 次请求,符合上面我们的 3:1 的权重配置。

1
2
3
4
5
6
7
8
[root@ceph01 ~]#curl wrr.od.com
你好, 这是(王先森)APP-v2服务中心
[root@ceph01 ~]#curl wrr.od.com
你好, 这是(王先森)APP-v1服务中心
[root@ceph01 ~]#curl wrr.od.com
你好, 这是(王先森)APP-v1服务中心
[root@ceph01 ~]#curl wrr.od.com
你好, 这是(王先森)APP-v1服务中心

会话保持

官方文档

会话保持功能依赖加权轮询功能

当我们使用 traefik 的负载均衡时,默认情况下轮循多个 k8s 的 service 服务,如果用户对同一内容的多次请求,可能被转发到了不同的后端服务器。假设用户发出请求被分配至服务器 A,保存了一些信息在 session 中,该用户再次发送请求被分配到服务器 B,要用之前保存的信息,若服务器 A 和 B 之间没有 session 粘滞,那么服务器 B 就拿不到之前的信息,这样会导致一些问题。traefik 同样也支持粘性会话,可以让用户在一次会话周期内的所有请求始终转发到一台特定的后端服务器上。

创建 traefikervie 和 ingressRoute,实现基于 cookie 的会话保持

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
cat > cookie.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroute-sticky
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(\`wrr.od.com\`)
kind: Rule
services:
- name: sticky
namespace: default
kind: TraefikService
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: sticky
namespace: default
spec:
weighted:
services:
- name: appv1
port: 80
weight: 1 # 定义权重
- name: appv2
port: 80
weight: 2
sticky: # 开启粘性会话
cookie: # 基于cookie区分客户端
name: boysec-cookie # 指定客户端请求时,包含的cookie名称
EOF

客户端访问测试,携带 cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@ceph01 ~]# for i in {1..5}; do curl -b "boysec-cookie=default-appv2-80" http://wrr.od.com; done
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v2服务中心
[root@ceph01 ~]# for i in {1..5}; do curl -b "boysec-cookie=default-appv1-80" http://wrr.od.com; done
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心
[root@ceph01 ~]#for i in {1..5}; do curl http://wrr.od.com; done
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v2服务中心
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心
你好, 这是(王先森)APP-v1服务中心

流量复制

官方文档

所谓的流量复制,是一种可以将流入流量复制并同时将其发送给其他服务的方法,镜像服务可以获得给定百分比的请求同时也会忽略这部分请求的响应。,这个功能在做一些压测或者问题复现的时候很有用。

traefik mirror

现在我们部署两个 nginx的服务

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
cat > mirror-nginx.yml <<EOF
apiVersion: v1
kind: Service
metadata:
name: v1
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: v1
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: v1
labels:
app: v1
spec:
selector:
matchLabels:
app: v1
template:
metadata:
labels:
app: v1
spec:
containers:
- name: v1
image: nginx:alpine
ports:
- name: web
containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
name: v2
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: v2
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: v2
labels:
app: v2
spec:
selector:
matchLabels:
app: v2
template:
metadata:
labels:
app: v2
spec:
containers:
- name: v2
image: nginx:alpine
ports:
- name: web
containerPort: 80
EOF

创建一个 IngressRoute 对象,将服务 v1 的流量复制 50% 到服务 v2,如下资源对象所示:

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
cat > mirror-ingress-route.yaml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: app-mirror
spec:
mirroring:
name: v1 # 发送 100% 的请求到 K8S 的 Service "v1"
port: 80
mirrors:
- name: v2 # 然后复制 50% 的请求到 v2
percent: 50
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: mirror-ingress-route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(\`mirror.od.com\`)
kind: Rule
services:
- name: app-mirror
kind: TraefikService # 使用声明的 TraefikService 服务,而不是 K8S 的 Service
EOF

创建这个资源对象后,在连续访问 4 次 mirror.od.com 可以发现有一半的请求也出现在了 v2 这个服务中。与预期相同,收到了 50% 的流量。

1
2
3
4
5
6
7
8
9
10
# V1 日志
$ kubectl logs -f v1-695b469cd-92jqj
172.16.130.2 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.130.1"
172.16.120.5 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.120.1"
172.16.100.5 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.100.1"
172.16.130.2 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.130.1"
# V2 日志
$ kubectl logs -f v2-7b889df5d6-q2gkt
172.16.130.2 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.130.1"
172.16.100.5 - - [25/Aug/2023:07:03:08 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.0.1" "172.16.100.1"