Kubernetes (K8S)中Traefik路由(ingressRoute)
王先森2023-08-172023-08-17
ingressRoute简介
kubernetes 中使用 Traefik ingress 的 ingressRoute 代理 http
、https
、tcp
、udp
。
官方文档
三种方式
Traefik 创建路由规则有多种方式,比如:
- 原生
Ingress
写法 - 使用 CRD
IngressRoute
方式 - 使用
GatewayAPI
的方式
相较于原生 Ingress 写法,ingressRoute 是 2.1 以后新增功能,简单来说,他们都支持路径 (path) 路由和域名 (host) HTTP 路由,以及 HTTPS 配置,区别在于 IngressRoute 需要定义 CRD 扩展,但是它支持了 TCP、UDP 路由以及中间件等新特性,强烈推荐使用 ingressRoute
匹配规则
规则 | 描述 |
---|---|
Headers(key, value) | 检查headers中是否有一个键为key值为value的键值对 |
HeadersRegexp(key, regexp) | 检查headers中是否有一个键位key值为正则表达式匹配的键值对 |
Host(example.com, boysec.cn, …) | 检查请求的域名是否包含在特定的域名中 |
HostRegexp(example.com, {subdomain:[a-z] }.example.com, …) | 检查请求的域名是否包含在特定的正则表达式域名中 |
Method(GET, …) | 检查请求方法是否为给定的methods(GET、POST、PUT、DELETE、PATCH)中 |
Path(/path, /articles/{cat:[a-z] }/{id:[0-9] }, …) | 匹配特定的请求路径,它接受一系列文字和正则表达式路径 |
PathPrefix(/products/, /articles/{cat:[a-z] }/{id:[0-9] }) | 匹配特定的前缀路径,它接受一系列文字和正则表达式前缀路径 |
Query(foo=bar, bar=baz) | 匹配查询字符串参数,接受key=value的键值对 |
ClientIP(10.0.0.0/16, ::1) | 如果请求客户端 IP 是给定的 IP/CIDR 之一,则匹配。它接受 IPv4、IPv6 和网段格式。 |
通过dashboard演示
如下所示通过 Ingress
创建一个资源对象:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: traefik-dashboard
namespace: kube-system
annotations:
kubernetes.io/ingress.class: traefik # 使用 traefk 的 IngressClass
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: ingress.od.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: traefik-v2
port:
number: 8090
访问: http://ingress.od.com
如下所示通过 ingressRoute
创建一个资源对象:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: kube-system
spec:
entryPoints:
- web
routes:
- match: Host(`traefik.od.com`) # 指定域名
kind: Rule
services:
- name: api@internal
kind: TraefikService
访问:http://traefik.test.com
ingressRoute
http路由
实现目标:集群外部用户通过访问http://whoami.od.com 域名时,将请求代理至whoami应用。
创建如下所示的 whoami
资源配置清单
cat > whoami.yml <<EOF
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: whoami
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoami
labels:
app: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- name: web
containerPort: 80
EOF
定义一个 IngressRoute 对象
代码语言:javascript复制cat > who-ing.yml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroute-demo
spec:
entryPoints:
- web
routes:
- match: Host(`whoami.od.com`) && PathPrefix(`/notls`)
kind: Rule
services:
- name: whoami
port: 80
EOF
通过 entryPoints
指定了我们这个应用的入口点是 web
,也就是通过 80 端口访问,然后访问的规则就是要匹配 whoami.od.com
这个域名,并且具有 /notls
的路径前缀的请求才会被 whoami
这个 Service 所匹配。我们可以直接创建上面的几个资源对象,然后对域名做对应的解析后,就可以访问应用:http://whoami.od.com/notls
https 路由
如果我们需要用 HTTPS 来访问我们这个应用的话,就需要监听 websecure
这个入口点,也就是通过 443 端口来访问,同样用 HTTPS 访问应用必然就需要证书,这里我们用 openssl
来创建一个自签名的证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=whoami.od.com"
然后通过 Secret 对象来引用证书文件:
代码语言:javascript复制# 要注意证书文件名称必须是 tls.crt 和 tls.key
kubectl create secret tls who-tls --cert=tls.crt --key=tls.key
这个时候我们就可以创建一个 HTTPS 访问应用的 IngressRoute 对象了
TraefikIngress
代码语言:javascript复制cat >> who-ing.yml <<EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroute-tls-demo
spec:
entryPoints:
- websecure
routes:
- match: Host(`whoami.od.com`) && PathPrefix(`/tls`)
kind: Rule
services:
- name: whoami
port: 80
tls:
secretName: who-tls
EOF
代码语言:javascript复制apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingressroute-tls-demo2
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
tls:
- secretName: who-tls
rules:
- host: whoami.od.com
http:
paths:
- pathType: Prefix
path: /ssl
backend:
service:
name: whoami
port:
number: 80
创建完成后就可以通过 HTTPS 来访问应用了,由于我们是自签名的证书,所以证书是不受信任的:
Traefik
访问:https://whoami.od.com/tls
Ingress
访问https://whoami.od.com/ssl
ingressRouteTCP
简单TCP服务
Traefik2.X 已经支持了 TCP 服务的,下面我们以 mongo 为例来了解下 Traefik 是如何支持 TCP 服务得。
ingreeRouteTCP 官方文档
代码语言:javascript复制cat > mongo.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-traefik
labels:
app: mongo-traefik
spec:
selector:
matchLabels:
app: mongo-traefik
template:
metadata:
labels:
app: mongo-traefik
spec:
containers:
- name: mongo
image: mongo:5.0
ports:
- containerPort: 27017
---
apiVersion: v1
kind: Service
metadata:
name: mongo-traefik
spec:
selector:
app: mongo-traefik
ports:
- port: 27017
EOF
创建成功后就可以来为 mongo 服务配置一个路由了。由于 Traefik 中使用 TCP 路由配置需要 SNI
,而 SNI
又是依赖 TLS
的,所以我们需要配置证书才行,如果没有证书的话,我们可以使用通配符 *
进行配置,我们这里创建一个 IngressRouteTCP
类型的 CRD 对象(前面我们就已经安装了对应的 CRD 资源)
cat > mongo-ingressroute-tcp.yaml <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`*`) # 由于 Traefik 中使用 TCP 路由配置需要 SNI,而 SNI 又是依赖 TLS 的,所以我们需要配置证书才行,如果没有证书的话,我们可以使用通配符*(适配ip)进行配置
services:
- name: mongo-traefik
port: 27017
EOF
要注意的是这里的 entryPoints
部分,是根据我们启动的 Traefik 的ConfigMap
静态配置中的 entryPoints 来决定的,我们可以自己添加一个用于 mongo 服务的专门入口点。关于 entryPoints 入口点的更多信息,可以查看文档 entrypoints 了解更多信息。
vim cm.yml
...
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
traefik:
address: ":8090"
metrics:
address: ":9100"
mongo:
address: ":27017" # 配置27017端口,作为mongo入口
...
然后更新 Traefik 重启即可
代码语言:javascript复制kubectl apply -f cm.yml
kubectl delete pods -n kube-system -l app=traefix-v2
创建完成后,同样我们可以去 Traefik 的 Dashboard 页面上查看是否生效:
然后我们配置一个域名 mongo.local
解析到 Traefik 所在的节点,然后通过 27017 端口来连接 mongo 服务:
带 TLS 证书的 TCP
上面我们部署的 mongo 是一个普通的服务,然后用 Traefik 代理的,但是有时候为了安全 mongo 服务本身还会使用 TLS 证书的形式提供服务,将上面证书放置到 certs 目录下面,然后我们新建一个 tls-mongo
的目录,在该目录下面执行如下命令来生成证书:
# 生成根证书
#-x509: 用于生成自签证书,如果不是自签证书则不需要此项
#-days: 证书的有效期限,默认是365天
#直接带参数的输入,直接输密码即可
openssl req -out ca.pem -new -x509 -days 3650 -subj "/C=CN/ST=BeiJing/O=Boysec/CN=server1/CN=Boysec/emailAddress=wangxiansen@boysec.cn"
#密码自行配置即可,passwd
# 生成证书私钥
openssl genrsa -out server.key 2048
# 生成证书申请文件 cat server.req
# CN=mongo.local 是mongo机器运行的节点域名信息,如果对不上就会报错
openssl req -key server.key -new -out server.req -subj "/C=CN/ST=BeiJing/O=Boysec/CN=server1/CN=Boysec/CN=mongo.local/emailAddress=wangxiansen@boysec.cn"
# 生成证书
openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAcreateserial -out server.crt -days 3650
# 合并私钥和公钥,生成server.pem
cat server.key server.crt > server.pem
在 tls-mongo/certs
目录下面执行如下命令通过 Secret 来包含证书内容:
$ kubectl create secret tls mongo-certs --cert=server.crt --key=server.key
然后重新更新 IngressRouteTCP
对象,增加 TLS 配置:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`mongo.local`)
services:
- name: mongo-traefik
port: 27017
tls:
secretName: mongo-certs
同样更新后,现在我们直接去访问应用就会被 hang 住,因为我们没有提供证书,这个时候我们可以带上证书来进行连接
代码语言:javascript复制$ mongo --host mongo.local --port 27017 --ssl --sslCAFile=./ca.pem --sslPEMKeyFile=./server.pem
MongoDB shell version v4.4.24
connecting to: mongodb://mongo.local:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("4a8a66b1-7371-415f-b353-23d71648e054") }
MongoDB server version: 5.0.5
WARNING: shell and server versions do not match
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
可以看到现在就可以连接成功了,这样就完成了一个使用 TLS 证书代理 TCP 服务的功能,这个时候如果我们使用其他的域名去进行连接就会报错了,因为现在我们指定的是特定的 HostSNI:
代码语言:javascript复制mongo --host traefik.od.com --port 27017 --ssl --sslCAFile=./ca.pem --sslPEMKeyFile=./server.pem
{"t":{"$date":"2023-07-22T03:52:20.300Z"},"s":"W", "c":"CONTROL", "id":23321, "ctx":"main","msg":"Option: This name is deprecated. Please use the preferred name instead.","attr":{"deprecatedName":"ssl","preferredName":"tls"}}
{"t":{"$date":"2023-07-22T03:52:20.300Z"},"s":"W", "c":"CONTROL", "id":23321, "ctx":"main","msg":"Option: This name is deprecated. Please use the preferred name instead.","attr":{"deprecatedName":"sslPEMKeyFile","preferredName":"tlsCertificateKeyFile"}}
{"t":{"$date":"2023-07-22T03:52:20.300Z"},"s":"W", "c":"CONTROL", "id":23321, "ctx":"main","msg":"Option: This name is deprecated. Please use the preferred name instead.","attr":{"deprecatedName":"sslCAFile","preferredName":"tlsCAFile"}}
MongoDB shell version v4.4.24
connecting to: mongodb://k8s.mongo.local:27017/?compressors=disabled&gssapiServiceName=mongodb
Error: couldn't connect to server k8s.mongo.local:27017, connection attempt failed: HostNotFound: Could not find address for k8s.mongo.local:27017: SocketException: Host not found (authoritative) :
connect@src/mongo/shell/mongo.js:374:17
@(connect):2:6
exception: connect failed
exiting with code 1
ingressRouteUDP
此外 Traefik2.3.x 版本也已经提供了对 UDP 的支持,所以我们可以用于诸如 DNS 解析的服务提供负载。同样首先部署一个如下所示的 UDP 服务:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: whoamiudp
spec:
ports:
- protocol: UDP
name: udp
port: 8080
selector:
app: whoamiudp
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoamiudp
labels:
app: whoamiudp
spec:
replicas: 2
selector:
matchLabels:
app: whoamiudp
template:
metadata:
labels:
app: whoamiudp
spec:
containers:
- name: whoamiudp
image: containous/whoamiudp
ports:
- name: udp
containerPort: 8080
部署完成后我们需要在 Traefik 中定义一个 UDP 的 entryPoint 入口点,修改我们部署 Traefik 的 ConfigMap
文件,增加 UDP 协议的入口点:
...
entryPoints:
web:
address: ":80" ## 配置 80 端口,并设置入口名称为 web
websecure:
address: ":443" # 配置443端口,并设置入口名称为 websecure
traefik:
address: ":8090" ## 配置 8090 端口,并设置入口名称为 dashboard
metrics:
address: ":9100" ## 配置 9100 端口,作为metrics收集入口
mongo:
address: ":27017" # 配置9200端口,作为tcp入口
udpep:
address: ":9300/udp" # 配置9300端口,作为udp入口
...
然后更新 Traefik 重启即可
代码语言:javascript复制kubectl apply -f cm.yml
kubectl delete pods -n kube-system -l app=traefix-v2
UDP 的入口点增加成功后,接下来我们可以创建一个 IngressRouteUDP
类型的资源对象,用来代理 UDP 请求:
cat <<EOF | kubectl apply -f -
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: whoamiudp
spec:
entryPoints:
- udpep
routes:
- services:
- name: whoamiudp
port: 8080
EOF
$ kubectl get ingressrouteudp
NAME AGE
whoamiudp 27s
创建成功后我们首先在集群上通过 Service 来访问上面的 UDP 应用:
代码语言:javascript复制$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
whoamiudp ClusterIP 192.168.145.194 <none> 8080/UDP 112s
$ echo "WHO" | socat - udp4-datagram:192.168.145.194:8080
Hostname: whoamiudp-7d968ff858-c425d
IP: 127.0.0.1
IP: 172.16.130.8
$ echo "wangxiansen" | socat - udp4-datagram:192.168.145.194:8080
Received: wangxiansen
我们这个应用当我们输入 WHO
的时候,就会打印出访问的 Pod 的 Hostname 这些信息,如果不是则打印接收到字符串。现在我们通过 Traefik 所在节点的 IP(mongo.local)与 9300 端口来访问 UDP 应用进行测试:
我们可以看到测试成功了,证明我就用 Traefik 来代理 UDP 应用成功了。除此之外 Traefik 还有很多功能,特别是强大的中间件和自定义插件的功能(下一章讲),为我们提供了不断扩展其功能的能力,我们完成可以根据自己的需求进行二次开发。