Traefik 2.0 介绍

traefik 是一个开源的反向代理和负载均衡工具,现在官方介绍中将其定位为云原生的边缘路由器,且用了一堆修饰词:简单、自动、高速、全面、开源、产品级、内置监控指标和主流集群技术集成等等。
当然,官方如此描述也是有理有据的,其开源后热度直线上升,而且被广泛使用,尤其是在当下的“微服务”、“云原生”场景。

从我个人的角度讲,其相对于传统的反向代理显得更“现代”:

  • 动态的更新配置,不需要重启/reload等操作。
  • 原生支持Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS等等后端。
  • 支持 RestAPI
  • 支持后端健康状态检查,根据状态自动地配置

这些特点,让其在云原生的场景下更贴合。而traefik本身的特性远不止如此。大家有兴趣可阅读它的官方文档:https://docs.traefik.io/,而且国内的资料介绍也很多。
本篇文章的背景是因为工作中用到了版本2中的一些特性,但国内关于新版的介绍资料较少,遂对 back to traefik 2.0 这篇文章的核心部分做了简单翻译提取。如有理解错漏,请指正。

目前(2019年5月21日)2.0 正在开发中,alpha4 版本在 4月17日发布。我们一起看看 2.0 有哪些东西。

核心概念

  • Providers 是指你正在使用的集群技术 (Kubernetes, Docker, Consul, Mesos, Rancher…). Traefik 通过 provider 的 API 来发现对应到你的服务的 routes(路由).
  • Entrypoints, 是最基本的配置,指监听请求的端口.
  • Services 是在你的基础设施上的运行的软件的体现. Traefik 知道如何处理你的程序的多个实例(当前提供多种负载均衡的能力/方法), 然后通过 services 配置如何联通到真实的运行程序.
  • Routers 将传入的请求和你的 service 连接起来. 他们持有的 rules 决定哪个 service 将处理对应的请求.
  • 最后, 中间件是可以在请求被 service 处理之前对其进行更新的组件.Traefik 提供了一些开箱即用的中间件来处理 认证、速率限制、断路器、白名单、缓冲等等.

配置结构

从图中可以看到,配置氛围静态配置和动态配置。

  • 静态配置,启动的时候加载。包括 Entrypoints、Provider 连接信息
  • 动态配置,运行时可动态读取改变的。包括 Routes、Services、Middlewares、Certificates 。

新特性

TCP 支持

历时三年,经过大量的讨论和开发,目前 traefik 完全支持 TCP 协议。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
[entrypoints]
[entrypoints.web]
address = ":80"
[entrypoints.mongo-port]
address = ":27017"
[tcp] # YAY!
[tcp.routers]
[tcp.routers.everything-to-mongo]
entrypoints = ["mongo-port"]
rule = "HostSNI(`*`)" # Catches every request
service = "database"

使用 FileProvider,重定向所有 27010 端口的请求到数据库服务。

来看更厉害的,Traefik 支持基于 SNIs 的路由:

服务器名称指示(SNI)是TLS计算机网络协议的扩展,通过该协议,客户机在握手过程开始时指示要连接到哪个主机名。这允许服务器在相同的IP地址和TCP端口号上显示多个证书,因此允许由相同的IP地址提供多个安全(HTTPS)站点(或TLS上的任何其他服务),而不需要所有这些站点使用相同的证书。它在概念上等同于HTTP/1.1基于名称的虚拟主机,但适用于HTTPS。所需的主机名在原始SNI扩展中没有加密,因此窃听者可以看到请求的是哪个站点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[entrypoints]
[entrypoints.web]
address = ":80"
[entrypoints.mongo-port]
address = ":27017"
[tcp] # YAY!
[tcp.routers]
[tcp.routers.to-db-1]
entrypoints = ["mongo-port"]
rule = "HostSNI(`db-1.domain`)"
service = "db-1"
[tcp.routers.to-db-1.tls] # The route is for TLS requests only
[tcp.routers.to-db-2]
entrypoints = ["mongo-port"]
rule = "HostSNI(`db-2.domain`)"
service = "db-2"
[tcp.routers.to-db-2.tls] # The route is for TLS requests only

更更厉害的呢?Traefik 可以牛到在同一个端口上同时支持 HTTP 和 TCP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[entrypoints]
[entrypoints.the-one]
address = ":443"
[tcp]
[tcp.routers]
[tcp.routers.to-db-1]
rule = "HostSNI(`db-1.domain`)"
service = "db-1"
[tcp.routers.to-db-1.tls] # The route is for TLS requests only
[http]
[http.routers]
[http.routers.my-api]
rule = "Host(`api.domain`)"
service = "my-api"

在它的第一个alpha版本中,Traefik 只在 FileProvider 中可以启用TCP路由,但是请做好准备,因为它很快就会对其他 Providers 可用!

用来更新请求的中间件(Middlewares)

过去很多特性被嵌入到 Traefik 中,但没有针对性的定制和对应选项去调整。

Middlewares 填补了这一空白。你可以做到针对每一个 router 开启和调整相关特性。

Middlewares 是在请求实际转发到服务之前对其进行操作的组件,如果不满足要求的条件,甚至可以决定不转发请求。

在发布时,Traefik附带了以下中间件:

  • AddPrefix(给请求添加一个前缀路径)
  • BasicAuth
  • DigestAuth
  • ForwardAuth(委托第三方服务身份验证)
  • Buffering
  • Chain (定义可重用的Middleware集和)
  • CircuitBreaker (断路器,避免调用压垮服务)
  • Compress
  • Errors(提供自定义的错误页面)
  • Headers
  • IpWhitelist
  • MaxConn(限制连接到服务的并发连接数)
  • PassTLSClientCert
  • RateLimit(在给定时间段内限制对服务的请求数量)
  • RedirectRegex
  • RedirectScheme
  • ReplacePath(在转发到服务之前更新请求路径)。

更重要的,项目重构了代码,使未来更方便地提供更多的中间件。

Kubernetes & CRD(CustomResourceDefinition)

在过去两年里,社区围绕“更好的 ingress”进行了大量的讨论,回看之前的 kubernettes provider,我们认为还有改进的空间。对于V2,如果我们想让 k8s 用户直接从中受益(比如 TCP 和 middleware),而不被大量 annotations 困扰,我们必须提供新的选择。

在备选方案中,CRD 越来越受欢迎,因为它们解决了ingress规范的缺点。受 Heptio 在 Contour 项目中的 IngressRoute 启发,我们扩展了这个规范来实现每个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
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.crd
namespace: default
spec:
entrypoints:
- web
- web-secure
routes:
- match: Host(`traefik.io`) && PathPrefix(`/foo`)
kind: Rule
services:
- name: whoami1
port: 80
strategy: RoundRobin
middlewares:
- name: stripprefix
- name: addprefix
namespace: foo
- match: Host(`containo.us`) && Method(`POST`)
kind: Rule
services:
- name: whoami2
port: 80
tls:
secretName: supersecret

新的更有表现力的路由规则语法

Traefik 现在支持更有表现力的语法来定义 router rules,包含 and, orparenthesis(括号)!

可用的 matchers 有 Headers, HeadersRegexp, Host, HostRegexp, Method, Path, PathPrefix, and Query.

由于 TCP 是完全不同的东东,所以目前只支持专用的 matcher:HostSNI。
Since TCP is a whole different world, for now, it only supports a dedicated matcher: HostSNI.

来看个例子:

1
2
rule = (Host(`api.domain`) && PathPrefix(`/v2`)) || Host(`api-v2.domain`)
rule = (Method(`DELETE`) || (Method(`POST`) && Query(`action`, `delete`))) && Host('api.domain')

Cross Provider

Traefik 一直与很多 providers 兼容, 这是他的优势之一:无论你的基础设施是什么,从裸机到各种协调器和集群方案,Traefik 都能搞定!

但从 V2 开始,我们更进一步做到了让用户在一个 provider 中声明元素(middlewares, services, routers)在其他不同的 provider 中使用。

让我们来看一个例子,例子中在配置文件中(file provider)声明了一个认证 middleware,在 Docker label(Docker Provider)中被使用。

1
2
3
4
# somewhere in a configuration file for the file provider
[http.middlewares.my-users.basicauth]
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
1
2
3
4
5
# somewhere in a docker compose file
your-container:
image: your-docker-image
labels:
- "traefik.http.routers.my-router.middlewares=file.my-users"

当然,你可以在一个 provider 中声明 router 指向其他 provider 中定义的 services。

TLS Termination per Route

为了添加 TCP 支持到 Traefik 中,我们几乎重新思考了所有事情,从集群的大门-entrypoint 开始。

我们起初的想法是每个入口点(包括其端口)指定一种类型的协议。不幸的是,在某些情况下,特别是当人们使用Traefik为多个集群路由请求时,我们认为它的限制太大。因此,我们允许每个入口点有多个协议。

之后,讨论依然存在其他方面,我们一直在寻找让用户在配置TLS termination 或 passthrough 时有更好的控制方式。在路由器级别启用TLS的想法赢得了头脑风暴,然后通过了概念证明的测试(我们团队中有相当多的概念证明分支,以至于它几乎成为了一个迷因)。

下面是三个路由器监听同一个入口点的例子,第一个 router 完成TLS连接(在HTTPS上),第二个 router 完成TLS连接(在TCP上),第三个 router 向后传递,将 TLS连接的细节处理交给服务本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[entrypoints]
[entrypoints.web-secure]
address = ":443"
[http]
[http.routers.to-service-1]
rule = "Host(`domain-1`)"
service = "service-1"
[http.routers.to-service-1.tls]
# terminates the tls connection and sends clear data
# to service 1
[tcp]
[tcp.routers.to-service-2]
rule = "HostSNI(`domain-2`)"
service = "service-2"
[tcp.routers.to-service-2.tls]
# terminates the tls connection and sends clear data
# to service 2
[tcp.routers.to-service-3]
rule = "HostSNI(`domain-3`)"
service = "service-3"
[tcp.routers.to-service-3.tls]
passthrough = true # sends encrypted data "as is" to service-3

Labels, Key-Value Configuration, Tags, …

这只是表面上的一个小调整,但是对代码有很大的影响,我们重新编写了配置解析器。这个新系统确保 Traefik 中的每个选项都有相同的路径,无论是否用TOML [something.that.is.here]或是容器上的一个 label something.that.is.there ,或是一个键(放在键值存储) something/that/is/somewhere,或者是将来可能可用的任何东西。

修改了文档

我们更新了文档结构来帮助用户快速的理解如何配置 Traefik。同时,我们列出了配置结构的大纲,让专业用户更方便的学习配置的细节。

阅读最终版

是的,更多的内容还在路上,只是我们(团队)迫不及待去分享了。

新的 WebUI, 剩余的 providers, metrics, UDP, YAML, TLS stores & options, canary(金丝雀), 更多的文档 — 什么都是有可能的.

除了重新启用其他 providers (alpha只支持 file、Docker和k8s),并为所有这些 provider 启用TCP(目前只支持 file)之外,路线图还具有很多特性。

首先,最明显的是,一个贡献者正在积极地开发一个经过改进的WebUI,从我们看到的模型来看,它看起来非常有前途。我们希望这个新UI能够帮助您无缝地浏览数百条路由。

其次,我们正在对TLS配置进行改进,包括存储和选项,他们应该自解释。

再次,既然我们已经尝试了一种新的协议,那么我们不妨添加其他协议(如UDP)。

2.0 文档地址:https://docs.traefik.io/v2.0/,大的变更地址:https://github.com/containous/traefik/blob/master/CHANGELOG.md.

谢谢鼓励