Skip to content

Traefik 高级配置1- acme与中间件功能

作者: ryan 发布于: 1970/1/1 更新于: 1970/1/1 字数: 0 字 阅读: 0 分钟

ACME

Traefik 通过扩展 CRD 的方式来扩展 Ingress 的功能,除了默认的用 Secret 的方式可以支持应用的 HTTPS 之外,还支持自动生成 HTTPS 证书。

部署 whoami 服务

比如现在我们有一个如下所示的 whoami 应用:

yaml
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: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: registry.cn-beijing.aliyuncs.com/xxk8s/whoami
          ports:
            - name: web
              containerPort: 80

应用配置

bash
root@master01:/k8s-traefik# kubectl apply -f whoim.yaml -n test-pod
service/whoami created
deployment.apps/whoami created
root@master01:/k8s-traefik#
root@master01:/k8s-traefik# kubectl get pod -n test-pod
NAME                      READY   STATUS    RESTARTS   AGE
mysql-0                   1/1     Running   0          42h
whoami-84b5557bd8-5mlph   1/1     Running   0          4s
whoami-84b5557bd8-8rsdl   1/1     Running   0          4s

定义一个 IngressRoute 对象:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-demo
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`who.xinn.com`) && PathPrefix(`/notls`)
      kind: Rule
      services:
        - name: whoami
          port: 80
bash
root@master01:/k8s-traefik# kubectl apply -f whoami-traefik.yaml -n test-pod
ingressroute.traefik.containo.us/ingressroute-demo created

通过 entryPoints 指定了我们这个应用的入口点是 web,也就是通过 32080 端口访问,然后访问的规则就是要匹配 who.xinn.com 这个域名,并且具有 /notls 的路径前缀的请求才会被 whoami 这个 Service 所匹配。

我们可以直接创建上面的几个资源对象,然后对域名做对应的解析后,就可以访问应用了:

img

在 IngressRoute 对象中我们定义了一些匹配规则,这些规则在 Traefik 中有如下定义方式:

img

使用自签名证书

如果我们需要用 HTTPS 来访问我们这个应用的话,就需要监听 websecure 这个入口点,也就是通过 443 端口来访问,同样用 HTTPS 访问应用必然就需要证书,这里我们用 openssl 来创建一个自签名的证书:

shell
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=who.xinn.com"

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

shell
# 要注意证书文件名称必须是 tls.crt 和 tls.key
root@master01:/k8s-traefik# kubectl create secret tls who-tls --cert=tls.crt --key=tls.key -n test-pod
secret/who-tls created

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

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-tls-demo
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`who.xinn.com`) && PathPrefix(`/tls`)
      kind: Rule
      services:
        - name: whoami
          port: 80
  tls:
    secretName: who-tls
bash
root@master01:/k8s-traefik# vim whoami-traefik-tls.yaml
root@master01:/k8s-traefik# kubectl apply -f whoami-traefik-tls.yaml -n test-pod
ingressroute.traefik.containo.us/ingressroute-tls-demo created

创建完成后就可以通过 HTTPS 来访问应用了,由于我们是自签名的证书,所以证书是不受信任的:

img

基于 ACME 自签发证书

除了手动提供证书的方式之外 Traefik 同样也支持使用 Let’s Encrypt 自动生成证书,要使用 Let’s Encrypt 来进行自动化 HTTPS,就需要首先开启 ACME,开启 ACME 需要通过静态配置的方式,也就是说可以通过环境变量、启动参数等方式来提供。

ACME 有多种校验方式 tlsChallenge、httpChallenge 和 dnsChallenge 三种验证方式,之前更常用的是 http 这种验证方式,关于这几种验证方式的使用可以查看文档:https://www.qikqiak.com/traefik-book/https/acme/ 了解他们之间的区别。要使用 tls 校验方式的话需要保证 Traefik 的 443 端口是可达的,dns 校验方式可以生成通配符的证书,只需要配置上 DNS 解析服务商的 API 访问密钥即可校验。我们这里用 DNS 校验的方式来为大家说明如何配置 ACME。

我们可以重新修改 Helm 安装的 values 配置文件,添加如下所示的定制参数:

yaml
# ci/deployment-prod.yaml
# 设置 Traefik 的 ACME 自动证书管理功能
additionalArguments:
  # 使用 dns 验证方式
  - --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
  # 使用阿里云 DNS 验证域名所有权
  # 先使用staging环境进行验证,验证成功后再使用移除下面一行的配置
  # - --certificatesResolvers.ali.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
  # 邮箱配置
  - --certificatesResolvers.ali.acme.email=562188771@qq.com
  # 保存 ACME 证书的位置
  - --certificatesResolvers.ali.acme.storage=/data/acme.json

envFrom:
  - secretRef:
      name: traefik-alidns-secret
      # ALICLOUD_ACCESS_KEY
      # ALICLOUD_SECRET_KEY
      # ALICLOUD_REGION_ID

#persistence:
#  enabled: true # 开启持久化
# accessMode: ReadWriteOnce
#  size: 128Mi
#  path: /data
  
persistence:
  enabled: true
  name: data
#  existingClaim: ""
  accessMode: ReadWriteOnce
  size: 128Mi
  storageClass: "rook-cephfs"
  # volumeName: ""
  path: /data
  annotations: {}



# 由于上面持久化了ACME的数据,需要重新配置下面的安全上下文
securityContext:
  readOnlyRootFilesystem: false
  runAsGroup: 0
  runAsUser: 0
  runAsNonRoot: false
bash
kubectl create secret generic traefik-alidns-secret -n kube-system \
  --from-literal=ALICLOUD_ACCESS_KEY=LTAI5t8qgj36nyfzXtLuLbh3 \
  --from-literal=ALICLOUD_SECRET_KEY=1O8bdba71RKDbMWEu4AaJE8Sb0ejPJ \
  --from-literal=ALICLOUD_REGION_ID=cn-beijing

这样我们可以通过设置 --certificatesresolvers.ali.acme.dnschallenge.provider=alidns 参数来指定指定阿里云的 DNS 校验,要使用阿里云的 DNS 校验我们还需要配置 3 个环境变量:ALICLOUD_ACCESS_KEYALICLOUD_SECRET_KEYALICLOUD_REGION_ID,分别对应我们平时开发阿里云应用的时候的密钥,可以登录阿里云后台 https://ram.console.aliyun.com/manage/ak 获取,由于这是比较私密的信息,所以我们用 Secret 对象来创建:

shell
$ kubectl create secret generic traefik-alidns-secret --from-literal=ALICLOUD_ACCESS_KEY=<aliyun ak> --from-literal=ALICLOUD_SECRET_KEY=<aliyun sk> --from-literal=ALICLOUD_REGION_ID=cn-beijing -n kube-system

创建完成后将这个 Secret 通过环境变量配置到 Traefik 的应用中,还有一个值得注意的是验证通过的证书我们这里存到 /data/acme.json 文件中,我们一定要将这个文件持久化,否则每次 Traefik 重建后就需要重新认证,而 Let’s Encrypt 本身校验次数是有限制的。

所以我们在 values 中重新开启了数据持久化,不过开启过后需要我们提供一个可用的 PV 存储,由于我们将 Traefik 固定到 master1 节点上的,所以我们可以创建一个 hostpath 类型的 PV(后面会详细讲解):

shell
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  name: traefik
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 128Mi
  hostPath:
    path: /data/k8s/traefik
EOF
bash
root@master01:/k8s-traefik# kubectl get pvc -n kube-system
NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
traefik   Bound    pvc-856bc7d6-4d51-4904-b200-57c63cc1d38e   128Mi      RWO            rook-cephfs    3m10s
bash
root@master01:/k8s-traefik/traefik# helm upgrade --install traefik ./ -f ./xin-traefik-valuse.yaml --namespace kube-system
Release "traefik" has been upgraded. Happy Helming!
NAME: traefik
LAST DEPLOYED: Wed Aug 28 16:52:29 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 4
TEST SUITE: None
NOTES:
Traefik Proxy v2.10.4 has been deployed successfully on kube-system namespace !

🚨 When enabling persistence for certificates, permissions on acme.json can be
lost when Traefik restarts. You can ensure correct permissions with an
initContainer. See https://github.com/traefik/traefik-helm-chart/issues/396 for
more info. 🚨

使用如下所示的命令更新 Traefik:

shell
$ helm upgrade --install traefik ./traefik -f ./traefik/ci/deployment-prod.yaml --namespace kube-system

更新完成后现在我们来修改上面我们的 whoami 应用:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-tls-demo
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
      kind: Rule
      services:
        - name: whoami
          port: 80
  tls:
    certResolver: ali
    domains:
      - main: '*.qikqiak.com'

在 Traefik 的配置中,使用 certResolver 指定了一个 ACME 证书解析器。这个解析器负责与 ACME 服务器(如 Let's Encrypt)进行交互,以自动获取和续订证书。

certResolver: ali指定使用名为 ali 的证书解析器。

通过指定 *.xinn.cc,获取一个通配符证书,这个证书将适用于 xinn.cc 下的所有子域名。

在 Traefik 的 ACME 配置中,指定了证书存储的路径。通常,Traefik 会将多个域名的证书自动写入到同一个 acme.json 文件中,它存储了由 ACME 证书解析器(certResolver)管理的 SSL/TLS 证书以及相关的密钥和元数据。

  • 首次获取: 当一个新的域名请求到达 Traefik,并且需要使用 TLS 加密时,Traefik 会检查 acme.json 文件中是否已经存在对应的证书。如果没有,Traefik 会使用 ACME 协议与指定的 ACME 服务器通信,自动申请一个新的证书。
  • 续订证书: Traefik 会自动检查已经存储的证书的有效期,并在证书接近过期时自动与 ACME 服务器通信来续订证书。续订后的证书也会存储在 acme.json 文件中。
bash
root@master01:/k8s-traefik# kubectl apply -f whoami-traefik-tls-ali.yaml -n test-pod
ingressroute.traefik.containo.us/ingressroute-tls-demo created

其他的都不变,只需要将 tls 部分改成我们定义的 ali 这个证书解析器,如果我们想要生成一个通配符的域名证书的话可以定义 domains 参数来指定,然后更新 IngressRoute 对象,这个时候我们再去用 HTTPS 访问我们的应用(当然需要将域名在阿里云 DNS 上做解析):

img

我们可以看到访问应用已经是受浏览器信任的证书了,查看证书我们还可以发现该证书是一个通配符的证书。

Middleware 中间件

中间件是 Traefik2.x 中一个非常有特色的功能,我们可以根据自己的各种需求去选择不同的中间件来满足服务,Traefik 官方已经内置了许多不同功能的中间件,其中一些可以修改请求,头信息,一些负责重定向,一些添加身份验证等等,而且中间件还可以通过链式组合的方式来适用各种情况。

img

跳转 https

同样比如上面我们定义的 whoami 这个应用,我们可以通过 https://who.qikqiak.com/tls 来访问到应用,但是如果我们用 http 来访问的话呢就不行了,就会 404 了,因为我们根本就没有简单 80 端口这个入口点,所以要想通过 http 来访问应用的话自然我们需要监听下 web 这个入口点:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls-http
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`who.xinn.cc`) && PathPrefix(`/tls`)
      kind: Rule
      services:
        - name: whoami
          port: 80

注意这里我们创建的 IngressRoute 的 entryPoints 是 web,然后创建这个对象,这个时候我们就可以通过 http 访问到这个应用了。

但是我们如果只希望用户通过 https 来访问应用的话呢?

按照以前的知识,我们是不是可以让 http 强制跳转到 https 服务去,对的,在 Traefik 中也是可以配置强制跳转的,只是这个功能现在是通过中间件来提供的了。

如下所示,我们使用 redirectScheme 中间件来创建提供强制跳转服务:

yaml
#whoami-middleware-https.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
spec:
  redirectScheme:
    scheme: https

然后将这个中间件附加到 http 的服务上面去,因为 https 的不需要跳转:

yaml
#whoami-middleware-redirect-https.yaml
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls-http
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`who.xinn.cc`) && PathPrefix(`/tls`)
      kind: Rule
      services:
        - name: whoami
          port: 80
      middlewares:
        - name: redirect-https

这个时候我们再去访问 http 服务可以发现就会自动跳转到 https 去了。

Basic Auth 认证

为了给 Traefik Dashboard 添加密码保护,可以使用 Traefik 的中间件(Middleware) 功能,通过 IngressRoute 结合 BasicAuth 中间件实现基本身份验证。

创建 BasicAuth Secret

首先,需要为基本身份验证创建一个包含用户名和密码的 Kubernetes Secret。可以使用 htpasswd 工具生成密码文件:

bash
# 安装 htpasswd 工具
sudo apt-get install apache2-utils

# 生成带有用户名和密码的 htpasswd 文件
root@master01:/k8s-traefik# htpasswd -nb xin "N47j#u4[£dss" > auth

这个命令会输出类似于:

bash
admin:$apr1$eI8D7f3v$u1JiMd7J4j4OUb9BrK13K1

然后将此加密后的密码保存到 Kubernetes Secret 中:

bash
root@master01:/k8s-traefik# kubectl create secret generic traefik-dashboard-auth   --from-file=users=auth   -n kube-system
secret/traefik-dashboard-auth created

这个命令会在 kube-system 命名空间中创建一个名为 traefik-dashboard-auth 的 Secret。

定义 BasicAuth 中间件

接下来,需要创建一个 Traefik 中间件来使用 BasicAuth 进行身份验证。以下是 BasicAuth 中间件的示例:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-auth
  namespace: kube-system
spec:
  basicAuth:
    secret: traefik-dashboard-auth
    realm: TraefikDashboard

这个中间件会引用之前创建的 Secret,并为 Dashboard 添加基本身份验证。

更新 IngressRoute 以使用 BasicAuth 中间件

接下来,需要在 IngressRoute 中引用这个身份验证中间件:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: kube-system
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.example.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
      middlewares:
        - name: traefik-dashboard-auth  # 添加中间件进行身份验证

在这个配置中:

  • middlewares 部分引用了我们定义的 traefik-dashboard-auth 中间件,从而在访问 Traefik Dashboard 时启用了基本身份验证。

应用这些资源

现在,可以将这些资源应用到 Kubernetes 集群中:

bash
kubectl apply -f traefik-dashboard-auth.yaml
kubectl apply -f traefik-dashboard-ingressroute.yaml

5. 访问带有密码保护的 Dashboard

之后,当访问 Traefik Dashboard 时,会提示您输入用户名和密码:

img

成功登录

img

URL Rewrite

部署Nexus 应用

接着我们再介绍如何使用 Traefik 来实现 URL Rewrite 操作,我们先部署一个 Nexus 应用,通过 IngressRoute 来暴露服务,对应的资源清单如下所示:

yaml
# nexus.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nexus
  labels:
    app: nexus
spec:
  selector:
    matchLabels:
      app: nexus
  template:
    metadata:
      labels:
        app: nexus
    spec:
      containers:
        - image: registry.cn-beijing.aliyuncs.com/xxk8s/nexus:3.20.1
          imagePullPolicy: IfNotPresent
          name: nexus
          ports:
            - containerPort: 8081
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nexus
  name: nexus
spec:
  ports:
    - name: nexusport
      port: 8081
      targetPort: 8081
  selector:
    app: nexus
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus
  namespace: kube-system # 和Service不在同一个命名空间
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(`nexus.xinn.cc`)
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081

由于我们开启了 Traefik 的跨命名空间功能(参数 --providers.kubernetescrd.allowCrossNamespace=true),所以可以引用其他命名空间中的 Service 或者中间件,直接部署上面的应用即可:

shell
$ kubectl apply -f nexus.yaml
$ kubectl get ingressroute -n kube-system
nexus               103s
traefik-dashboard   19h

$ kubectl get pods -l app=nexus
NAME                    READY   STATUS    RESTARTS   AGE
nexus-6b7649789-lnt6m   1/1     Running   0          2m3s

$ kubectl get svc -l app=nexus
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
nexus   ClusterIP   10.99.196.220   <none>        8081/TCP   2m19s
10.1.0.16 traefik.xinn.com who.xinn.com who.xinn.cc  nexus.xinn.cc

部署完成后,我们根据 IngressRoute 对象中的配置,只需要将域名 nexus.xinn.cc 解析到 Traefik 的节点即可访问:

img

到这里我们都可以很简单的来完成,同样的现在我们有一个需求是目前我们只有一个域名可以使用,但是我们有很多不同的应用需要暴露,这个时候我们就只能通过 PATH 路径来进行区分了,比如我们现在希望当我们访问 http:/nexus.xinn.com/foo 的时候就是访问的我们的 Nexus 这个应用,当路径是 /bar 开头的时候是其他应用,这种需求是很正常的,这个时候我们就需要来做 URL Rewrite 了。

StripPrefix中间件

首先我们使用 StripPrefix 这个中间件,这个中间件的功能是在转发请求之前从路径中删除前缀,在使用中间件的时候我们只需要理解中间件操作的都是我们直接的请求即可,并不是真实的应用接收到请求过后来进行修改。

现在我们添加一个如下的中间件:

yaml
#traefik-middleware-stripprefix.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: strip-foo-path
  namespace: default # 注意这里的中间件我们定义在default命名空间下面
spec:
  stripPrefix:
    prefixes:
      - /foo
bash
root@master01:/k8s-traefik# kubectl apply -f traefik-middleware-stripprefix.yaml
middleware.traefik.containo.us/strip-foo-path created

修改IngressRoute

然后现在我们就需要从 http:/nexus.xinn.cc/foo 请求中去匹配 /foo 的请求,把这个路径下面的请求应用到上面的中间件中去,因为最终我们的 Nexus 应用接收到的请求是不会带有/foo 路径的,所以我们需要在请求到达应用之前将这个前缀删除,更新 IngressRoute 对象:

yaml
#nexus-stripprefix.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus
  namespace: kube-system
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(`nexus.xinn.cc`) && PathPrefix(`/foo`) # 匹配 /foo 路径
      middlewares:
        - name: strip-foo-path
          namespace: default # 由于我们开启了traefik的跨命名空间功能,所以可以引用其他命名空间中的中间件
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081

创建中间件更新完成上面的 IngressRoute 对象后,这个时候我们前往浏览器中访问 http:/nexus.xinn.cc/foo,这个时候发现我们的页面任何样式都没有了:

img

我们通过 Chrome 浏览器的 Network 可以查看到 /foo 路径的请求是 200 状态码,但是其他的静态资源对象确全都是 404 了,这是为什么呢?我们仔细观察上面我们的 IngressRoute 资源对象,我们现在是不是只匹配了 /foo 的请求,而我们的静态资源是 /static 路径开头的,当然就匹配不到了,所以就出现了 404,所以我们只需要加上这个 /static 路径的匹配就可以了,同样更新 IngressRoute 对象:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus
  namespace: kube-system
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(`nexus.xinn.cc`) && PathPrefix(`/foo`)
      middlewares:
        - name: strip-foo-path
          namespace: default
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081
    - kind: Rule
      match: Host(`nexus.xinn.cc`) && PathPrefix(`/static`) # 匹配 /static 的请求
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081

然后更新 IngressRoute 资源对象,这个时候再次去访问应用,可以发现页面样式已经正常了,也可以正常访问应用了:

img

但进入应用后发现还是有错误提示信息,通过 Network 分析发现还有一些 /service 开头的请求是 404,当然我们再加上这个前缀的路径即可:

yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus
  namespace: kube-system
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(`nexus.xinn.cc`) && PathPrefix(`/foo`)
      middlewares:
        - name: strip-foo-path
          namespace: default
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081
    - kind: Rule
      match: Host(`nexus.xinn.cc`) && (PathPrefix(`/static`) || PathPrefix(`/service`)) # 匹配 /static 和 /service 的请求
      services:
        - kind: Service
          name: nexus
          namespace: default
          port: 8081

更新后,再次访问应用就已经完全正常了:

img

Traefik2.X 版本中的中间件功能非常强大,基本上官方提供的系列中间件可以满足我们大部分需求了,其他中间件的用法,可以参考文档:https://www.qikqiak.com/traefik-book/middlewares/overview/。