在 K3s 中配置 Traefik HTTPS 泛域名证书

在 K3s 中配置 Traefik HTTPS 泛域名证书

Published
Published March 12, 2022
Updated
Last updated April 10, 2022
Description
Progress
Author
这篇文章将针对一个新安装的 K3s,使用 Traefik 自带的 Let’s Encrypt 为其配置泛域名证书。我所使用的 K3s 版本为 v1.22.6+k3s1 ,因此其使用的 Traefik 版本是 v2

写在开始之前

 
当我们第一次接触此类配置时候,困难不单单来源于步骤的不明晰。一般来说,会有以下几个问题一直萦绕着我们:
  • 对 Kubernetes 本身不够熟悉
  • 缺少细致的、必要的文档说明
  • 手头的文档或资料与实际环境的版本不符
  • 问题可能有多种解决方法,我们所采用的方法和教程中的方法不一致
  • 我们对自己想要达成的大目标不够细分,无法变成实际可解决的步骤
  • 缺少调试手段以至于无法定位 corner case
很遗憾,这些问题关乎着我们面对的问题的本质,也更难以从文字、视频中学习到。但是还是希望我的这篇文章能为你带来一些些启发——毕竟这几乎是此类文章存在的全部意义。

如何才能完成泛域名证书自动续签

我们可以预先将问题分解如下:
  1. 准备基本的环境(包括绑定域名)
  1. 找到一种办法能够修改 Traefik 的行为(Traefik 是通过 HelmCart 控制的,显然无法直接修改 Deployment.yaml 等文本配置
  1. 修改配置,使 Traefik 可以自动签发证书
  1. 找到一种配置方法,帮助 Traefik 发现我们的服务并使用 HTTPS

环境配置

  1. 泛域名绑定的时候,一定要让 *.example.com 使用 A/AAAA 记录绑定到 IP,而不是 CNAMEexample.com

通过 HelmChartConfig 修改 Traefik 的配置

To allow overriding values for packaged components that are deployed as HelmCharts (such as Traefik), K3s versions starting with v1.19.0+k3s1 support customizing deployments via a HelmChartConfig resources.
K3s 的官方文档在 Helm 这一章节给了我们基本的提示,我们可以通过在 traefik-config.yaml
配置 HelmChartConfig 来给原有 HelmChart 提供“Patch”的功能。
然而这还没有结束:配置的格式是什么?
答案需要去 Helm 使用的 Traefik Chart 源码中去找寻,仓库中的 value.yaml 是我们可以修改的所有配置项以及格式。
因此所有的配置可以如此映射:
# value.yaml logs: general: level: DEBUG
# /var/lib/rancher/k3s/server/manifests/traefik-config.yaml apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- logs: general: level: DEBUG

修改配置,使 Traefik 可以签发证书

⚠️
DNSPod 用户特殊注意项
DNSPod 有以下两个问题:
  • 我们从管理界面生成的 IDTOKEN 需要以 DNSPOD_API_KEY=<ID>,<TOKEN> 的格式使用。
  • 由于解析生效时间比较慢,因此建议延长 Timeout 时间。
 
我使用的是 DNSPod,因此我的配置如下:
# /var/lib/rancher/k3s/server/manifests/traefik-config.yaml apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- ports: websecure: tls: enabled: true certResolver: "le" additionalArguments: - "--entrypoints.websecure.http.tls.domains[0].main=k3s.example.com" - "--entrypoints.websecure.http.tls.domains[0].sans=*.k3s.example.com" - "[email protected]" - "--certificatesresolvers.le.acme.storage=/data/acme.json" - "--certificatesresolvers.le.acme.dnschallenge=true" - "--certificatesresolvers.le.acme.dnschallenge.provider=dnspod" env: - name: "DNSPOD_API_KEY" value: "<ID>,<TOKEN>" - name: "DNSPOD_HTTP_TIMEOUT" value: "150" - name: "DNSPOD_POLLING_INTERVAL" value: "5" - name: "DNSPOD_PROPAGATION_TIMEOUT" value: "300" # logs: # general: # level: DEBUG
 
在尝试配置的时候,我遇到过许多报错。如果你也需要调试的话,建议把日志阈值调低。
配置保存以后,K3s 将通过另一个容器 helm-install-traefik 调用 Helm 的更新命令。因此有关配置语法的正确性需要看这个 Pod 的日志;而其余的日志要去 traefik Pod 查看。
⚠️
另外需要注意的是,Let’s Encrypt 存在访问速率限制,需要注意。

帮助 Traefik 发现我们的服务并使用 HTTPS

Traefik 在官方文档中提供了多种方式;需要指出的是,kind: Ingress 是由 Kubernetes 官方维护的 Ingress 配置方式,而 kind: IngressRoute 是由 Traefik 团队维护的专门针对 Traefik 服务发现的配置(功能更专业一些)。因此,最后我们采用了后者:
apiVersion: v1 kind: Namespace metadata: name: test --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: test namespace: test spec: routes: - kind: Rule match: Host(`test.k3s.example.net`) services: - name: hello-world port: 80 # tls: # certResolver: le # domains: # - main: "k3s.bitnp.net" --- apiVersion: v1 kind: Service metadata: name: hello-world namespace: test spec: ports: - port: 80 protocol: TCP selector: app: hello-world --- apiVersion: apps/v1 kind: Deployment metadata: name: hello-world-nginx namespace: test spec: selector: matchLabels: app: hello-world replicas: 1 template: metadata: labels: app: hello-world spec: containers: - name: nginx image: nginx ports: - containerPort: 80
⚠️
后记: 由于我们部署在内网,出口流量被严重限制。我们的自动续签计划没能实现。(Traefik 代码中有递归查询域名解析提供商的 DNS 逻辑,而我们这里的环境无法访问这些 DNS)