Kubernetes (K8S)中Traefik路由(ingressRoute)

2023-10-17 15:23:17 浏览数 (1)

Kubernetes (K8S)中Traefik路由(ingressRoute)

王先森2023-08-172023-08-17

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

代码语言:javascript复制
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创建一个资源对象:

代码语言:javascript复制
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 资源配置清单

代码语言:javascript复制
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 来创建一个自签名的证书

代码语言:javascript复制
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 资源)

代码语言:javascript复制
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 了解更多信息。

代码语言:javascript复制
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 的目录,在该目录下面执行如下命令来生成证书:

代码语言:javascript复制
# 生成根证书
#-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 来包含证书内容:

代码语言:javascript复制
$ kubectl create secret tls mongo-certs --cert=server.crt --key=server.key

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

代码语言:javascript复制
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 协议的入口点:

代码语言:javascript复制
...
    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 请求:

代码语言:javascript复制
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 还有很多功能,特别是强大的中间件和自定义插件的功能(下一章讲),为我们提供了不断扩展其功能的能力,我们完成可以根据自己的需求进行二次开发。

0 人点赞