Kubernetes (K8S)中Traefik路由(ingressRoute)

ingressRoute简介

kubernetes 中使用 Traefik ingress 的 ingressRoute 代理 httphttpstcpudp

官方文档

三种方式

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创建一个资源对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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创建一个资源对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.od.com

ingressRoute

http路由

实现目标:集群外部用户通过访问http://whoami.od.com 域名时,将请求代理至whoami应用。

创建如下所示的 whoami 资源配置清单

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
28
29
30
31
32
33
34
35
36
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 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 来创建一个自签名的证书

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=whoami.od.com"

然后通过 Secret 对象来引用证书文件:

1
2
# 要注意证书文件名称必须是 tls.crt 和 tls.key
kubectl create secret tls who-tls --cert=tls.crt --key=tls.key

这个时候我们就可以创建一个 HTTPS 访问应用的 IngressRoute 对象了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 官方文档

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
28
29
30
31
32
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 资源)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 了解更多信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vim cm.yml
...
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
traefik:
address: ":8090"
metrics:
address: ":9100"
mongo:
address: ":27017" # 配置27017端口,作为mongo入口
...

然后更新 Traefik 重启即可

1
2
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 的目录,在该目录下面执行如下命令来生成证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 生成根证书
#-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 来包含证书内容:

1
$ kubectl create secret tls mongo-certs --cert=server.crt --key=server.key

然后重新更新 IngressRouteTCP 对象,增加 TLS 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 住,因为我们没有提供证书,这个时候我们可以带上证书来进行连接

1
2
3
4
5
6
7
8
9
10
$ 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:

1
2
3
4
5
6
7
8
9
10
11
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 服务:

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
28
29
30
31
32
33
34
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 协议的入口点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
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 重启即可

1
2
kubectl apply -f cm.yml
kubectl delete pods -n kube-system -l app=traefix-v2

UDP 的入口点增加成功后,接下来我们可以创建一个 IngressRouteUDP 类型的资源对象,用来代理 UDP 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 应用:

1
2
3
4
5
6
7
8
9
10
$ 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 应用进行测试:

UDP测试

我们可以看到测试成功了,证明我就用 Traefik 来代理 UDP 应用成功了。除此之外 Traefik 还有很多功能,特别是强大的中间件和自定义插件的功能(下一章讲),为我们提供了不断扩展其功能的能力,我们完成可以根据自己的需求进行二次开发。