基于traefik的TCP-SNI路由和自动SSL证书
背景
- 有一台公有云vm,部署了k3s,之前主要用于内网穿透,集群中部署了frps,并且直接用hostNetwork接管了节点的443端口。
- 同时pod里还部署了一个nginx容器,接管节点的80端口,并直接把所有请求重定向到443端口。
- 由此实现了http流量和https流量全部走frps,穿透到内网的真实集群中。
近期产生了一个新的需求,打算在这台公有云节点上部署一些服务,同样需要用https暴露,但是不需要穿透到内网集群。由于之前的方案443端口被frps独占了,因此需要对集群进行改造,但不改动frps的配置。因此考虑增加一个443端口的网关,在tcp层面路由一下。
这个tcp网关可以考虑用traefik或ingress nginx实现,他们现在都支持在TCP层面对连接进行路由。由于k3s自带了traefik,所以优先考虑使用traefik。
部署traefik
由于之前禁用了traefik,需要重新启用。
# 之前禁用的方法
touch /var/lib/rancher/k3s/server/manifests/traefik.yaml.skip
# 重新启用
rm /var/lib/rancher/k3s/server/manifests/traefik.yaml.skip
k3s自动部署了traefik后,我们需要优化下其arg
- '--accesslog'
- '--certificatesresolvers.myresolver.acme.tlschallenge'
- '--certificatesresolvers.myresolver.acme.email=xxxxx@xxxxx.com'
- '--certificatesresolvers.myresolver.acme.storage=/data/acme.json'
准备集群LB
由于之前直接用hostNetwork的pod接管了443和80,所以没用到service。
现在要使用更复杂的网络结构,service的使用就必须了,比如后面traefik的IngressRouteTCP是要以service作为路由目标的。
为了方便暴露服务到外部,可以通过k3s自带的serviceLB组件,用LB service暴露服务,如果部署时没有禁用该组件,LB类型的service会触发daemonset pod创建,并用iptalbes将node上的流量转发到service上再到pod里。
尝试external IP
使用公有云的k8s,由于是有真实公网IP的,可以考虑为node配置externalIP,这样LB service配置好后,其external IP也会被设置为node的external IP,看起来更cloud native一些。
# k3s server node:vim /etc/systemd/system/k3s.service
ExecStart=/usr/local/bin/k3s \
server \
'--node-external-ip' \
'x.x.x.x' \
systemctl daemon-reload
systemctl restart k3s
此时可以看到node有了external IP,LB service的external ip也变为了该IP
但是需要注意的是,node有了external IP后,集群中连接k8s api server时会被iptables转一趟
-A KUBE-SEP-57TZDVPHHE3XNPCJ -p tcp -m comment --comment "default/kubernetes:https" -m tcp -j DNAT --to-destination X.X.X.X:6443
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https -> X.X.X.X:6443" -j KUBE-SEP-57TZDVPHHE3XNPCJ
如果公有云的防火墙没有开放6443,集群里的pod会无法连接api server。所以该步骤可选。
修改fprs部署方式
- 加service,443用cluster IP,监听frpc的端口用lb,对应重定向用的nginx的80端口也用lb
- hostNetwork改为false
如此一来,frps pod中,443不再对外暴露,只从traefik接受流量。80和监听frpc的端口不变,仍然直接暴露,分别用于重定向和接受frpc的连接。
traefik基于SNI的TCP流量分发
核心思路就是利用IngressRouteTCP这一CR,配置对同一traefik entrypoint流量在TCP层的分发。
需要解决的主要问题:
- 分发规则:主要参考文档rule部分进行配置,实现匹配主域名和所有二级域名。
- https卸载/透传:主要通过`
spec.tls.passthrough
字段控制。 - https证书:在原来走fprs的流量,我们透传了https流量后,仍由frps解决证书部分,此处无需关心。
最终的yaml如下
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: frps
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: HostSNIRegexp(`{sub:^[^.]+\.(?:wubw.fun)$}`) || HostSNI(`wubw.fun`)
services:
- name: frps-internal
port: 443
tls:
passthrough: true
关于IngressRouteTCP CR的字段,参考文档
新增服务使用IngressRoute暴露
在确定原有的frps部分的服务能够正常路由后,可以开始考虑新加服务的部署方式。
新加的https服务需求:
- https
- 由traefik内置的acme支持自动管理证书
- 走原生的k8s ingress暴露,方便以后新服务扩展
经过尝试,直接使用IngressRoute,即可仍然通过ingress暴露集群内服务。更秒的是,即使ingress用到的域名被上面TCP路由规则包含了,但是优先级依然是ingress的更高。所以二者结合使用互不干扰:ingress配置的域名走ingress,没配置ingress且被IngressRouteTCP捕获的域名则被分发到frps走原来的路径。
自动配置证书:在上述部署traefik后修改了arg后,traefik已经有了resolver,在ingressRoute中直接引用resolver即可自动为host配置证书。
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: uptime
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`uptime.wubw.fun`)
services:
- name: uptime
port: 80
tls:
certResolver: myresolver
参考
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。