跳转至

使用 cert-manager 为 dnspod 域名签发免费证书 ZeroSSL

线下的 ssl 证书过期了、续签失败导致内网网关挂了、查看原因发现之前的 apikey 失效了。重新安装并从 Let's Encrypt 切换成 zerossl

1. 配置并安装 cert-manager


1. 添加 cert-manager 仓库

https://cert-manager.k8s.ac.cn/docs/installation/helm/

helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update

2. 切换 zerossl

# cat values.yaml

ingressShim:
  defaultIssuerName: "zerossl-production"
  defaultIssuerKind: "ClusterIssuer"
helm upgrade  --install --namespace cert-manager  --version v1.17.2 cert-manager jetstack/cert-manager -f values.yaml

3. 安装 zerossl 的 cert-manager

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.16.1 \
  --set crds.enabled=true \
  -f values.yaml

等待安装

kubectl wait --for=condition=Ready pods --all -n cert-manager

kubectl get pods -n cert-manager | grep cert-manager-webhook-dnspod

2. 配置并安装 zerossl

官方链接:https://cert-manager.k8s.ac.cn/docs/tutorials/zerossl/zerossl/

1. 获取 zerossl 账号的密钥

https://app.zerossl.com/developer

2. 创建 zerossl 账号的secret

首先将其编码为 base64。


# cat /cert/secret/1-zero-secretkey.yaml
apiVersion: v1
kind: Secret
metadata:
  name: zero-ssl-eabsecret
  namespace: cert-manager
type: Opaque
stringData:
  secret: YOUR_ENCODED_ZEROSSL_EAB_HMAC_KEY

kubectl apply -f /cert/secret/1-zero-secretkey.yaml


kubectl get secret zero-ssl-eabsecret -n cert-manager -o jsonpath='{.data.secret}' | base64 -d

查看是否创建成功

kubectl -n cert-manager get Secret zero-ssl-eabsecret

3. 切换 zerossl

# cat zero-values.yaml

ingressShim:
  defaultIssuerName: "zerossl-production"
  defaultIssuerKind: "ClusterIssuer"
helm upgrade  --install --namespace cert-manager  --version v1.17.2 cert-manager jetstack/cert-manager -f values.yaml
helm upgrade  --install --namespace cert-manager  --version v1.16.1 cert-manager jetstack/cert-manager -f zero-values.yaml
kubectl delete certificate linuxcdn-crt -n cert-manager
kubectl delete certificaterequest linuxcdn-crt-1 -n cert-manager
kubectl delete order linuxcdn-crt-1-1879590116 -n cert-manager
kubectl delete challenge -l "cert-manager.io/certificate-name=linuxcdn-crt" -n cert-manager

3. 使用 dnspod 配置 DNS 记录

1. 安装 webhook-dnspod 插件

# 此文件包含 DNSPod Webhook 的完整部署配置(RBAC、服务、部署、证书等)

wget https://raw.githubusercontent.com/imroc/cert-manager-webhook-dnspod/master/bundle.yaml -O 3-webhook-dnspod.yaml

kubectl apply -f 3-webhook-dnspod.yaml

2. 申请腾讯云 dnspod 的 apikey

https://console.dnspod.cn/account/token/apikey

记录 secretId 和 secretKey

3. 创建包含 apikey 的 secret

使用上面的 secretId 和 secretKey 创建 secret


# cat /cert/secret/1-dnspod-secretkey.yaml
apiVersion: v1
kind: Secret
metadata:
  name: dnspod-secret
  namespace: cert-manager
type: Opaque
stringData:
  secretId: xxx
  secretKey: xxx

kubectl apply -f /cert/secret/1-dnspod-secretkey.yaml


查看是否成功

kubectl -n cert-manager get Secret dnspod-secret

4. 创建 ClusterIssuer

Issuer 定义了 cert-manager 将如何请求 TLS 证书,ClusterIssuer,它被认为是集群范围内的版本。

1. 创建 letsencrypt 和 dnspod


# cat /cert/clusterissuer/1-letsencrypt-dnspod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: dnspod
  namespace: cert-manager
spec:
  acme:
    email: huichengcheng@datarc.cn                                  # 当域名即将到期时,通知将发送到此电子邮件地址。
    privateKeySecretRef:
      name: dnspod-letsencrypt
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
      - dns01:
          webhook:
            config:
              secretIdRef:
                key: secretId           # secretId是secretKey您的腾讯云账号的SecretId和SecretKey。
                name: dnspod-secret
              secretKeyRef:
                key: secretKey
                name: dnspod-secret
              ttl: 600                                  # webhook 创建的 dns TXT 记录的可选 ttl。
              recordLine: ""                    # dnspod 的可选 recordLine 参数。
            groupName: acme.dnspod.com  # 安装中指定的 groupName,默认为 acme.dnspod.com。
            solverName: dnspod                  # dnspod

kubectl apply -f /cert/clusterissuer/1-letsencrypt-dnspod.yaml


查看是否成功

kubectl describe clusterissuer dnspod

Status:
  Acme:
    Last Private Key Hash:  dasdasdasdasda
    Last Registered Email:  dsadasda.com
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/2411639967

  Conditions:
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready

kubectl get clusterissuer dnspod

2. 创建 zero 和 dnspod


# cat /cert/clusterissuer/2-zerossl-dnspod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: zerossl-production-dnspod
  namespace: cert-manager
spec:
  acme:
    # ZeroSSL ACME server
    server: https://acme.zerossl.com/v2/DV90
    email: dummy-email@yopmail.com

    # name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: zerossl-prod

    # for each cert-manager new EAB credencials are required
    externalAccountBinding:
      keyID: YOUR_ZEROSSL_EAB_KEY_ID
      keySecretRef:
        name: zero-ssl-eabsecret
        key: secret
      keyAlgorithm: HS256

    solvers:
      - dns01:
          webhook:
            config:
              secretIdRef:
                key: secretId       # secretId是secretKey您的腾讯云账号的SecretId和SecretKey。
                name: dnspod-secret
              secretKeyRef:
                key: secretKey
                name: dnspod-secret
              ttl: 600                  # webhook 创建的 dns TXT 记录的可选 ttl。
              recordLine: ""            # dnspod 的可选 recordLine 参数。
            groupName: acme.dnspod.com  # 安装中指定的 groupName,默认为 acme.dnspod.com。
            solverName: dnspod          # dnspod

kubectl apply -f /cert/clusterissuer/2-zerossl-dnspod.yaml


查看是否成功

kubectl describe clusterissuer zerossl-production-dnspod

Status:
  Acme:
    Last Private Key Hash:  dasdasdasdasda
    Last Registered Email:  dsadasda.com
    Uri:                    https://acme.zerossl.com/v2/DV90/account/aWsL-PiH52Hgasodsadas
  Conditions:
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready

kubectl get clusterissuer zerossl-production-dnspod

5. 申请泛域名证书

1. 签发 letsencrypt 免费证书

创建 Certificate 对象来签发 letsencrypt 免费证书:

# /cert/Certificate/1-letsencrypt-linuxcdn.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-crt
  namespace: cert-manager
spec:
  secretName: example-crt-secret # 证书保存在这个 secret 中
  issuerRef:
    name: dnspod # 这里使用自动生成出来的 ClusterIssuer
    kind: ClusterIssuer
    group: cert-manager.io
  dnsNames: # 填入需要签发证书的域名列表,支持泛域名,确保域名是使用 dnspod 管理的
  - "example.com"
  - "*.example.com"

kubectl apply -f /cert/Certificate/1-letsencrypt-linuxcdn.yaml


2. 签发 zero 免费证书


创建 Certificate 对象来签发 zero 免费证书:

# /cert/Certificate/2-zerossl-linuxcdn.yaml

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: linuxcdn-crt
  namespace: cert-manager
spec:
  secretName: linuxcdn-crt-secret # 证书保存在这个 secret 中
  issuerRef:
    name: zerossl-production-dnspod # 这里使用自动生成出来的 ClusterIssuer
    kind: ClusterIssuer
    group: cert-manager.io
  dnsNames: # 填入需要签发证书的域名列表,支持泛域名,确保域名是使用 dnspod 管理的
  - "*.linuxcdn.com"

kubectl apply -f /cert/Certificate/2-zerossl-linuxcdn.yaml


等待状态变成 Ready 表示签发成功:

kubectl -n cert-manager get certificates.cert-manager.io
kubectl -n cert-manager get secret linuxcdn-crt-secret

若签发失败可 describe 一下看下原因:

#查看状态
kubectl get Certificate --all-namespaces

kubectl -n cert-manager describe Certificate  

kubectl -n cert-manager describe CertificateRequest 

kubectl -n cert-manager describe order 

kubectl -n cert-manager describe challenges 

# 使用 dig 命令查询 TXT 记录
dig +short TXT 

# 检查 DNSPod Webhook 日志
查看 Webhook Pod 的日志,确认是否成功调用 DNSPod API:
kubectl logs -n cert-manager -l app=cert-manager-webhook-dnspod

kubectl -n cert-manager describe order | grep -A 3 "Challenges:"

dns 检验
> https://dnschecker.org/

6. ingress 使用证书

证书签发成功后会保存到我们指定的 secret 中,下面给出一些使用示例。

在 ingress 中使用:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - test.example.com
      secretName: example-crt-secret # 引用证书 secret
  rules:
  - host: test.example.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx-service
            port:
              number: 80

1. 检查证书

openssl s_client -showcerts -connect test.example.com:443
# verify server certificate and its chain


issuer=C = AT, O = ZeroSSL, CN = ZeroSSL RSA Domain Secure Site CA

7. 配置证书复制到其他 namespace

https://cert-manager.k8s.ac.cn/docs/devops-tips/syncing-secrets-across-namespaces/

https://github.com/emberstack/kubernetes-reflector

1. 安装 reflector

kubectl apply -f https://github.com/emberstack/kubernetes-reflector/releases/latest/download/reflector.yaml -n kube-system

2. 修改 Certificated 文件

修改 Certificated 文件,为 certificated 对象设置 secretTemplate, 设置需要同步到哪些 namespace。

# /cert/Certificate/2-zerossl-linuxcdn.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: linuxcdn-crt
  namespace: cert-manager
spec:
  secretName: linuxcdn-crt-secret # 证书保存在这个 secret 中
  issuerRef:
    name: zerossl-production-dnspod # 这里使用自动生成出来的 ClusterIssuer
    kind: ClusterIssuer
    group: cert-manager.io
  dnsNames: # 填入需要签发证书的域名列表,支持泛域名,确保域名是使用 dnspod 管理的
  - "*.linuxcdn.com"
  secretTemplate:
    annotations:
      reflector.v1.k8s.emberstack.com/reflection-allowed: "true" # 开启 Secret 资源的跨命名空间反射(同步)功能。
      reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "default,staging,prod-[0-9]*"
      reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" 
      reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "dev,staging,prod-[0-9]*" 

3. 创建测试 namespace

上面的 secretTemplate 指定了同步namespace label 的过滤条件,我们使用这条规则测试 prod-[0-9]*

kubectl create ns prod-1

4. 查看目标 namespace 是否成功

kubectl get secret -n prod-1
image-20250522170328919