0. 前言

基于 centos7.9docker-ce-20.10.18kubelet-1.22.3-0traefik-2.9.10

参考:https://www.cuiliangblog.cn/detail/section/29427812

1. 简介

1.1 Traefik 简介

Traefik 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。 它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, file…) 来自动化、动态的应用它的配置文件设置。

它是一个边缘路由器,它会拦截外部的请求并根据逻辑规则选择不同的操作方式,这些规则决定着这些请求到底该如何处理。Traefik 提供自动发现能力,会实时检测服务,并自动更新路由规则。

traefik-architecture

1.2 Traefik 核心组件

img

从上图可知,当请求 Traefik 时,请求首先到 entrypoints,然后分析传入的请求,查看他们是否与定义的 Routers 匹配。如果匹配,则会通过一系列 middlewares 处理,再到 traefikServices 上做流量转发,最后请求到 kubernetes的services上

这就涉及到以下几个重要的核心组件:

  • Providers 是基础组件,Traefik 的配置发现是通过它来实现的,它可以是协调器,容器引擎,云提供商或者键值存储。Traefik 通过查询 ProvidersAPI 来查询路由的相关信息,一旦检测到变化,就会动态的更新路由。
  • EntrypointsTraefik 的网络入口,它定义接收请求的接口,以及是否监听TCP或者UDP。
  • Routers 主要用于分析请求,并负责将这些请求连接到对应的服务上去,在这个过程中,Routers还可以使用Middlewares来更新请求,比如在把请求发到服务之前添加一些Headers。
  • Services 负责配置如何到达最终将处理传入请求的实际服务。
  • Middlewares 用来修改请求或者根据请求来做出一些判断(authentication, rate limiting, headers, …),中间件被附件到路由上,是一种在请求发送到你的服务之前(或者在服务的响应发送到客户端之前)调整请求的一种方法。

1.3 Traefik CRD资源

官方文档

traefik通过自定义资源实现了对traefik资源的创建和管理,支持的crd资源类型如下所示:

kind 功能
IngressRoute HTTP路由配置
Middleware HTTP中间件配置
TraefikService HTTP负载均衡/流量复制配置
IngressRouteTCP TCP路由配置
MiddlewareTCP TCP中间件配置
IngressRouteUDP UDP路由配置
TLSOptions TLS连接参数配置
TLSStores TLS存储配置
ServersTransport traefik与后端之间的传输配置

2. Traefik 部署

traefik 是支持 helm 部署的,但是查看 helm 包的 value.yaml 配置发现总共有 500 多行配置,当需要修改配置项或者对 traefik 做一下自定义配置时,并不灵活。如果只是使用 traefik 的基础功能,推荐使用 helm 部署。如果想深入研究使用 traefik 的话,推荐使用自定义方式部署。

2.1 crd rbac serviceaccount

crd

[root@k8s-node1 ~]# mkdir /opt/traefik
[root@k8s-node1 ~]# cd /opt/traefik
[root@k8s-node1 traefik]# wget https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
[root@k8s-node1 traefik]# kubectl apply -f kubernetes-crd-definition-v1.yml

rbac

[root@k8s-node1 traefik]# wget https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml
[root@k8s-node1 traefik]# kubectl apply -f kubernetes-crd-rbac.yml
clusterrole.rbac.authorization.k8s.io/traefik-ingress-controller created
clusterrolebinding.rbac.authorization.k8s.io/traefik-ingress-controller created

serviceaccount.yml

apiVersion: v1
kind: Namespace
metadata:
  name: traefik
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: traefik
  name: traefik-ingress-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: traefik

2.2 configmap

在 Traefik 中有三种方式定义静态配置:在配置文件中、在命令行参数中、通过环境变量传递,由于 Traefik 配置很多,通过 CLI 定义不是很方便,一般时候选择将其配置选项放到配置文件中,然后存入 ConfigMap,将其挂入 traefik 中。

configmap.yml 文件内容:

apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
  namespace: traefik
data:
  traefik.yaml: |-
    global:
      checkNewVersion: false    # 周期性的检查是否有新版本发布
      sendAnonymousUsage: false # 周期性的匿名发送使用统计信息
    serversTransport:
      insecureSkipVerify: true  # Traefik忽略验证代理服务的TLS证书
    api:
      insecure: true            # 允许HTTP 方式访问API
      dashboard: true           # 启用Dashboard
      debug: false              # 启用Debug调试模式
    metrics:
      prometheus:               # 配置Prometheus监控指标数据,并使用默认配置
        addRoutersLabels: true  # 添加routers metrics
        entryPoint: "metrics"   # 指定metrics监听地址
    entryPoints:
      web:
        address: ":80"          # 配置80端口,并设置入口名称为 web
        forwardedHeaders: 
          insecure: true        # 信任所有的forward headers
      websecure:
        address: ":443"         # 配置443端口,并设置入口名称为 websecure
        forwardedHeaders: 
          insecure: true
      traefik:
        address: ":9000"        # 配置9000端口为 dashboard 的端口,不设置默认值为 8080
      metrics:
        address: ":9100"        # 配置9100端口,作为metrics收集入口
      tcpep:
        address: ":9200"        # 配置9200端口,作为tcp入口
      udpep:
        address: ":9300/udp"    # 配置9300端口,作为udp入口
    providers:
      kubernetesIngress: ""     # 启用 Kubernetes Ingress 方式来配置路由规则
      kubernetesGateway: ""     # 启用 Kubernetes Gateway API
      kubernetesCRD:            # 启用Kubernetes CRD方式来配置路由规则
        ingressClass: ""        # 指定traefik的ingressClass名称
        allowCrossNamespace: true   #允许跨namespace
        allowEmptyServices: true    #允许空endpoints的service
    log:
      filePath: "/etc/traefik/logs/traefik.log" # 设置调试日志文件存储路径,如果为空则输出到控制台
      level: "DEBUG"            # 设置调试日志级别
      format: "json"          # 设置调试日志格式
    accessLog:
      filePath: "/etc/traefik/logs/access.log" # 设置访问日志文件存储路径,如果为空则输出到 stdout 和 stderr
      format: "json"          # 设置访问调试日志格式
      bufferingSize: 0          # 设置访问日志缓存行数
      fields:                   # 设置访问日志中的字段是否保留(keep保留、drop不保留)
        defaultMode: keep       # 设置默认保留访问日志字段
        names:                  # 针对访问日志特别字段特别配置保留模式
          ClientUsername: drop
          StartUTC: drop        # 禁用日志timestamp使用UTC
        headers:                # 设置Header中字段是否保留
          defaultMode: keep     # 设置默认保留Header中字段
          names:                # 针对Header中特别字段特别配置保留模式
            # User-Agent: redact# 可以针对指定agent
            Authorization: drop
            Content-Type: keep    

设置节点 label,用于控制在哪些节点部署 Traefik,这里我们使用 k8s-node1(master) 节点作为边缘节点部署

kubectl label node k8s-node1  IngressProxy=true

2.3 deployment service

使用 DeamonSet 或者 Deployment 均可,此处使用 Deployment 方式部署 Traefik,调度至含有 IngressProxy=true 的边缘节点

同时使用 podAntiAffinity 避免多个 traefik 实例运行在同一节点造成单点故障.

kubectl apply -f deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
  namespace: traefik
  labels:
    app: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      name: traefik
      labels:
        app: traefik
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - traefik
            topologyKey: "kubernetes.io/hostname"
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 5         # 等待容器优雅退出的时长
      tolerations:                             # 设置容忍所有污点,防止节点被设置污点
        - operator: "Exists"
      nodeSelector:                            # 设置node筛选器,在特定label的节点上启动
        IngressProxy: "true"                   # 调度至IngressProxy: "true"的节点
      containers:
      - name: traefik
        image: traefik:v2.9
        env:
        - name: KUBERNETES_SERVICE_HOST       # 手动指定k8s api,避免网络组件不稳定。
          value: "1.1.1.1"
        - name: KUBERNETES_SERVICE_PORT_HTTPS # API server端口
          value: "6443"
        - name: KUBERNETES_SERVICE_PORT       # API server端口
          value: "6443"
        - name: TZ                            # 指定时区
          value: "Asia/Shanghai"
        ports:
          - name: web
            containerPort: 80
          - name: websecure
            containerPort: 443
          - name: dashboard
            containerPort: 9000               # Traefik Dashboard 端口
          - name: metrics
            containerPort: 9100
          - name: tcpep
            containerPort: 9200               # tcp端口
          - name: udpep
            containerPort: 9300               # udp端口
        securityContext:                      # 只开放网络权限
          capabilities:
            drop:
              - ALL
            add:
              - NET_BIND_SERVICE
        args:
          - --configfile=/etc/traefik/config/traefik.yaml
        volumeMounts:
        - mountPath: /etc/traefik/config
          name: config
        - mountPath: /etc/traefik/logs
          name: logdir
        - mountPath: /etc/localtime
          name: timezone
          readOnly: true
        resources:
          requests:
            memory: "5Mi"
            cpu: "10m"
          limits:
            memory: "256Mi"
            cpu: "1000m"
      volumes:
        - name: config                         # traefik配置文件
          configMap:
            name: traefik-config
        - name: logdir                         # traefik日志目录
          hostPath:
            path: /var/log/traefik
            type: "DirectoryOrCreate"
        - name: timezone                       # 挂载时区文件
          hostPath:
            path: /etc/localtime
            type: File

service kubectl apply -f service.yml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: traefik
  name: traefik             # 实际提供服务的 service, 使用 NodePort 模式
  namespace: traefik
spec:
  type: NodePort
  selector:
    app: traefik
  ports:
  - name: web
    protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 80
  - name: websecure
    protocol: TCP
    port: 443
    targetPort: 443
    nodePort: 443
  - name: dashboard
    protocol: TCP
    port: 9000
    targetPort: 9000
    nodePort: 9000
  - name: tcpep
    protocol: TCP
    port: 9200
    targetPort: 9200
    nodePort: 9200
  - name: udpep
    protocol: UDP
    port: 9300
    targetPort: 9300
    nodePort: 9300
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: traefik-metrics
  name: traefik-metrics           # metrics 用于给集群内的 prometheus 提供数据
  namespace: traefik
spec:
  selector:
    app: traefik
  ports:
  - name: metrics
    protocol: TCP
    port: 9100
    targetPort: 9100

2.4 验证

[root@k8s-node1 traefik]# kubectl get pod,cm,sa,svc -n traefik |grep traefik
pod/traefik-69bd67497f-v27qp   1/1     Running   0          12m
configmap/traefik-config     1      7d5h
serviceaccount/traefik-ingress-controller   1         7d5h
service/traefik          NodePort    10.101.142.158   <none>        80:80/TCP,443:443/TCP,9000:9000/TCP,9200:9200/TCP,9300:9300/UDP   11m
service/traefik-metrics   ClusterIP   10.98.89.13      <none>        9100/TCP                                                          12m

可以直接通过 http://1.1.1.1:9000 访问到 dashboard

image-20230426155416528

2.5 其他配置

2.5.1 强制使用TLS v1.2+

如今,TLS v1.0 和 v1.1 因为存在安全问题,现在已被弃用。为了保障系统安全,所有入口路由都应该强制使用TLS v1.2 或更高版本。

参考文档:https://doc.traefik.io/traefik/user-guides/crd-acme/#force-tls-v12

[root@k8s-node1 traefik]# tee traefik-tlsoption.yml <<-'EOF'
apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
  name: default
  namespace: traefik
spec:
  minVersion: VersionTLS12
  cipherSuites:
    - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384   # TLS 1.2
    - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305    # TLS 1.2
    - TLS_AES_256_GCM_SHA384                  # TLS 1.3
    - TLS_CHACHA20_POLY1305_SHA256            # TLS 1.3
  curvePreferences:
    - CurveP521
    - CurveP384
  sniStrict: true
EOF
[root@k8s-node1 traefik]# kubectl apply -f traefik-tlsoption.yml
tlsoption.traefik.containo.us/default created

2.5.2 日志切割

官方并没有日志轮换的功能,但是 traefik 收到 USR1 信号后会重建日志文件,因此可以通过 logrotate 实现日志轮换

mkdir -p /etc/logrotate.d/traefik
tee /etc/logrotate.d/traefik/config <<-'EOF'
/var/log/traefik/*.log {
  daily
  rotate 15
  missingok
  notifempty
  compress
  dateext
  dateyesterday
  dateformat .%Y-%m-%d
  create 0644 root root
  postrotate
   docker kill --signal="USR1" $(docker ps | grep traefik |grep -v pause| awk '{print $1}')
  endscript
 }
EOF

创建定时任务

crontab -e
0 0 * * * /usr/sbin/logrotate -f /etc/logrotate.d/traefik/config >/dev/null 2>&1

2.6 多控制器

有的业务场景下可能需要在一个集群中部署多个 traefik,例如:避免单个traefik配置规则过多导致加载处理缓慢。每个namespace部署一个traefik。或者traefik生产与测试环境区分等场景,需要不同的实例控制不同的 IngressRoute 资源对象,要实现该功能有两种方法

2.6.1 annotations 注解筛选

首先在 traefik 配置文件中的 providers 下增加 Ingressclass 参数,指定具体的值

    providers:
      kubernetesCRD:            # 启用Kubernetes CRD方式来配置路由规则
        ingressClass: "traefik-v2.9" # 指定traefik的ingressClass实例名称
        allowCrossNamespace: true   #允许跨namespace
        allowEmptyServices: true    #允许空endpoints的service

接下来在 IngressRoute 资源对象中的 annotations 参数中添加 kubernetes.io/ingress.class: traefik-v2.9 即可

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashboard
  namespace: traefik
  annotations:
    kubernetes.io/ingress.class: traefik-v2.9 #  因为静态配置文件指定了ingressclass,所以这里的annotations 要指定,否则访问会404
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.test.com`)
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
          namespace: traefik

2.6.2 label 标签选择器筛选

首先在traefik配置文件中的providers下增加labelSelector参数,指定具体的标签键值。

    providers:
      kubernetesCRD:            # 启用Kubernetes CRD方式来配置路由规则
        # ingressClass: "traefik-v2.9"    # 指定traefik的ingressClass名称
        labelSelector: "app=traefik-v2.9" # 通过标签选择器指定traefik标签 
        allowCrossNamespace: true   #允许跨namespace
        allowEmptyServices: true    #允许空endpoints的service

然后在 IngressRoute 资源对象中添加labels标签选择器,选择 app: traefik-v2.9 这个标签即可

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashboard
  labels:     # 通过标签选择器,该IngressRoute资源由配置了app=traefik-v2.9的traefik处理
    app: traefik-v2.9
  # annotations:
    # kubernetes.io/ingress.class: traefik-v2.9
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.test.com`)
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
          namespace: traefik