本文为家庭宽带环境下,k3s中traefik配置Let’s Encrypt TLS证书的方法。

之前设置了 morningserver.local 的本地域名,但一些问题。

  1. DNS 设置比较麻烦
    1. mDNS不支持泛域名广播
    2. 本地 hosts 也不支持泛域名
    3. 只能通过设置路由器上的DNSmasq,达到内网泛域名解析的目的
  2. 本地域名后期无法对外服务

因此考虑直接通过一个公网域名进行服务访问。由于本地域名的特殊性,浏览器会做特殊处理,忽略“http不安全”问题。对于公网域名,通过http访问,浏览器就会报错,同时自签证书也会需要设备信任及ca存储一系列问题。最后考虑通过traefik配置Let’s Encrypt自动获取 TLS 证书。

ACME

Automatic Certificate Management Environment (ACME)是自动证书管理环境,可以通过客户端申请 / 续期证书。 免费的证书颁发机构 Let’s Encrypt申请证书都是通过ACME完成的。

ACME有好几种验证方式,在traefik中也都支持验证方式

  • HTTP-01: 通过服务器的80端口进行认证;
  • TLS-ALPN-01: 通过服务器443端口进行认证;
  • DNS-01: 通过在域名DNS中TXT记录中放置特定字符串来认证对域名的归属权;

所有家庭宽带都不开放443及80端口,因此对于家宽用户只能使用DNS-01认证。

DNS-01 认证

DNS-01 认证通过在域名DNS中TXT记录中放置特定字符串来认证对域名的归属权,因此需要DNS支持API。 traefik支持的DNS API列表可以查看 Let’s Encrypt providers。需要将对应的密钥或token写到环境变量。

打两个比方:

  • DNSPod:需要获取到DNSPod的token(非腾讯云API密钥),dnspod需使用 ID,Token才是完整版token格式
    • DNSPOD_API_KEY
  • 阿里云DNS:获取API key, 建议在阿里云上建立DNS专用帐号分权分域
    • ALICLOUD_ACCESS_KEY, ALICLOUD_SECRET_KEY, ALICLOUD_REGION_ID

traefik配置

K3S的traefik是通过chart包进行安装的,此时可以通过 HelmChartConfig 来覆盖默认配置的一些字段(参考网络 | K3s使用 HelmChartConfig 自定义打包组件)。

可以参考Traefik Let’s Encrypt Documentation了解一些概念。但实际配置HelmChartConfig需参考 traefik-helm-chart/values.yaml at v20.3.0 · traefik/traefik-helm-chart 进行配置。此时要关注到 k3s实用的 traefik chart包版本,此处使用 v20.3.0 tag。

在宿主机上创建文件 /var/lib/rancher/k3s/server/manifests/traefik-config.yaml 并写入

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    certResolvers:
      dnspod:
        # for challenge options cf. https://doc.traefik.io/traefik/https/acme/
        email: [email protected]
        dnsChallenge:
          # also add the provider's required configuration under env
          # or expand then from secrets/configmaps with envfrom
          # cf. https://doc.traefik.io/traefik/https/acme/#providers
          provider: dnspod
          # add futher options for the dns challenge as needed
          # cf. https://doc.traefik.io/traefik/https/acme/#dnschallenge
          delayBeforeCheck: 30
        # match the path to persistence
        storage: /data/acme.json
    env:
      - name: LEGO_DISABLE_CNAME_SUPPORT
        value: 'true'
      - name: DNSPOD_API_KEY
        value: tokenid,token    

此时的resolver名称叫 dnspod,后面会使用到。同时通过打开 LEGO_DISABLE_CNAME_SUPPORT参数来适配cname的域名,不打开会产生混乱。

IngressRoute配置

创建一个IngressRoute资源,此时需注意apiVersion,老版本的traefik域名为traefik.containo.us,不要使用traefik.io。这是一个指向portainer service 9000端口的IngressRoute,同时指明了使用 dnspod这个resolver资源。

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: portainer
  namespace: portainer
  spec:
  entryPoints:
    - websecure
  routes:
    # 多个域名只要在同一个namespace下都能往下加
    - kind: Rule
      match: Host(`portainer.yourdomain.com`) && PathPrefix(`/`)
      services:
        - name: portainer
          port: 9000
  tls:
    certResolver: dnspod

DNS配置

我配置了公网域名 in.yourdomain.com 解析到家庭服务器局域网地址,同时将 * CNAME到 _in.yourdomain.com _

此时通过https访问域名已经可以成功了 image.png

http跳转https

使用 traefik 中间件功能,可以做到http跳转https。这边考虑到仍然会有http的需求,就不做全局配置,仅针对各个域名进行设置。

Middleware

配置中间件。永久跳转到 websecure

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect
  namespace: default
spec:
  redirectScheme:
    permanent: true
    scheme: https

http IngressRoute

复制上面的IngressRoute, 将entryPoints改为 web,删除tls那一节,并在每一项需要的route里增加中间件

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: portainer-http
  namespace: portainer
  spec:
  entryPoints:
    - web
  routes:
    # 多个域名只要在同一个namespace下都能往下加
    - kind: Rule
      match: Host(`portainer.yourdomain.com`) && PathPrefix(`/`)
      services:
        - name: portainer
          port: 9000
      middlewares:
        - name: redirect

打开浏览器跳转成功 image.png

Debug

当然是查看 traefik pod日志啦,证书获取失败会有很详细的原因介绍,就是得多等一会儿。应该会受 delayBeforeCheck: 30 的时间影响。

另外灵活使用curl命令查看返回结果。

总结

这样内网就可以直接使用公网域名访问内部服务了。后续有对外服务的需求只要直接暴露服务,并修改DNS即可。