作为一款革新的边缘路由器 ,意味着 Traefik 是所构建的整个应用平台的守卫者,拦截并路由每一个接入的请求:基于所设定的逻辑和规则,以确定哪些服务处理对应的请求。关于 Traefik 的模型画像,具体可参考如下所示:
当然,除了所具备的服务代理特征之外, Traefik 同时也拥有“服务发现”功能机制,其动态检测后端服务状态信息并实时更新路由规则,从而达到服务治理之功效。
我们以 Docker Provider 场景作为参考模型对 Traefik 生态进行简要的剖析,以使得大家能够深入了解 Traefik 相关特性。
如下场景中,我们基于最新版的 Traefik v2.5.2 镜像为例,进行相关相关实例的部署及运行,具体如下所示:
[administrator@JavaLangOutOfMemory ~] % vi docker-compose.yaml
version: '3'
services:
reverse-proxy:
image: traefik:latest
# 开启 web UI 并且 Traefik 监听 Docker
command: --api.insecure=true --providers.docker
ports:
# HTTP 端口
- "80:80"
# Web UI 端口(通过 --api.insecure=true 启用)
- "8080:8080"
volumes:
# Traefik 监听 Docker 事件
- /var/run/docker.sock:/var/run/docker.sock
然后,我们运行此实例,具体如下:
[administrator@JavaLangOutOfMemory ~] % docker-compose up -d reverse-proxy
[administrator@JavaLangOutOfMemory ~] % curl -i http://192.168.56.114:8080/api/rawdata?json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 09 Sep 2021 02:05:08 GMT
Content-Length: 1361
{"routers":{"api@internal":{"entryPoints":["traefik"],"service":"api@internal","rule":"PathPrefix(`/api`)","priority":2147483646,"status":"enabled","using":["traefik"]},"dashboard@internal":{"entryPoints":["traefik"],"middlewares":["dashboard_redirect@internal","dashboard_stripprefix@internal"],"service":"dashboard@internal","rule":"PathPrefix(`/`)","priority":2147483645,"status":"enabled","using":["traefik"]},"reverse-proxy-traefik@docker":{"entryPoints":["http"],"service":"reverse-proxy-traefik","rule":"Host(`reverse-proxy-traefik`)","status":"enabled","using":["http"]}},"middlewares":{"dashboard_redirect@internal":{"redirectRegex":{"regex":"^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$","replacement":"${1}/dashboard/","permanent":true},"status":"enabled","usedBy":["dashboard@internal"]},"dashboard_stripprefix@internal":{"stripPrefix":{"prefixes":["/dashboard/","/dashboard"]},"status":"enabled","usedBy":["dashboard@internal"]}},"services":{"api@internal":{"status":"enabled","usedBy":["api@internal"]},"dashboard@internal":{"status":"enabled","usedBy":["dashboard@internal"]},"noop@internal":{"status":"enabled"},"reverse-proxy-traefik@docker":{"loadBalancer":{"servers":[{"url":"http://172.20.0.2:80"}],"passHostHeader":true},"status":"enabled","usedBy":["reverse-proxy-traefik@docker"],"serverStatus":{"http://172.20.0.2:80":"UP"}}}}
此时,我们也可以通过浏览器访问 http://192.168.56.114:8080/api/rawdata 接口来查看 Traefik 的 API 原始数据。
接下来,我们部署一个 Demo 服务,基于 Traefik 进行路由创建,具体如下所示:
version: '3'
services:
reverse-proxy:
# 官方的 Traefik 2.0 Docker 镜像
image: traefik:latest
# 开启 web UI 并且告诉 Traefik 监听 Docker
command: --api.insecure=true --providers.docker
ports:
# HTTP 端口
- "80:80"
# Web UI 端口(通过 --api.insecure=true 启用)
- "8080:8080"
volumes:
# 这样 Traefik 可以监听 Docker 事件
- /var/run/docker.sock:/var/run/docker.sock
whoami:
# 一个通过 API 暴露其 IP 地址的容器
image: containous/whoami
labels:
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
基于上面的 Yaml文件中,我们定义了 一个名称为 whoami 简单的 web 服务,此服务会打印部署的机器的相关信息(IP 地址、主机等等)。然后我们使用以下命令启动 whoami 服务,具体:
[administrator@JavaLangOutOfMemory ~] % docker-compose up -d whoami
此时,我们再次进行请求 Api 接口,结果如下所示:
[administrator@JavaLangOutOfMemory ~] % curl -i http://192.168.56.114:8080/api/rawdata?json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 09 Sep 2021 02:09:25 GMT
Content-Length: 1705
{"routers":{"api@internal":{"entryPoints":["traefik"],"service":"api@internal","rule":"PathPrefix(`/api`)","priority":2147483646,"status":"enabled","using":["traefik"]},"dashboard@internal":{"entryPoints":["traefik"],"middlewares":["dashboard_redirect@internal","dashboard_stripprefix@internal"],"service":"dashboard@internal","rule":"PathPrefix(`/`)","priority":2147483645,"status":"enabled","using":["traefik"]},"reverse-proxy-traefik@docker":{"entryPoints":["http"],"service":"reverse-proxy-traefik","rule":"Host(`reverse-proxy-traefik`)","status":"enabled","using":["http"]},"whoami@docker":{"entryPoints":["http"],"service":"whoami-traefik","rule":"Host(`whoami.docker.localhost`)","status":"enabled","using":["http"]}},"middlewares":{"dashboard_redirect@internal":{"redirectRegex":{"regex":"^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$","replacement":"${1}/dashboard/","permanent":true},"status":"enabled","usedBy":["dashboard@internal"]},"dashboard_stripprefix@internal":{"stripPrefix":{"prefixes":["/dashboard/","/dashboard"]},"status":"enabled","usedBy":["dashboard@internal"]}},"services":{"api@internal":{"status":"enabled","usedBy":["api@internal"]},"dashboard@internal":{"status":"enabled","usedBy":["dashboard@internal"]},"noop@internal":{"status":"enabled"},"reverse-proxy-traefik@docker":{"loadBalancer":{"servers":[{"url":"http://172.20.0.2:80"}],"passHostHeader":true},"status":"enabled","usedBy":["reverse-proxy-traefik@docker"],"serverStatus":{"http://172.20.0.2:80":"UP"}},"whoami-traefik@docker":{"loadBalancer":{"servers":[{"url":"http://172.20.0.3:80"}],"passHostHeader":true},"status":"enabled","usedBy":["whoami@docker"],"serverStatus":{"http://172.20.0.3:80":"UP"}}}}
通过查看 /api/rawdata 接口返回的数据,我们发现 Traefik 已自动检测到新的容器并更新了相应的配置。前面在介绍 Traefik 的时候,我们说过,当 Traefik 检测到新服务时,它会自动创建相应的路由,然后我们可以访问相应的路由。此时,我们借助 Curl 工具进行验证,具体如下所示:
[administrator@JavaLangOutOfMemory ~] % curl -H Host:whoami.docker.localhost http://127.0.0.1
Hostname: d1078dbb0332
IP: 127.0.0.1
IP: 172.20.0.3
RemoteAddr: 172.20.0.2:37580
GET / HTTP/1.1
Host: whoami.docker.localhost
User-Agent: curl/7.29.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.20.0.1
X-Forwarded-Host: whoami.docker.localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 814dfa472b98
X-Real-Ip: 172.20.0.1
接下来,我们对 whoami 服务进行扩容,以验证 Traefik 的负载均衡功能,具体操作如下所示:
[administrator@JavaLangOutOfMemory ~] % docker-compose up -d --scale whoami=4
traefik_reverse-proxy_1 is up-to-date
Creating traefik_whoami_2 ... done
Creating traefik_whoami_3 ... done
Creating traefik_whoami_4 ... done
此时,我们看一下扩容完成后当前我们的服务运行的容器状况,如下所示:
[administrator@JavaLangOutOfMemory ~] % docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------------------------------------------------------------------------
traefik_reverse-proxy_1 /entrypoint.sh --api.insec ... Up 0.0.0.0:80->80/tcp,:::80->80/tcp, 0.0.0.0:8080->8080/tcp,:::8080->8080/tcp
traefik_whoami_1 /whoami Up 80/tcp
traefik_whoami_2 /whoami Up 80/tcp
traefik_whoami_3 /whoami Up 80/tcp
traefik_whoami_4 /whoami Up 80/tc
[administrator@JavaLangOutOfMemory ~] % curl -i http://192.168.56.114:8080/api/rawdata?json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 09 Sep 2021 02:28:35 GMT
Content-Length: 1882
{"routers":{"api@internal":{"entryPoints":["traefik"],"service":"api@internal","rule":"PathPrefix(`/api`)","priority":2147483646,"status":"enabled","using":["traefik"]},"dashboard@internal":{"entryPoints":["traefik"],"middlewares":["dashboard_redirect@internal","dashboard_stripprefix@internal"],"service":"dashboard@internal","rule":"PathPrefix(`/`)","priority":2147483645,"status":"enabled","using":["traefik"]},"reverse-proxy-traefik@docker":{"entryPoints":["http"],"service":"reverse-proxy-traefik","rule":"Host(`reverse-proxy-traefik`)","status":"enabled","using":["http"]},"whoami@docker":{"entryPoints":["http"],"service":"whoami-traefik","rule":"Host(`whoami.docker.localhost`)","status":"enabled","using":["http"]}},"middlewares":{"dashboard_redirect@internal":{"redirectRegex":{"regex":"^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$","replacement":"${1}/dashboard/","permanent":true},"status":"enabled","usedBy":["dashboard@internal"]},"dashboard_stripprefix@internal":{"stripPrefix":{"prefixes":["/dashboard/","/dashboard"]},"status":"enabled","usedBy":["dashboard@internal"]}},"services":{"api@internal":{"status":"enabled","usedBy":["api@internal"]},"dashboard@internal":{"status":"enabled","usedBy":["dashboard@internal"]},"noop@internal":{"status":"enabled"},"reverse-proxy-traefik@docker":{"loadBalancer":{"servers":[{"url":"http://172.20.0.2:80"}],"passHostHeader":true},"status":"enabled","usedBy":["reverse-proxy-traefik@docker"],"serverStatus":{"http://172.20.0.2:80":"UP"}},"whoami-traefik@docker":{"loadBalancer":{"servers":[{"url":"http://172.20.0.4:80"},{"url":"http://172.20.0.5:80"},{"url":"http://172.20.0.3:80"},{"url":"http://172.20.0.6:80"}],"passHostHeader":true},"status":"enabled","usedBy":["whoami@docker"],"serverStatus":{"http://172.20.0.3:80":"UP","http://172.20.0.4:80":"UP","http://172.20.0.5:80":"UP","http://172.20.0.6:80":"UP"}}}}
此时,我们请求4次,以查看 Traefik 在四个实例之间的负载均衡,具体命令如下所示:
[administrator@JavaLangOutOfMemory ~] % curl -H Host:whoami.docker.localhost http://127.0.0.1
# 第一次
Hostname: 50f70b1ccace
IP: 127.0.0.1
IP: 172.20.0.4
RemoteAddr: 172.20.0.2:40000
...
# 第二次
Hostname: 8c2204231f98
IP: 127.0.0.1
IP: 172.20.0.5
RemoteAddr: 172.20.0.2:39660
...
# 第三次
Hostname: d1078dbb0332
IP: 127.0.0.1
IP: 172.20.0.3
RemoteAddr: 172.20.0.2:37600
...
# 第四次
Hostname: f1cf5b632fde
IP: 127.0.0.1
IP: 172.20.0.6
RemoteAddr: 172.20.0.2:50384
...
基于此,我们通过 http://192.168.56.114:8080/dashboard/#/ 访问 Traefik Web UI 以观测不同路由请求、服务、中间件以及其他相关联信息。具体如下所示:
在整个首页,我们可以看到 Traefik 所支持的不同协议的请求、服务类型,涉及 HTTP、TCP 以及 UDP 等。同时,显示不同类型的 Providers 信息,涉及 Docker、K8S 及其他相关信息等。基于 HTTP 协议,可以看到所建立的 4个路由规则,5个服务以及2个自定义中间件。以 Middlewares 为例,我们点击基于 HTTP 协议的 “Middlewares ” 版块的“Explore” 链接,可以清新地看到当前所部署应用所形成的链路拓扑调用链,具体如下所示:
针对 Traefik Middlewares 的自定义开发相关实践,大家若感兴趣的话,可参考之前文章:Traefik Middleware 插件实践,以便使的大家能够了解 Traefik 的功能之丰富。
针对证书的自定续订功能,也是 Traefik 组件的强大功能之一,基于此,我们将分别基于 HTTP 和 DNS 两方面进行简要阐述。
基于上述场景,其简要的活动流程解析为:
Traefik 使用 ACME(一种协议(精确约定的通信方式),用于协商来自 LE 的证书。它是 Traefik 的一部分) 向 LE(Let's Encrypt,一种提供免费证书的服务)请求特定域的证书,如,example.com。LE 使用一些随机生成的文本进行回答,Traefik 将这些文本放在服务器上的特定位置。LE 然后询问DNS Internet 服务器,例如 .com,该服务器指向某个 IP 地址。LE 通过端口 80/443 查看包含该随机文本的文件的 IP 地址。
如果存在,那么这证明了请求证书的人同时控制服务器和域,因为它显示了对 DNS 记录的控制。证书已颁发,有效期为3个月,剩余不足30天时,Traefik将自动尝试续订。
基于 HttpChallenge 的优势在于能够获得通配符证书。这些是验证所有子域 *.example.com 的证书此外,不需要打开任何端口。其相关文件配置如下所示:
## STATIC CONFIGURATION
log:
level: INFO
api:
insecure: true
dashboard: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
lets-encr:
acme:
#caServer: https://acme-staging-v02.api.letsencrypt.org/directory
storage: acme.json
email: whatever@gmail.com
httpChallenge:
entryPoint: web
基于此场景,Traefik 使用 ACME 向 LE 请求特定域的证书,如 example.com。LE 回答一些随机生成的文本,Traefik 将其作为新的 DNS TXT 记录。然后,LE 检查 example.com DNS 记录以查看文本是否存在。
如果它存在,那么这证明了请求证书的人控制了域,证书有效期为3个月。Traefik将在剩余时间不足30天时自动尝试续订。
相比基于 HttpChallenge ,Traefik 需要能够对 DNS 记录进行自动更新,因此需要任何管理 DNS 站点的人对此提供支持。这就是为什么要使用 Cloudflare。其相关文件配置如下所示:
## STATIC CONFIGURATION
log:
level: INFO
api:
insecure: true
dashboard: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
lets-encr:
acme:
#caServer: https://acme-staging-v02.api.letsencrypt.org/directory
email: whatever@gmail.com
storage: acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
除上述特性外,在 Traefik V2.3.x 及后续的版本中,引入了一系列最新功能,包括 Traefik 插件系统、与 Traefik Pilot 的集成、对 Amazon ECS 的支持等,使的 Traefik 生态组件功能越来越丰富,应用场景越来越广泛。
除了上述所展现的实例、告警、监控信息功能之外,Traefik Pilot 也支持自定义“插件”开发功能,其提供了丰富的插件类型,可结合实际的业务场景进行适应性装配,具体如下所示:
综上所述,作为一款云原生边缘路由器,Traefik 功能已经能够满足绝大部分的业务场景,同时,也落地不少的行业及应用。或许,在下一个版本中,我们将迎来更为强大的 Traefik 生态,包括对谷歌团队最新推出的 Kubernetes Service API 的支持,以及用于在 Traefik Mesh 中支持 mTLS 的功能。除上述外,其他可能潜在的功能特性也会随着市场的不断变化而应运而生,最后,让我们拭目以待~
参考资料