使用cert-manager为Traefik IngressRoute自动签发Let’s Encrypt证书

cert-manager是一款云原生证书管理系统,能够根据Kubernetes原生Ingress对象的注释(annotation)自动为其签发合适的证书。Traefik的一些功能难以配合原生Ingress对象,而需要使用其定制的IngressRoute才能方便地使用,但我们仍然能够利用cert-manager为其自动签发证书。

本文将在自带Traefik Ingress控制器的k3s环境中,介绍如何利用cert-manager为Traefik IngressRoute对象签发Let’s Encrypt证书。

安装cert-manager

首先假设你已经拥有了一个工作正常的k3s集群,并且有一个指向该集群的域名(这个域名可以指向其中一个节点或者指向一个外部负载均衡器)。稍后Let’s Encrypt需要在外网使用这个域名访问我们集群内Traefik的80端口。

k3s可以通过创建HelmChart资源来安装cert-manager的Helm Chart。首先创建文件cert-manager.yaml,输入以下内容:

cert-manager.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  repo: https://charts.jetstack.io
  chart: cert-manager
  targetNamespace: cert-manager
  version: v1.13.2
  valuesContent: |-
    installCRDs: true

运行以下命令,创建HelmChart资源来安装cert-manager:

sudo kubectl apply -f cert-manager.yaml

运行以下命令来查看安装进度:

sudo kubectl get jobs -n cert-manager

当正在安装时,结果应显示job.batch/helm-install-cert-manager这一作业的完成情况为“0/1”:

NAME                                  COMPLETIONS   DURATION   AGE
job.batch/helm-install-cert-manager   0/1           5s         5s

可多次运行上述命令直到安装完成。安装完成后,结果应显示上述作业完成情况为“1/1”:

NAME                                  COMPLETIONS   DURATION   AGE
job.batch/helm-install-cert-manager   1/1           29s        3m21s

创建Issuer

cert-manager的Issuer是表示证书发行者的Kubernetes资源类型。我们可以创建一个表示Let’s Encrypt测试环境的Issuer,用来测试我们的系统是否工作正常。首先,新建一个lets-encrypt-staging.yaml文件,并输入如下内容(请根据实际情况将形如{xxx}的内容更换为所需的值):

lets-encrypt-staging.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: lets-encrypt-staging
spec:
  # cert-manager通过ACME协议与Let's Encrypt交互
  acme:
    # Let's Encrypt的测试ACME API地址
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Let's Encrypt用来联系你的邮箱
    email: {你的邮箱}
    # 用于与API通信的用户密钥将会存在这个Secret当中
    privateKeySecretRef:
      name: lets-encrypt-staging
    # 使用HTTP-01方式验证域名
    solvers:
      - http01:
          ingress:
            # cert-manager会在Traefik中创建相应的服务来响应HTTP-01挑战
            ingressClassName: traefik

运行以下命令创建该Issuer:

sudo kubectl apply -f lets-encrypt-staging.yaml

运行以下命令来确认Issuer创建成功:

sudo kubectl get issuer

输出结果应类似:

NAME                   READY   AGE
lets-encrypt-staging   True    9s

第二步:创建Certificate

Certificate是cert-manager中用于定义证书的Kubernetes资源类型(参考:Certificate resource – cert-manager)。创建文件certificate-staging.yaml,输入以下内容:

certificate-staging.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: certificate-staging
spec:
  secretName: tls-staging
  duration: 2160h
  renewBefore: 720h
  dnsNames:
    # Let's Encrypt会为这个域名颁发证书
    # 请确保这个域名的80端口能够访问集群内的Traefik
    - {你的集群的域名}
  issuerRef:
    # 证书将会通过这里指定的Issuer签发。
    name: lets-encrypt-staging
    kind: Issuer

运行以下命令创建该Certificate对象:

sudo kubectl apply -f certificate-staging.yaml

运行以下命令查看刚刚创建的Certificate对象:

sudo kubectl get certificate

当证书签发完成后,输出结果应显示ready的值为True:

NAME                  READY   SECRET        AGE
certificate-staging   True    tls-staging   8s

截至目前为止,我们已经成功使用Let’s Encrypt为我们的域名签发了证书。

第三步:创建Web服务

接下来,让我们创建一个Web服务来使用刚才准备好的证书。新建文件whoami.yaml,输入以下内容:

whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  labels:
    app.kubernetes.io/name: whoami
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: whoami
  template:
    metadata:
      labels:
        app.kubernetes.io/name: whoami
    spec:
      containers:
      - name: whoami
        image: traefik/whoami:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app.kubernetes.io/name: whoami
  ports:
  - port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingress
spec:
  routes:
  - kind: Rule
    match: Host("{你的集群的域名}")
    services:
    - kind: Service
      name: whoami
      port: 80
  tls:
    # 这是上一个步骤中创建的Certificate资源的spec.secretName属性
    secretName: tls-staging
    domains:
    - main: {你的集群的域名}

运行以下命令,启动我们的Web服务:

sudo kubectl apply -f whoami.yaml

稍等片刻,运行以下命令查看Deployment启动情况:

sudo kubectl get deployment

当启动完成后,输出结果的ready一列值应为“1/1”:

NAME     READY   UP-TO-DATE   AVAILABLE   AGE
whoami   1/1     1            1           2m46s

此时,使用浏览器访问https://{你的集群的域名},浏览器会提示证书无效。这是正常现象,因为我们目前使用的是测试环境颁发的测试证书。查看证书信息可以看到,证书由“(STAGING) Let’s Encrypt”签发,说明我们的证书已经配置正确,我们稍后会切换到正式环境的证书。

第四步:切换到Let’s Encrypt正式环境

首先为Let’s Encrypt正式环境创建Issuer。新建lets-encrypt.yaml,输入以下内容:

lets-encrypt.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: lets-encrypt
spec:
  # cert-manager通过ACME协议与Let's Encrypt交互
  acme:
    # Let's Encrypt的正式ACME API地址
    server: https://acme-v02.api.letsencrypt.org/directory
    # Let's Encrypt用来联系你的邮箱
    email: {你的邮箱}
    # 用于与API通信的用户密钥将会存在这个Secret当中
    privateKeySecretRef:
      name: lets-encrypt
    # 使用HTTP-01方式验证域名
    solvers:
      - http01:
          ingress:
            # cert-manager会在Traefik中创建相应的服务来响应HTTP-01挑战
            ingressClassName: traefik

注意,这个文件中metadata.namespec.acme.serverprivateKeySecretRef的值与一开始的lets-encrypt-staging.yaml中的不同。类似地,运行以下命令来创建新的Issuer:

sudo kubectl apply -f lets-encrypt.yaml

新建文件certificate.yaml,输入以下内容:

certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: certificate
spec:
  secretName: tls
  duration: 2160h
  renewBefore: 720h
  dnsNames:
    # Let's Encrypt会为这个域名颁发证书
    # 请确保这个域名的80端口能够访问集群内的Traefik
    - {你的集群的域名}
  issuerRef:
    # 证书将会通过这里指定的Issuer签发
    name: lets-encrypt
    kind: Issuer

运行这个命令来创建一个新的的Certificate对象:

sudo kubectl apply -f certificate.yaml

whoami.yaml文件中的spec.tls.secretNametls-staging改为tls,如下所示:

whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  labels:
    app.kubernetes.io/name: whoami
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: whoami
  template:
    metadata:
      labels:
        app.kubernetes.io/name: whoami
    spec:
      containers:
      - name: whoami
        image: traefik/whoami:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app.kubernetes.io/name: whoami
  ports:
  - port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingress
spec:
  routes:
  - kind: Rule
    match: Host("{你的集群的域名}")
    services:
    - kind: Service
      name: whoami
      port: 80
  tls:
    # 这是上一个步骤中创建的Certificate资源的spec.secretName属性
    secretName: tls
    domains:
    - main: {你的集群的域名}

运行以下命令让我们的Web服务改用最新的证书:

sudo kubectl apply -f whoami.yaml

稍等片刻,使用浏览器访问https://{你的集群的域名},将会看到证书已被浏览器信任。cert-manager将会在证书到期前30天自动续签证书。

清理

要删除用于测试的Let’s Encrypt测试环境相关对象,请运行这些命令(注意,cert-manager自动生成的Secret需要手动删除):

sudo kubectl delete -f lets-encrypt-staging.yaml
sudo kubectl delete -f certificate-staging.yaml
sudo kubectl delete secret/lets-encrypt-staging
sudo kubectl delete secret/tls-staging

要删除用于测试的Web服务,请运行以下命令:

sudo kubectl delete -f whoami.yaml

要删除Issuer、Certificate以及它们创建的Secret,请运行以下命令:

sudo kubectl delete -f lets-encrypt.yaml
sudo kubectl delete -f certificate.yaml
sudo kubectl delete secret/lets-encrypt
sudo kubectl delete secret/tls

题外话

Kubernetes正在使用原生Gateway和HTTPRoute对象替代原本的Ingress对象以及各家创造的变体(包括IngressRoute)。cert-manager的自动证书签发功能已经支持了Gateway对象,但Traefik对Gateway对象的支持仍处于实验性阶段。

留言

有想法?请给我们留言!您的留言不会直接显示在网站内。