traefik与TLS自签证书
traefik与TLS自签证书
最近发现一个神器 traefik, 其实很久以前就 star 了的, 只是没有多加去了解; 这几天在测试各种负载均衡器, 又好好看了一下它, 感觉十分惊艳;
给太长不看的同学说一下本文核心:
- 一个端口代理所有
- TLS SNI
- 负载均衡的路径配置由谁维护
测试过程中被这个 TLS 的证书配置拦住了好久, 刚才终于理解了一些, 值得记录分享一下;
关于负载均衡的路径信息该在哪个节点维护
一般来说数据流有 3 个区域: 客户端 -> 负载均衡器(lb) -> N 个上游服务器(service)
路线一: 在客户端配置
-
一般模式: 客户端请求的 header 头内有一个字段包含 service 的标记, lb 根据这个标记转发到不同的服务器; 这个标记一般是个 cookie, lb 查询本地映射关系后转到指定的服务器; 如果连 lb 这里的映射信息也不想维护, 干脆可以在这个标记里面写明 server 的 ip 这个标记首次是怎么来的呢?可以是客户端内置的一个列表, 也可以是首次访问时 lb 随机指定;
-
特殊模式: 客户先请求一个接口, 获取一个地址列表, 然后选一个再发起业务请求, 例如DNS
优点: 简单, 可控, 客户端调试时很容易定位到这个请求是哪个服务器处理的; 缺点: 极端情况下变更服务节点可能需要同时改这三个区域的配置;
路线二: 在 LB 上配置
传统的 nginx, haprox 等都是这样做的; 即 LB 根据自身独立的路径配置规则然后转发到后端;
优点: 好理解, 可控程度高 缺点: 后端变更后(如新增节点), 需要再改一次 lb 的配置
路线三: 在后端服务上配置
核心是 lb 动态获取后端服务的变化, 有多种细分实现方法;
-
方法一: lb 提供一个接口, 后端服务上线时自动去 lb 上注册, 下线时主动注销;
-
方法二: 有一个公共注册中心或配置中心(etcd, consul, redis, eureka, mysql 等), service 去注册和注销, lb 动态去拉取更新, 或者是 lb 进行 watch 监听;
这两个方法都存在的问题: 程序异常停止没主动下线时的问题, 需要配合健康检查;
-
路线三: 和方法二差不多, 但 lb 不是监听注册中心, 而是监听 docker, kubernetes 等组件的 api, 服务一有变动立即可知; 程序内部不需要引入注册类的库, 历史老旧应用无需改造即可适配, 特别适合云原生环境;
优点: 一次变更只需要后端的服务改一次 缺点: 假设某个节点服务异常, 希望隔离流量待排查, 而不是直接下线服务时, 比较麻烦;
路线选择
根据实际情况选, 绝对性能型优先客户端负载, 可控型优先lb配置, 自动化程度要求高就由后端配置; 如果是云原生或微服务场景更推荐路线三
关于 traefik 我的看法
做负载均衡软件很多, 但这么有特色的就很少了; 当然其它代理或多或少也能实现这些功能, 但配置起来就麻烦一些;
traefik 的思路也是监听 docker 或 kubernetes 的 api, 但同时也支持监听 etcd, consol, 文件 等
特色: 一个端口代理所有
场景
假设你有很多的服务组件需要同时暴露出来, 需要考虑什么呢?
如果是单机, 首先得规划每个组件各自的端口, 避免冲突, 而且得考虑后续新增的组件端口规划; 如果生产环境等网络策略要求比较严格的地方, 还得去申请网络策略, 也需要考虑;
为了方便, 往往我们的做法都是提前申请和规划一个端口范围, 给以后的应用做预留
有没有一个办法可以做到 一个端口 代理所有业务呢? 如 http, https, redis, mysql, memcache 等杂七杂八的各种服务都通过一个端口来提供服务呢?后续新增的组件也都通过这个端口提供服务呢?再也不去管理这个lb的配置了呢?
有 以前的办法是(代理 + 隧道), 简单点就如 ssh 隧道, 但这种方式还是比较麻烦的, 还是要经常去配置几个节点, 不方便;
新办法核心原理: 根据TLS请求的 sni 来路由
简述步骤
- 制作一个通配符证书, 例如 *.services.wait
- traefik 监听一个端口, 并使用 这张证书
- 请求到来后, 根据 sni 路由到指定的 traefik 内部服务;
- traefik 内部服务负载到不同的后端节点上
实例
traefik TLS 的拦(难)点解析
- 如果没有提供默认证书, 则会使用 traefik 自己创建的一个证书
- 如果 sni 没有匹配的证书, 则会走默认证书; 特别注意这里的匹配 是"完全等于"; 即便是通配符证书也不等的; 所以下例走的是默认证书;
- tls 的内容属于动态内容, 不能加到静态配置里去
- 后端服务只需要在声明路由时 tls=true 即可;
## tls.yml
tls:
stores:
default:
defaultCertificate:
certFile: /ssl/services.wait.pem
keyFile: /ssl/services.wait-key.pem## docker-compose.yml
version: '3'
services:
traefik:
image: traefik:v2.9.8
command:
- "--api.insecure=true"
- "--log.level=DEBUG"
- "--entrypoints.port443.address=:443"
- "--providers.docker"
- "--providers.docker.exposedbydefault=false"
- "--providers.file"
- "--providers.file.directory=/etc/traefik/dynamic_conf"
- "--providers.file.watch=true"
ports:
- "443:443"
- "8080:8080" # web ui, 必须先启用 --api.insecure=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./tls.yml:/etc/traefik/dynamic_conf/tls.yml:ro
- /home/wait/sync/git/mycode/ssl/services.wait/:/ssl/:ro
baseWeb:
restart: unless-stopped
image: registry.wait/cwx/base-web:0.0.4
labels:
- "traefik.enable=true"
- "traefik.http.routers.r-web.entrypoints=port443"
- "traefik.http.routers.r-web.rule=Host(`web.services.wait`)"
- "traefik.http.routers.r-web.tls=true"
deploy:
replicas: 2
redis:
restart: unless-stopped
image: registry.wait/cwx/db/redis:7.0.7-alpine3.17
command:
[
"--maxmemory", "128mb"
]
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.r-redis.entrypoints=port443"
- "traefik.tcp.routers.r-redis.rule=HostSNI(`redis.services.wait`)"
- "traefik.tcp.routers.r-redis.tls=true"验证
curl --cacert /home/wait/code/ssl/ca/cwxCA.pem https://web.services.waitredis-cli -h redis.services.wait -p 443 \
--tls \
--cert /home/wait/code/ssl/services.wait/services.wait.pem \
--key /home/wait/code/ssl/services.wait/services.wait-key.pem \
--cacert /home/wait/code/ssl/ca/cwxCA.pem \
--sni redis.services.wait \
info