王先森2023-08-242023-08-24
Traefik Middlewares
是一个处于路由和后端服务之前的中间件,在外部流量进入 Traefik
,且路由规则匹配成功后,将流量发送到对应的后端服务前,先将其发给中间件进行一系列处理(类似于过滤器链 Filter,进行一系列处理),例如,添加 Header 头信息、鉴权、流量转发、处理访问路径前缀、IP 白名单等等,经过一个或者多个中间件处理完成后,再发送给后端服务,这个就是中间件的作用。 Traefik内置了很多不同功能的Middleware,主要是针对HTTP和TCP,这里挑选几个比较常用的进行演示。
我们定义的 whoami 这个应用,我们可以通过 https://whoami.od.com/tls
来访问到应用,但是如果我们用 http
来访问的话呢就不行了,就会 404 了,因为我们根本就没有简单 80 端口这个入口点,所以要想通过 http
来访问应用的话自然我们需要监听下 web
这个入口点:
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
中间件来创建提供强制跳转服务:
cat >> tls-https.yml <<EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: redirect-https
spec:
redirectScheme:
scheme: https
EOF
然后将这个中间件附加到 http 的服务上面去,因为 https 的不需要跳转
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。
$ 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
假设现在有这样一个需求,当访问 http://myapp.test.com/v1
时,流量调度至 myapp1。当访问 http://myapp.test.com/v2
时,流量调度至 myapp2。这种需求是非常常见的,在 NGINX 中,我们可以配置多个 Location 来定制规则,使用 Traefik 也可以这么做。但是定制不同的前缀后,由于应用本身并没有这些前缀,导致请求返回 404,这时候我们就需要对请求的 path 进行处理。
创建一个 IngressRoute,并设置两条规则,根据不同的访问路径代理至相对应的 service
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
部署测试
[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"
为提高安全性,通常情况下一些管理员界面会设置 ip 访问白名单,只希望个别用户可以访问。
示例
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
EOF
非白名单中IP访问则会出现403。
通常企业安全要求规范除了要对管理员页面限制访问ip外,还需要添加账号密码认证,而 traefik 默认没有提供账号密码认证功能,此时就可以通过BasicAuth 中间件完成用户认证,只有认证通过的授权用户才可以访问页面。
安装 htpasswd 工具生成密码文件
yum -y install httpd-tools.x86_64
# 密码文件名称为htpasswd
htpasswd -bc htpasswd wangxiansen 123456
kubectl create secret generic basic-auth --from-file=htpasswd
创建 ingressroute,使用 basicAuth 中间件
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
访问测试,可以看到弹出界面提示需要输入用户名和密码,输入后回车显示正常页面
为了提高业务的安全性,安全团队会定期进行漏洞扫描,其中有些 web 漏洞就需要通过修改响应头处理,traefik 的 Headers 中间件不仅可以修改返回客户端的响应头信息,还能修改反向代理后端 service 服务的请求头信息。
例如对 https://whoami.od.com/tls
提高安全策略,强制启用HSTS HSTS:即 HTTP 严格传输安全响应头,收到该响应头的浏览器会在 63072000s(约 2 年)的时间内,只要访问该网站,即使输入的是 http,浏览器会自动跳转到 https。(HSTS 是浏览器端的跳转,之前的HTTP 重定向到 HTTPS是服务器端的跳转)
创建 ingressRoute 和 headers 中间件
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
访问测试
$ 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 插件添加的响应头
在实际生产环境中,流量限制也是经常用到的,它可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),还可以用来抵御 DDOS 攻击。更常见的情况,该功能被用来保护下游应用服务器不被同时太多用户请求所压垮。
创建 ingressRoute 和 rateLimit 中间件
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 秒
$ 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
服务熔断的作用类似于保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
熔断器三种状态
服务熔断原理(断路器的原理) 统计用户在指定的时间范围(默认10s)之内的请求总数达到指定的数量之后,如果不健康的请求(超时、异常)占总请求数量的百分比(50%)达到了指定的阈值之后,就会触发熔断。触发熔断,断路器就会打开(open),此时所有请求都不能通过。在5s之后,断路器会恢复到半开状态(half open),会允许少量请求通过,如果这些请求都是健康的,那么断路器会回到关闭状态(close).如果这些请求还是失败的请求,断路器还是恢复到打开的状态(open).
traefik支持的触发器
创建 ingressRoute ,添加 circuitBreaker 中间件,指定 50% 的请求比例响应时间大于 1MS 时熔断。
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 秒。
$ 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
在实际的业务中,肯定会存在 4XX
5XX
相关的错误异常,如果每个应用都开发一个单独的错误页,无疑大大增加了开发成本,traefik 同样也支持自定义错误页,但是需要注意的是,错误页面不是由 traefik 存储处理,而是通过定义中间件,将错误的请求重定向到其他的页面。
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
有时候客户端和服务器之间会传输比较大的报文数据,这时候就占用较大的网络带宽和时长。为了节省带宽,加速报文的响应速速,可以将传输的报文数据先进行压缩,然后再进行传输,traefik也同样支持数据压缩。
traefik 默认只对大于 1024 字节,且请求标头包含 Accept-Encoding gzip
的资源进行压缩。可以指定排除特定类型不启用压缩或者根据内容大小来决定是否压缩。
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 字节,开启了压缩