Traefik之IngressRoute示例

Sunday, December 5, 2021

1. 概述

在上一篇文章 k8s搭配traefik 中,测试的两个业务 music 和 video 都是使用 kubernetes 的 ingress 创建的应用路由,traefik 本身也提供了另外一种路由 IngressRoute,这篇文章就是使用这种方式为两个业务创建路由。

开始试验前先介绍下两种路由。

2. 简介

2.1 Ingress

Ingress 公开了从集群外部到集群内服务 的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。

下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:

image-20211205112039438

可以将 Ingress 配置为服务提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及提供基于名称的虚拟主机等能力。Ingress 不会公开任意端口或协议。 将 HTTP 和 HTTPS 以外的服务公开到 Internet 时,通常使用 Service.Type=NodePortService.Type=LoadBalancer 类型的服务。

必须具有 Ingress 控制器 才能满足 Ingress 的要求。 仅创建 Ingress 资源本身没有任何效果。

Ingress 的一个简单资源示例如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

与所有其他 Kubernetes 资源一样,Ingress 需要使用 apiVersionkindmetadata 字段。 Ingress 对象的命名必须是合法的 DNS 子域名名称 。 有关使用配置文件的一般信息,请参见部署应用配置容器管理资源 。 Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress 控制器,例如 重写目标注解 。 不同的 Ingress 控制器 支持不同的注解。查看文档以供你选择 Ingress 控制器,以了解支持哪些注解。

Ingress 规约 提供了配置负载均衡器或者代理服务器所需的所有信息。 最重要的是,其中包含与所有传入请求匹配的规则列表。 Ingress 资源仅支持用于转发 HTTP 流量的规则,每个 HTTP 规则都包含以下信息:

  • 可选的 host。在此示例中,未指定 host,因此该规则适用于通过指定 IP 地址的所有入站 HTTP 通信。 如果提供了 host(例如 foo.bar.com),则 rules 适用于该 host
  • 路径列表 paths(例如,/testpath),每个路径都有一个由 serviceNameservicePort 定义的关联后端。 在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。
  • backend(后端)是 Service 文档 中所述的服务和端口名称的组合。 与规则的 hostpath 匹配的对 Ingress 的 HTTP(和 HTTPS )请求将发送到列出的 backend

通常在 Ingress 控制器中会配置 defaultBackend(默认后端),以服务于任何不符合规约中 path 的请求。

以上内容摘选自k8s官方文档 ,有兴趣的话可以详细看一下官方说明。

2.2 Ingress Route

在早期版本中,Traefik 仅通过Kubernetes Ingress provider 来支持 Kubernetes ,它是一个严格意义上的 Kubernetes Ingress 控制器。

但是,由于社区表示需要在不求助于(大量)注释的情况下从 Traefik 功能中受益,因此 Traefik 工程团队 为 IngressRoute 类型开发了自定义资源定义 (CRD),定义如下,以便提供更好的方法配置对 Kubernetes 集群的访问。

关于 IngressRoute 的详细介绍可以查看官方说明文档

3. 开始实践

上面介绍了两个概念,接下来我们就动手实际创建一下两个服务的路由。我们先列一下当前的环境清单,规划一下我们的部署方案。

节点 角色 IP 配置 Label
master master, etcd 192.168.1.100 4核4G50G usefulness=schedule
node1 worker 192.168.1.101 8核8G100G usefulness=devops
node2 worker 192.168.1.102 8核8G100G usefulness=demo
node3 worker 192.168.1.103 8核8G100G usefulness=business

如上列表所示,node1 节点我们打上了 devops 的标签,node2 我们打上了 demo 的标签,所以我们这次的实例计划如下:

  1. 安装 traefik,并将 traefik 的 pod 部署在 node1 上
  2. 自定义启动 dashboard
  3. 部署两个业务 music 和 video,并将它们的 pod 部署在 node2 上
  4. 创建两个 ingress-route,分别指向两个 service

下面是我们这次测试的三个域名,我们需要将它们解析到 node1 节点,因为我们接下来要将 traefik 的 pod 部署在 node1 上。

域名 IP 节点 Label
traefik.local.com 192.168.1.101 node1 usefulness=devops
music.local.com 192.168.1.101 node1 usefulness=devops
video.local.com 192.168.1.101 node1 usefulness=devops

使用下面脚本将域名解析写入 hosts:

cat >> /etc/hosts <<EOF
192.168.1.101 traefik.local.com
192.168.1.101 music.local.com
192.168.1.101 video.local.com
EOF

3.1 安装 traefik

本次我们采用 helm 的方式安装 traefik,并提供一份 values.yaml 修改默认的一些配置选项,如下:

ingressRoute:
  dashboard:
    enabled: false
    # Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class)
    annotations: {}
    # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels)
    labels: {}
    
logs:
  general:
    format: json
    level: ERROR
  access:
    enabled: true
    format: json
    
ports:
  traefik:
    expose: true
  web:
    port: 80
    hostPort: 80
    expose: true
    exposedPort: 80
  websecure:
    port: 443
    hostPort: 443
    expose: true
    exposedPort: 443
nodeSelector:
  usefulness: devops

这份清单我们主要自定义了三项内容:

  1. 修改了ingressRoute,关闭默认的 dashboard,我们自定义开启 dashboard
  2. 修改了 ports,让 traefik 使用节点的 80 和 443 端口来监听流量
  3. 修改了 nodeSelector,指定 traefik 的 pod 都部署在 label 为 usefulness=devops 的节点上,也就是 node1 节点

保存上述配置为 values.yaml,接下来使用如下命令安装 traefik:

# 添加仓库
helm repo add traefik https://helm.traefik.io/traefik
# 更新
helm repo update
# 使用配置文件安装
helm install traefik traefik/traefik -f values.yaml

看到如下输出说明 traefik 已经安装成功了:

NAME: traefik
LAST DEPLOYED: Sun Dec  5 11:56:38 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

我们查看下 deploy 和 pod,可以看到 pod 部署在了 node1 节点上:

root@master:~/k8s/traefik# kubectl get deploy -o wide
NAME      READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES          SELECTOR
traefik   1/1     1            1           2m17s   traefik      traefik:2.5.4   app.kubernetes.io/instance=traefik,app.kubernetes.io/name=traefik

root@master:~/k8s/traefik# kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
traefik-5b747d6f97-hv2pl   1/1     Running   0          63s   10.233.90.16   node1   <none>           <none>

3.2 安装 dashboard

我们启用 traefik 的 dashboard 的方式是创建一个 IngressRoute 资源,它的清单如下:

# dashboard.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashboard
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.local.com`) && (PathPrefix(`/`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService

以上内容保存为 dashboard.yaml,使用如下命令创建即可:

kubectl apply -f dashboard.yaml

我们查看下 该 ingressroute 的详细信息:

root@master:~/k8s/traefik# kubectl get ingressroute
NAME        AGE
dashboard   4s

root@master:~/k8s/traefik# kubectl describe ingressroute dashboard
Name:         dashboard
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  traefik.containo.us/v1alpha1
Kind:         IngressRoute
Metadata:
  Creation Timestamp:  2021-12-05T04:02:40Z
  Generation:          1
  Managed Fields:
    API Version:  traefik.containo.us/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:entryPoints:
        f:routes:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2021-12-05T04:02:40Z
  Resource Version:  292491
  UID:               20e64b25-f774-4896-9700-a3df630f9b0c
Spec:
  Entry Points:
    web
  Routes:
    Kind:   Rule
    Match:  Host(`traefik.local.com`) && (PathPrefix(`/`) || PathPrefix(`/api`))
    Services:
      Kind:  TraefikService
      Name:  api@internal
Events:      <none>

这个时候,我们访问 http://traefik.local.com 就可以正常看到 traefik 的 dashboard 内容了:

image-20211205120931409

3.3 部署业务

这里我们开始部署两个业务:music 和 video,这两个业务的默认请求分别返回如下内容

  • music:this is music server!
  • video:this is video server!

我们的计划是创建一个命名空间 demo,将该 demo 下的所有 pod 都部署在 node2 节点上,资源清单如下:

apiVersion: v1
kind: Namespace
metadata:
  name: demo
  annotations:
    scheduler.alpha.kubernetes.io/node-selector: usefulness=demo

scheduler.alpha.kubernetes.io/node-selector 代表为该命名空间下的所有 pod 都配置指定的 nodeSelector,该功能需要开启插件 PodNodeSelector,详细信息参见我的另一篇文章 K8s零散小知识

将上述内容保存为 demo.yaml,并使用如下命令创建命名空间:

kubectl apply -f demo.yaml

接下来是我们创建这两个业务,两个 deploy 的资源清单如下:

music.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: music
  namespace: demo
spec:
  replicas: 1
  minReadySeconds: 1
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: music
  template:
    metadata:
      name: music
      labels:
        app: music
    spec:
      containers:
        - name: music
          image: jormin/music:latest
          ports:
            - containerPort: 80
              protocol: TCP
          readinessProbe:
            periodSeconds: 60
            httpGet:
              path: /
              port: 80

---

apiVersion: v1
kind: Service
metadata:
  name: music
  namespace: demo
spec:
  type: NodePort
  selector:
    app: music
  # 端口
  ports:
    - port: 80
      targetPort: 80

video.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: video
  namespace: demo
spec:
  replicas: 1
  minReadySeconds: 1
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: video
  template:
    metadata:
      name: video
      labels:
        app: video
    spec:
      containers:
        - name: video
          image: jormin/video:latest
          ports:
            - containerPort: 80
              protocol: TCP
          readinessProbe:
            periodSeconds: 60
            httpGet:
              path: /
              port: 80

---

apiVersion: v1
kind: Service
metadata:
  name: video
  namespace: demo
spec:
  type: NodePort
  selector:
    app: video
  # 端口
  ports:
    - port: 80
      targetPort: 80

使用如下命令部署:

kubectl apply -f music.yaml
kubectl apply -f video.yaml

部署好后我们查看下这两个业务的资源,可以看到两个业务的 pod 都部署在了 node2 节点上。

root@master:~/k8s/traefik# kubectl get deploy -o wide -n demo
NAME    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                SELECTOR
music   1/1     1            1           88s   music        jormin/music:latest   app=music
video   1/1     1            1           82s   video        jormin/video:latest   app=video

root@master:~/k8s/traefik# kubectl get pod -o wide -n demo
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
music-655bb6dc78-mtxtj   1/1     Running   0          91s   10.233.96.8   node2   <none>           <none>
video-7948dbfbb-2swbn    1/1     Running   0          85s   10.233.96.9   node2   <none>           <none>

root@master:~/k8s/traefik# kubectl get svc -o wide -n demo
NAME    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
music   NodePort   10.233.13.52   <none>        80:21066/TCP   95s   app=music
video   NodePort   10.233.5.118   <none>        80:31045/TCP   88s   app=video

3.4 创建 IngressRoute

接下来我们创建两个 IngressRoute,分别指向 music 和 demo 的 service,资源清单如下:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: music-video
  namespace: demo
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`music.local.com`)
    kind: Rule
    services:
    - name: music
      port: 80
  - match: Host(`video.local.com`)
    kind: Rule
    services:
    - name: video
      port: 80

上述内容保存为 ingress-route.yaml,使用如下命令创建:

kubectl apply -f ingress-route.yaml

创建好后查看下创建的路由信息:

root@master:~/k8s/traefik# kubectl get ingressroute -n demo
NAME          AGE
music-video   7s

root@master:~/k8s/traefik# kubectl describe ingressroute music-video -n demo
Name:         music-video
Namespace:    demo
Labels:       <none>
Annotations:  <none>
API Version:  traefik.containo.us/v1alpha1
Kind:         IngressRoute
Metadata:
  Creation Timestamp:  2021-12-05T04:32:09Z
  Generation:          1
  Managed Fields:
    API Version:  traefik.containo.us/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:entryPoints:
        f:routes:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2021-12-05T04:32:09Z
  Resource Version:  298837
  UID:               fc2e5007-9dcc-463d-a5fe-5b4d96dd201e
Spec:
  Entry Points:
    web
  Routes:
    Kind:   Rule
    Match:  Host(`music.local.com`)
    Services:
      Name:  music
      Port:  80
    Kind:    Rule
    Match:   Host(`video.local.com`)
    Services:
      Name:  video
      Port:  80
Events:      <none>

3.5 验证

经过上述几步,我们的操作就完成了,接下来我们使用 curl 进行验证:

➜  ~ curl music.local.com && echo ''
this is music server!

➜  ~ curl video.local.com && echo ''
this is video server!

4. 总结

本篇文章是在 k8s搭配traefik 的基础上,用 IngressRoute 替代了 Ingress,实际工作中才用 Traefik 这个控制器的话,我们更倾向于使用 IngressRoute 这种类型资源。

Kubernetes Kubernetes Traefik Ingress IngressRoute

Helm安装ZooKeeper和KafkaGo与设计模式