[Workshops]Cert-manager 部署与证书签发

这一篇也是基础设施的部署记录。cert-manager用于集群的 ingress https 证书的管理。

他可以自动的完成 证书的签发,然后配合 ingressroute就可以很简单的进行证书的部署。更加适用于K8s 的体系。对比于 nginxproxy manager 这种,虽然也是使用 acme 来进行 https 的证书签发,其无法与集群进行很好的结合。以及证书的管理需要存储在硬盘上,无法很好的兼容 K8 的模式。

完成部署后,可以直接在内网使用 https访问,提升整体的系统美感。后续使用 proxy 进行服务暴露时候也更加的优雅。

这篇记录实际部署以及操作流程。

Cert-manager安装

安装部份直接使用 helm 来进行配置。之前对 helm 还有些抵触,觉得降低了自己的定制性。但是逐渐发现通过 values 也提供了 很好的定制性。而且由于资源是打包的,有更好的整体性。换个方式也可以理解为 K8s 下的 apt

apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager

安装过程很简单,导入 charts,安装 CRD,然后安装整个 chart

helm repo add jetstack https://charts.jetstack.io
helm repo update
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.13.2 --values=values.yaml

这里一些参数需要自定义,values 的内容如下。因为 CRD 已经手动安装,所以fasle,这里的PodDNS 配置比较重要,因为需要让他使用 我们的 resolver 的DNS 直接进行DNS 查询,如果使用 本地网路的 LDNS 可能会出现查询缓存,甚至是 TXT 记录查询不到的问题。

installCRD: false
replicaCount: 2
extraArgs:
  - --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53,8.8.8.8:53
  - --dns01-recursive-nameservers-only 
podDnsPolicy: None
podDnsConfig:
  nameservers:
    - "1.1.1.1"
    - "9.9.9.9"
    - "8.8.8.8"

ISSUER 配置

issuer 就是我们证书的签发方。我们配置好之后就可以用它来进行配置的签发

CF-token-secret

我们通过Cloudflare 作为我们的 DNS 托管商,在签发证书的时候需要通过 acme 的TXT记录来进行 所有权限的检验。

这个token是 用来操作CF自动新增acme 的TXT 记录所使用。所以需要有DNS edit 的权限。

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  cloudflare-token: "1234"

ISSUER

Issuer 就是我们用来颁发证书的配置,这里我们创建两个,一个是用于生产环境的。 一个是 staging,用于预生产的测试。因为letsencrypt的接口会有限频。如果配置有问题的情况下,可能会导致被 429 导致长时间的等待。所以 这里就直接使用 staging 来用于验证,没问题后切换到 生产。

具体配置可以参考 API文档

Staging

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: "[email protected]"
    privateKeySecretRef:
      name: letsencrypt-production
    solvers:
      - dns01:
          cloudflare:
            email: "[email protected]"
            apiTokenSecretRef:
              name: cloudflare-token-secret
              key: cloudflare-token
        selector:
          dnsZones:
            - "r4y.site"

Production

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: "[email protected]"
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-token-secret
              key: cloudflare-token
        selector:
          dnsZones:
            - "r4y.site"

签发证书

有了前面的 issuer 配置之后,就可以创建证书资源来获取证书了。配置也很简单,指定 issuer用于 证书签发,然后配置 commonName和后面的子域名。

# 这个是 Staging 的
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: local-r4y
  namespace: default
spec:
  secretName: local-r4y
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: "*.local.r4y.site"
  dnsNames:
    - "local.r4y.site"
    - "*.local.r4y.site
---
# Production 的配置
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: local-r4y-prod
  namespace: default
spec:
  secretName: local-r4y-prod
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  commonName: "*.local.r4y.site"
  dnsNames:
    - "local.r4y.site"
    - "*.local.r4y.site"

Apply 之后可以看到创建了 Challenge 的资源,等待完成 challenge的过程。

➜  issuers git:(main) ✗ kubectl get challenges.acme.cert-manager.io -A -o wide
NAMESPACE   NAME                                     STATE   DOMAIN           REASON                           AGE
default     local-r4y-prod-1-2800136040-3102289587           local.r4y.site                                    2m37s
default     local-r4y-prod-1-2800136040-519127018    valid   local.r4y.site   Successfully authorized domain   2m37s

Challenging 的过程我们可以通过log看到,等待大概5分钟左右,完成证书签发。

kubectl logs -n cert-manager cert-manager-57c9f949b7-2vkx4
...
I1211 13:48:56.816111       1 trigger_controller.go:215] "cert-manager/certificates-trigger: Certificate must be re-issued" key="default/local-r4y" reason="DoesNotExist" message="Issuing certificate as Secret does not exist"
I1211 13:48:56.816245       1 conditions.go:203] Setting lastTransitionTime for Certificate "local-r4y" condition "Issuing" to 2023-12-11 13:48:56.816206109 +0000 UTC m=+1276688.137750553
I1211 13:48:56.832457       1 controller.go:162] "cert-manager/certificates-trigger: re-queuing item due to optimistic locking on resource" key="default/local-r4y" error="Operation cannot be fulfilled on certificates.cert-manager.io \"local-r4y\": the object has been modified; please apply your changes to the latest version and try again"
I1211 13:48:56.832536       1 trigger_controller.go:215] "cert-manager/certificates-trigger: Certificate must be re-issued" key="default/local-r4y" reason="DoesNotExist" message="Issuing certificate as Secret does not exist"
I1211 13:48:56.832555       1 conditions.go:203] Setting lastTransitionTime for Certificate "local-r4y" condition "Issuing" to 2023-12-11 13:48:56.832549773 +0000 UTC m=+1276688.154094209
...

之后查看证书资源,可以看到已经完成签发的证书。

➜  issuers git:(main) kubectl get certificateS -o wide
NAME             READY   SECRET           ISSUER                   STATUS                                          AGE
local-r4y        True    local-r4y        letsencrypt-staging      Certificate is up to date and has not expired   14h
local-r4y-prod   True    local-r4y-prod   letsencrypt-production   Certificate is up to date and has not expired   13h

配置ingress

有了证书之后,我们就可以通过 ingress 配置来引用这个证书。配置如下,主要是在 Tls 的部份添加了这个证书的引用。需要注意的是证书需要在同一个命名空间下。

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  namespace: default
  name: echo-ingress-tls
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`traefik-ui.local.r4y.site`) && PathPrefix(`/`)
      services:
        - kind: Service
          name: echo-test
          namespace: default
          port: 80
      middlewares:
        - name: basic-auth
          namespace: kube-system
        - name: echo-header
          namespace: default
  tls:
    secretName: local-r4y-prod
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  namespace: default
  name: echo-ingress
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(`traefik-ui.local.r4y.site`) && PathPrefix(`/`)
      services:
        - kind: Service
          name: echo-test
          namespace: default
          port: 80
      middlewares:
        - name: basic-auth
          namespace: kube-system
        - name: echo-header
          namespace: default

apply 之后,创建/修改 ingress 资源,这样我们就有了一个合法的 HTTPS 证书,用于我们的内网服务。

image-20231212123026287

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注