cert-manager 是 Kubernetes 上的证书管理工具,支持基于 ACME 协议与 Let's Encrypt 签发免费证书并为证书自动续期。
安装 cert-manager
cert-manager 将证书和证书颁发者作为资源类型添加到 Kubernetes 集群中,并简化了获取、更新和使用这些证书的过程。
它可以从各种受支持的来源颁发证书,包括 Let’s Encrypt、HashiCorp Vault 和 Venafi 以及私有 PKI。
它将确保证书有效且是最新的,并在到期前的配置时间尝试更新证书。
上图显示,cert-manager 拥有 Issuer 和 Certificate 等自定义资源,我们稍后会创建这些资源。
添加 cert-manager 仓库
helm repo add jetstack https://charts.jetstack.io
helm repo update
生成 values.yaml
helm show values jetstack/cert-manager > values.yaml
修改 values.yaml
installCRDs: true
prometheus:
enabled: false
webhook:
timeoutSeconds: 10
如果想查看生成的清单,可以使用
helm template cert-manager jetstack/cert-manager -n cert-manager -f values.yaml > cert-manager.yaml
安装 cert-manager
helm install cert-manager jetstack/cert-manager -n cert-manager --create-namespace -f values.yaml
等待
kubectl wait --for=condition=Ready pods --all -n cert-manager
# pod/cert-manager-74cb9c54dd-rs446 condition met
# pod/cert-manager-cainjector-5b99cf9569-c6bpt condition met
# pod/cert-manager-webhook-b9999597-xtfl6 condition met
创建 Issuer
Let’s Encrypt 利用 ACME (Automated Certificate Management Environment) 协议校验域名的归属,校验成功后可以自动颁发免费证书。免费证书有效期只有 90 天,需在到期前再校验一次实现续期。使用 cert-manager 可以自动续期。校验域名归属的两种方式分别是 HTTP-01 和 DNS-01,校验原理详情可参见 Let's Encrypt 的运作方式。
DNS-01 校验支持泛域名, 但是是不同 DNS 提供商的配置方式不同,DNS 提供商过多而 cert-manager 的 Issuer 不能全部支持。部分可以通过部署实现 cert-manager 的 Webhook 服务来扩展 Issuer 进行支持。例如阿里 DNS 就是通过 Webhook 的方式进行支持。
添加 DNS 记录
whoami.sundayhk.com 下面测试使用
前往个人资料 -> API 令牌, 使用编辑区域 DNS 模版, 创建一个 token。
测试令牌是否能正常工作:
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer <yout api token>" \
-H "Content-Type:application/json"
敏感文件不提交git仓库,这里将 token信息保存到 .env 文件,并将 .env 添加到 .gitignore。
# .env
# api-token=<token>
api-token=Anbv2XXQ4pOqKYHGLtx5uSnnAqzTkvCPzxogjLKP
这里使用 Kustomize 来读取.env文件的token
可以直接使用kubectl kustomize命令
编写 kustomization.yaml
# kustomization.yaml
resources:
- letsencrypt-issuer.yaml
namespace: cert-manager
secretGenerator:
- name: cloudflare-api-token-secret
envs:
- .env # token本地文件
generatorOptions:
disableNameSuffixHash: true
编写 letsencrypt-issuer.yaml
# letsencrypt-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
privateKeySecretRef:
name: letsencrypt-dns01
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
email: xxx@gmail.com # 替换成你的 cloudflare 邮箱账号
apiTokenSecretRef:
key: api-token
name: cloudflare-api-token-secret # 引用上面cloudflare Secret
所生成的 Secret 可以使用下面的命令来检查
kubectl kustomize ./
如无问题,运行如下命令,创建 issuer
kubectl apply -k ./
查看 Let't Encrypt 注册状态
kubectl describe clusterissuer letsencrypt-dns01
如下表示注册成功
root@kr-master:~# kubectl describe clusterissuer letsencrypt-dns01
...
Status:
Acme:
Last Private Key Hash: MjEcqc52vL8XCp+yKXJeqKnO6yVtJVx/S4zeU4CzYFd=
Uri: https://acme-v02.api.letsencrypt.org/acme/acct/2653488531
Conditions:
Last Transition Time: 2025-09-11T05:32:16Z
Message: The ACME account was registered with the ACME server
Observed Generation: 1
Reason: ACMEAccountRegistered
Status: True
Type: Ready
Events: <none>
创建 Certificate
编写 certificate.yaml
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-sundayhk-com
namespace: default
spec:
dnsNames:
- "*.sundayhk.com" # 替换成自己要签发证书的域名
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns01 # 引用 ClusterIssuer,名字和 letsencrypt-issuer.yaml 中保持一致
secretName: wildcard-sundayhk-com-tls # 最终签发出来的证书会保存在这个 Secret 里面
kubectl apply -f certificate.yaml
查看证书是否签发成功
root@kr-master:~/ks# kubectl get certificate -w
NAME READY SECRET AGE
wildcard-sundayhk-com False wildcard-letsencrypt-tls 13s
wildcard-sundayhk-com False wildcard-letsencrypt-tls 26s
wildcard-sundayhk-com True wildcard-letsencrypt-tls 26s
wildcard-sundayhk-com True wildcard-letsencrypt-tls 26s
注意:Let's Encrypt 一个星期内只为同一个域名颁发 5 次证书,sundayhk.com 和 whoami.sundayhk.com 被视为不同的域名。
测试
编写 whoami.yaml
# whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
labels:
app: containous
name: whoami
spec:
replicas: 2
selector:
matchLabels:
app: containous
task: whoami
template:
metadata:
labels:
app: containous
task: whoami
spec:
containers:
- name: containouswhoami
image: containous/whoami
resources:
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- name: http
port: 80
selector:
app: containous
task: whoami
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts:
- "whoami.sundayhk.com"
secretName: wildcard-sundayhk-com-tls
rules:
- host: whoami.sundayhk.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
kubectl apply -f whoami.yaml
https://cert-manager.io/docs/installation/helm/
https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/
https://www.tencentcloud.com/zh/document/product/457/38713