惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
博客园_首页
宝玉的分享
宝玉的分享
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
T
Tailwind CSS Blog
雷峰网
雷峰网
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客
罗磊的独立博客
F
Fortinet All Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
阮一峰的网络日志
阮一峰的网络日志
I
InfoQ
Simon Willison's Weblog
Simon Willison's Weblog
D
DataBreaches.Net
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Scott Helme
Scott Helme
B
Blog
M
MIT News - Artificial intelligence
K
Kaspersky official blog
H
Help Net Security
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
Project Zero
Project Zero
The Hacker News
The Hacker News
B
Blog RSS Feed
T
Tor Project blog

lvbibir's Blog

shell | 磁盘空间分析脚本 Linux cat 和 tee 命令写入文件 python | 使用 uv 管理你的 python 环境 Claude Code 完整配置指南 windows | mihomo 内核独立部署指南 linux | 磁盘扩容 wsl | 释放长久运行占用的磁盘空间 mysql | 线程上限问题处理 麒麟 7.6 安装谷歌 OTP 认证模块 suse 12sp5 升级 openssh 及 openssl suse 12sp5 部署 mysql 5.7 《谁的青春不迷茫》 docker | centos7 部署 docker vim | 基础配置和使用 windows | rime 输入法 & 雾凇方案 shell | sshpass 批量传输文件及执行命令 wsl | 原生 linux 方式安装 docker nodejs | fnm + pnpm 开发环境配置 wsl | 安装配置 miniconda 虚拟环境 wsl | 自动更新系统代理 wsl | bashrc 环境变量不正确加载的处理方法 wsl | win10 安装 wsl2 troubleshooting | ssh 成功但是 scp 失败 linux | 常用命令总结 docker 部署 piclist shell | 检测网站存活并自动钉钉告警 Zabbix | 监控端口连通性并自动追踪 TCP 路由 windows | 自定义开机快速启动项 windows | miniconda 配置 python 虚拟环境 Zabbix | 监控主机到指定 ip 的流量大小 shell | centos 初始化 loki (二) 部署 loki (一) 简介 prometheus (六) Alertmanager prometheus (五) 记录规则与告警规则 prometheus (四) 黑盒监控 prometheus (三) 服务发现 prometheus (二) 静态配置 prometheus (一) 简介及部署 traefik (四) TraefikService 服务 traefik (二) ingressRoute 路由 traefik (一) 简介、部署和配置 kubernetes | Gateway API 简介及部署 linux | dns 配置文件中 search 和 options ndots 详解 kubernetes | command args 和 dockerfile 中的 ENTRYPOINT CMD kubernetes | statefulset 控制器详解 docker | dockerfile 最佳实践 docker | dockerfile 指令详解 linux | kill 命令详解以及 linux 中的信号 troubleshooting | elasticsearch 安装插件报错 shell | if 条件判断 docker | 下载外网镜像的几种方式 python | 鬼谷子数学问题 steam挂刀教程 linux | history 命令的格式化输出 shell | 将本地镜像批量推送到 harbor windows | hosts 文件修复 kubernetes | configmap & secret kubernetes | RBAC 鉴权和 NetworkPolicy kubernetes | service & ingress kubernetes | 存储 kubernetes | 滚动升级和自动伸缩 kubernetes | 控制器 kubernetes | 日志 kubernetes | 调度 kubernetes | pod kubernetes | 杂记 linux | set 命令详解 ceph | openeuler 部署 ceph-v16 ceph | ceph-v16 离线安装解决方案 pxe 如何应对复杂的服务器硬件环境 python | 批量修改目录下文件名 《微习惯》 ceph | openeuler (aarch64) 部署 ceph-v16 troubleshooting | glibc 误升级后修复 pxe 安装配置大全 《人间失格》 跑步日常 [置顶] Hello, hugo! shell | 检索某 url 中所有文件的内容 shell | 不同执行方式的区别 shell | 开启 debug 模式 vscode | 常见问题 windows & linux 多网卡时设置默认路由以及添加静态路由 mysql (二) 主从复制原理 GTID 并行复制 mysql (一) 部署 mysql | 杂记 cicd | jenkins 部署 mall-swarm 项目 httpd 源码打包编译成 rpm 包 openssl 源码打包编译成 rpm 包 centos7 | 修改网卡名称 ceph | pool pg_num 配置 docker | 脚本方式批量导出/导入镜像 centos 密码尝试次数过多问题处理 centos7 | 升级内核至 5.10 CVE-1999-0526 ceph | centos7 部署 ceph-v12 python | 修改 pip 源 troubleshooting | 安装 cloud-init 后导致 ssh 连接失败 windows | autohotkey 常用脚本
traefik (三) Middleware 中间件
lvbibir · 2023-04-18 · via lvbibir's Blog

0 前言

基于 centos7.9docker-ce-20.10.18kubelet-1.22.3-0traefik-2.9.10

示例中用到的 myappsecret 资源请查看系列文章第二篇中的演示

1 简介

官方文档

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

Traefik 内置了很多不同功能的 Middleware,主要是针对 HTTP 和 TCP,这里挑选几个比较常用的进行演示。

1.1 重定向 -redirectScheme

官方文档

定义一个 ingressroute,包含一个自动将 http 跳转到 https 的中间件

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp2
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`myapp2.test.com`)
    kind: Rule
    services:
    - name: myapp2
      port: 80
    middlewares:
    - name: redirect-https-middleware   # 指定使用RedirectScheme中间件,完成http强制跳转至https
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https-middleware
spec:
  redirectScheme:
    scheme: https   # 自动跳转到 https

测试,可以看到访问 http 自动 307 重定向到了 https

[root@k8s-node1 ~]# curl -I http://myapp2.test.com
HTTP/1.1 307 Temporary Redirect
Location: https://myapp2.test.com/
Date: Wed, 19 Apr 2023 07:58:34 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

1.2 去除请求路径前缀 -stripPrefix

官方文档

假设现在有这样一个需求,当访问 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"

1.3 白名单 -IPWhiteList

官方文档

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

示例

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`myapp1.test.com`) 
    kind: Rule
    services:
    - name: myapp1
      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
      - 1.1.1.253

测试

# 白名单外主机
[root@k8s-node1 ~]# curl -I http://myapp1.test.com
HTTP/1.1 403 Forbidden

# 白名单内主机
Admin@BJLPT0152 MINGW64 ~
$ ipconfig | grep 1.1.1.253
   IPv4 地址 . . . . . . . . . . . . : 1.1.1.253

Admin@BJLPT0152 MINGW64 ~
$ curl -i  http://myapp1.test.com
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

1.4 基础用户认证 -basicAuth

官方文档

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

安装 htpasswd 工具生成密码文件

[root@k8s-node1 ~]# yum install -y httpd
[root@k8s-node1 ~]# htpasswd -bc basic-auth-secret-lvbibir lvbibir 123
Adding password for user lvbibir
[root@k8s-node1 ~]# kubectl create secret generic basic-auth-lvbibir --from-file=basic-auth-secret-lvbibir
secret/basic-auth-lvbibir created

创建 ingressroute,使用 basicAuth 中间件

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`myapp1.test.com`) 
    kind: Rule
    services:
    - name: myapp1
      port: 80
    middlewares:
    - name: basic-auth-middleware
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: basic-auth-middleware
spec:
  basicAuth:
    secret: basic-auth-lvbibir

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

image-20230419163142212

官方文档

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

例如对 https://myapp2.test.com 提高安全策略,强制启用 HSTS

HSTS:即 HTTP 严格传输安全响应头,收到该响应头的浏览器会在 63072000s(约 2 年)的时间内,只要访问该网站,即使输入的是 http,浏览器会自动跳转到 https。(HSTS 是浏览器端的跳转,之前的 HTTP 重定向到 HTTPS 是服务器端的跳转)

创建 ingressRoute 和 headers 中间件

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp2-tls
spec:
  entryPoints:
  - web
  - websecure
  routes:
  - match: Host(`myapp2.test.com`)
    kind: Rule
    services:
    - name: myapp2
      port: 80 
    middlewares:
      - name: hsts-header-middleware
  tls:
    secretName: myapp2-tls         # 指定tls证书名称
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: hsts-header-middleware
spec:
  headers:
    customResponseHeaders:
      Strict-Transport-Security: 'max-age=63072000'

访问测试

[root@k8s-node1 ~]# curl -kI https://myapp2.test.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 65
Content-Type: text/html
Date: Wed, 19 Apr 2023 08:37:07 GMT
Etag: "5a9251f0-41"
Last-Modified: Sun, 25 Feb 2018 06:04:32 GMT
Server: nginx/1.12.2
Strict-Transport-Security: max-age=63072000   # headers 插件添加的响应头

1.6 限流 -rateLimit

官方文档

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

创建 ingressRoute 和 rateLimit 中间件

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp1
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`myapp1.test.com`)
    kind: Rule
    services:
    - name: myapp1  
      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

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

[root@k8s-node1 ~]# ab -n 100 -c 10 "http://myapp1.test.com/"

Concurrency Level:      10
Time taken for tests:   0.409 seconds
Complete requests:      100
Failed requests:        72
   (Connect: 0, Receive: 0, Length: 72, Exceptions: 0)
Non-2xx responses:      72

1.7 熔断 -circuitBreaker

官方文档

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

熔断器三种状态

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

服务熔断原理 (断路器的原理)

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

traefik 支持的触发器

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

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

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: myapp1
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`myapp1.test.com`)
    kind: Rule
    services:
    - name: myapp1  
      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

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

[root@k8s-node1 traefik]# ab -n 1000 -c 100  "http://myapp1.test.com/"
Concurrency Level:      100
Time taken for tests:   1.742 seconds
Complete requests:      1000
Failed requests:        999
   (Connect: 0, Receive: 0, Length: 2, Exceptions: 0)
Write errors:           0
Non-2xx responses:      999

1.8 自定义错误页 -errorPages

官方文档

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

首先,我们先创建一个应用。这个 web 应用的功能是:

  • 当请求 / 时,返回状态码为 200
  • 当请求 /400 时,返回 400 状态码
  • 当请求 /500 时,返回 500 状态码

创建 deployment svc

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask
spec:
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
      - name: flask
        image: cuiliang0302/request-code:v2.0
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
  name: flask
spec:
  type: ClusterIP
  selector:
    app: flask
  ports:
  - port: 5000
    targetPort: 5000

创建 ingressRoute

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: flask
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`flask.test.com`)
    kind: Rule
    services:
    - name: flask  
      port: 5000 

访问测试,模拟 400 500 错误

[root@k8s-node1 ~]# curl -I http://flask.test.com
HTTP/1.1 200 OK

[root@k8s-node1 ~]# curl -I http://flask.test.com/400
HTTP/1.1 400 Bad Request

[root@k8s-node1 ~]# curl -I http://flask.test.com/500
HTTP/1.1 500 Internal Server Error

[root@k8s-node1 ~]# curl -I http://flask.test.com/404
HTTP/1.1 404 Not Found

现在提出一个新的需求,当我访问 flask 项目时,如果错误码为 400,返回 myapp1 的页面,如果错误码为 500,返回 myapp2 的页面 (前提是 myapp1 和 myapp2 服务已创建)。

创建 errors 中间件

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: errors5
spec:
  errors:
    status:
      - "500-599"
    # query: /{status}.html   # 可以为每个页面定义一个状态码,也可以指定5XX使用统一页面返回
    query : /                 # 指定返回myapp2的请求路径
    service:
      name: myapp2
      port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: errors4
spec:
  errors:
    status:
      - "400-499"
    # query: /{status}.html   # 可以为每个页面定义一个状态码,也可以指定5XX使用统一页面返回
    query : /                 # 指定返回myapp1的请求路径
    service:
      name: myapp1
      port: 80

修改 ingressRoute

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: flask
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`flask.test.com`)
    kind: Rule
    services:
    - name: flask  
      port: 5000
    middlewares:
      - name: errors4
      - name: errors5

访问测试,可以看到 400 页面和 500 页面已经成功重定向了

[root@k8s-node1 ~]# curl http://flask.test.com/
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flask</title>
</head>
<body>
<h1>hello flask</h1>
<img src="/static/photo.jpg" alt="photo">
</body>
</html>

[root@k8s-node1 ~]# curl http://flask.test.com/400
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

[root@k8s-node1 ~]# curl http://flask.test.com/500
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>

1.9 数据压缩 -compress

官方文档

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

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

继续使用上面创建的 flask 应用,现在创建中间件并修改 ingressRoute,使用默认配置策略即可。

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: flask
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`flask.test.com`)
    kind: Rule
    services:
    - name: flask  
      port: 5000
    middlewares:
      - name: compress
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: compress
spec:
  compress: {}  

访问测试

html 文件小于 1024 字节,未开启压缩

image-20230419175108058

图片资源大于 1024 字节,开启了压缩

image-20230419175324586

以上