Kubernetes (K8S)中Traefik中间件(Middleware)

Traefik Middlewares简介

官方文档

Traefik Middlewares 是一个处于路由和后端服务之前的中间件,在外部流量进入 Traefik,且路由规则匹配成功后,将流量发送到对应的后端服务前,先将其发给中间件进行一系列处理(类似于过滤器链 Filter,进行一系列处理),例如,添加 Header 头信息、鉴权、流量转发、处理访问路径前缀、IP 白名单等等,经过一个或者多个中间件处理完成后,再发送给后端服务,这个就是中间件的作用。 Traefik内置了很多不同功能的Middleware,主要是针对HTTP和TCP,这里挑选几个比较常用的进行演示。

traefik middleware overview

重定向-redirectScheme

官方文档

我们定义的 whoami 这个应用,我们可以通过 https://whoami.od.com/tls 来访问到应用,但是如果我们用 http 来访问的话呢就不行了,就会 404 了,因为我们根本就没有简单 80 端口这个入口点,所以要想通过 http 来访问应用的话自然我们需要监听下 web 这个入口点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > tls-https.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls-http
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/tls\`)
kind: Rule
services:
- name: whoami
port: 80
EOF

这里我们创建的 IngressRoute 的 entryPoints 是 web,然后创建这个对象,这个时候我们就可以通过 http 访问到这个应用了。

但是我们如果只希望用户通过 https 来访问应用的话呢?按照以前的知识,我们是不是可以让 http 强制跳转到 https 服务去,对的,在 Traefik 中也是可以配置强制跳转的,只是这个功能现在是通过中间件来提供的了。如下所示,我们使用 redirectScheme 中间件来创建提供强制跳转服务:

1
2
3
4
5
6
7
8
9
10
cat >> tls-https.yml <<EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: redirect-https
spec:
redirectScheme:
scheme: https
EOF

然后将这个中间件附加到 http 的服务上面去,因为 https 的不需要跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat >> tls-https.yml <<EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls-http
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/tls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: redirect-https
EOF

这个时候我们再去访问 http 服务可以发现自动 307 重定向到了 https

1
2
3
4
5
6
7
8
$ curl -I http://whoami.od.com/tls
HTTP/1.1 307 Temporary Redirect
Server: nginx/1.18.0
Date: Thu, 24 Aug 2023 06:14:23 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 18
Connection: keep-alive
Location: https://whoami.od.com/tls

去除请求路径前缀-stripPrefix

官方文档

假设现在有这样一个需求,当访问 http://myapp.test.com/v1 时,流量调度至 myapp1。当访问 http://myapp.test.com/v2 时,流量调度至 myapp2。这种需求是非常常见的,在 NGINX 中,我们可以配置多个 Location 来定制规则,使用 Traefik 也可以这么做。但是定制不同的前缀后,由于应用本身并没有这些前缀,导致请求返回 404,这时候我们就需要对请求的 path 进行处理。

创建一个 IngressRoute,并设置两条规则,根据不同的访问路径代理至相对应的 service

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
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
entryPoints:
- web
routes:
- match: Host(`myapp.test.com`) && PathPrefix(`/v1`)
kind: Rule
services:
- name: myapp1
port: 80
middlewares:
- name: prefix-url-middleware
- match: Host(`myapp.test.com`) && PathPrefix(`/v2`)
kind: Rule
services:
- name: myapp2
port: 80
middlewares:
- name: prefix-url-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: prefix-url-middleware
spec:
stripPrefix: # 去除前缀的中间件 stripPrefix,指定将请求路径中的v1、v2去除。
prefixes:
- /v1
- /v2

部署测试

1
2
3
4
5
6
7
8
9
10
[root@k8s-node1 ~]# curl http://myapp.test.com/v1
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@k8s-node1 ~]# curl http://myapp.test.com/v2
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>

[root@k8s-node1 ~]# kubectl logs -l app=myapp1 | tail -2
# 未添加插件的访问路径为 /v1/
10.244.36.64 - - [19/Apr/2023:08:02:03 +0000] "GET /v1/ HTTP/1.1" 404 169 "-" "curl/7.29.0" "1.1.1.1"
# 添加插件后的访问路径为 /
10.244.36.64 - - [19/Apr/2023:08:04:31 +0000] "GET / HTTP/1.1" 200 65 "-" "curl/7.29.0" "1.1.1.1"

白名单-IPWhiteList

官方文档

为提高安全性,通常情况下一些管理员界面会设置 ip 访问白名单,只希望个别用户可以访问。

示例

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 > ip-white.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: ip-white-list-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: ip-white-list-middleware
spec:
ipWhiteList:
sourceRange:
- 127.0.0.1/32
- 10.1.1.11/32
ipStrategy: # 如果X-Forwarded-For存在多个IP指定过滤第几个
depth: 1 # 这里通过第一个IP进行白名单限制
EOF

非白名单中IP访问则会出现403。

基础用户认证-basicAuth

官方文档

通常企业安全要求规范除了要对管理员页面限制访问ip外,还需要添加账号密码认证,而 traefik 默认没有提供账号密码认证功能,此时就可以通过BasicAuth 中间件完成用户认证,只有认证通过的授权用户才可以访问页面。

安装 htpasswd 工具生成密码文件

1
2
3
4
5
6
yum -y install httpd-tools.x86_64

# 密码文件名称为htpasswd
htpasswd -bc htpasswd wangxiansen 123456

kubectl create secret generic basic-auth --from-file=htpasswd

创建 ingressroute,使用 basicAuth 中间件

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
cat > basic-auth.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: basic-auth-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: basic-auth-middleware
spec:
basicAuth:
secret: basic-auth
EOF

访问测试,可以看到弹出界面提示需要输入用户名和密码,输入后回车显示正常页面

修改请求/响应头信息-headers

官方文档

为了提高业务的安全性,安全团队会定期进行漏洞扫描,其中有些 web 漏洞就需要通过修改响应头处理,traefik 的 Headers 中间件不仅可以修改返回客户端的响应头信息,还能修改反向代理后端 service 服务的请求头信息。

例如对 https://whoami.od.com/tls 提高安全策略,强制启用HSTS HSTS:即 HTTP 严格传输安全响应头,收到该响应头的浏览器会在 63072000s(约 2 年)的时间内,只要访问该网站,即使输入的是 http,浏览器会自动跳转到 https。(HSTS 是浏览器端的跳转,之前的HTTP 重定向到 HTTPS是服务器端的跳转)

创建 ingressRoute 和 headers 中间件

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 > headers.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: headers-tls-demo
spec:
entryPoints:
- web
- websecure
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/tls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: hsts-header-middleware
tls:
secretName: who-tls # 指定tls证书名称
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hsts-header-middleware
spec:
headers:
customResponseHeaders:
Strict-Transport-Security: 'max-age=63072000'
EOF

访问测试

1
2
3
4
5
6
$ curl -Ik https://whoami.od.com/tls
HTTP/1.1 200 OK
Content-Length: 363
Content-Type: text/plain; charset=utf-8
Date: Thu, 24 Aug 2023 06:52:41 GMT
Strict-Transport-Security: max-age=63072000 # headers 插件添加的响应头

限流-rateLimit

官方文档

在实际生产环境中,流量限制也是经常用到的,它可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),还可以用来抵御 DDOS 攻击。更常见的情况,该功能被用来保护下游应用服务器不被同时太多用户请求所压垮。

创建 ingressRoute 和 rateLimit 中间件

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
cat > rate-limit.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: rate-limit
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: rate-limit-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: rate-limit-middleware
spec:
rateLimit: # 指定 1s 内请求数平均值不大于 10 个,高峰最大值不大于 50 个。
burst: 10
average: 50
EOF

压力测试,使用ab工具进行压力测试,一共请求 100 次,每次并发 10。测试结果失败的请求为

63 次,总耗时 0.110 秒

1
2
3
4
5
6
7
$ ab -n 100 -c 10 "http://whoami.od.com/notls"  
Document Path: /notls
Concurrency Level: 10
Time taken for tests: 0.110 seconds
Complete requests: 100
Failed requests: 63
Non-2xx responses: 63

熔断-circuitBreaker

官方文档

服务熔断的作用类似于保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。

熔断器三种状态

  • Closed:关闭状态,所有请求都正常访问。
  • Open:打开状态,所有请求都会被降级。traefik 会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。
  • Recovering:半开恢复状态,open 状态不是永久的,打开后会进入休眠时间。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

服务熔断原理(断路器的原理) 统计用户在指定的时间范围(默认10s)之内的请求总数达到指定的数量之后,如果不健康的请求(超时、异常)占总请求数量的百分比(50%)达到了指定的阈值之后,就会触发熔断。触发熔断,断路器就会打开(open),此时所有请求都不能通过。在5s之后,断路器会恢复到半开状态(half open),会允许少量请求通过,如果这些请求都是健康的,那么断路器会回到关闭状态(close).如果这些请求还是失败的请求,断路器还是恢复到打开的状态(open).

traefik支持的触发器

  • NetworkErrorRatio:网络错误率
  • ResponseCodeRatio:状态代码比率
  • LatencyAtQuantileMS:分位数的延迟(以毫秒为单位)

创建 ingressRoute ,添加 circuitBreaker 中间件,指定 50% 的请求比例响应时间大于 1MS 时熔断。

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
cat > circuit.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: rate-limit
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: circuit-breaker-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: circuit-breaker-middleware
spec:
circuitBreaker:
expression: LatencyAtQuantileMS(50.0) > 1
EOF

压力测试,一共请求 1000 次,每次并发 100 次。触发熔断机制,测试结果失败的请求为 995次,总耗时 0.378 秒。

1
2
3
4
5
6
7
$ ab -n 1000 -c 100 "http://whoami.od.com/notls"
Document Path: /notls
Concurrency Level: 100
Time taken for tests: 0.378 seconds
Complete requests: 1000
Failed requests: 995
Non-2xx responses: 995

自定义错误页-errorPages

官方文档

在实际的业务中,肯定会存在 4XX 5XX 相关的错误异常,如果每个应用都开发一个单独的错误页,无疑大大增加了开发成本,traefik 同样也支持自定义错误页,但是需要注意的是,错误页面不是由 traefik 存储处理,而是通过定义中间件,将错误的请求重定向到其他的页面。

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
cat > error.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: errors4
spec:
errors:
status:
- "400-499"
# query: /{status}.html # 可以为每个页面定义一个状态码,也可以指定4XX使用统一页面返回
query : /notls # 指定返回whoami的请求路径
service:
name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: errors-ing
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: errors4
EOF

数据压缩-compress

官方文档

有时候客户端和服务器之间会传输比较大的报文数据,这时候就占用较大的网络带宽和时长。为了节省带宽,加速报文的响应速速,可以将传输的报文数据先进行压缩,然后再进行传输,traefik也同样支持数据压缩。

traefik 默认只对大于 1024 字节,且请求标头包含 Accept-Encoding gzip 的资源进行压缩。可以指定排除特定类型不启用压缩或者根据内容大小来决定是否压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat > compress.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: compress
spec:
compress: {}
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: compress-ing
spec:
entryPoints:
- web
routes:
- match: Host(\`whoami.od.com\`) && PathPrefix(\`/notls\`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: compress
EOF

html 文件小于 1024 字节,未开启压缩,图片资源大于 1024 字节,开启了压缩