K8s 证书管理 Cert-Manager
1. 基础信息介绍
核心组件
- cert-manager (Controller):
- 职责:大脑。它负责监控你在 K8s 中创建的
Certificate、Issuer等资源,并驱动整个申请流程(创建 Order、Challenge 等)
- 职责:大脑。它负责监控你在 K8s 中创建的
- cert-manager-cainjector:
- 职责:辅助工具。它负责将证书注入到 Webhook 或 APIService 中。在申请普通业务证书时,它主要在后台保障 cert-manager 自身的通信安全。
- cert-manager-webhook:
- 职责:守门员。当你提交一个 Ingress 或 Certificate YAML 时,它会验证你的 YAML 语法是否合法。如果你填错了字段,报错通常就是它弹出来的。
然后为了申请证书,我们还需要配置另外一个重要的组件 Issuer,主要定义如何与证书颁发机构(如 Let’s Encrypt)对接(比如用 HTTP Challenge 还是 DNS Challenge,访问网址,身份信息等等)。
Issuer 分两种, Issuer vs ClusterIssuer
- Issuer:命名空间级别。只能为同一个 Namespace 下的
Certificate提供服务。 - ClusterIssuer:集群级别(推荐)。全集群通用,不管你的业务在哪个 Namespace,都可以引用它。
ClusterIssuer 样例
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@laily.net
privateKeySecretRef:
name: letsencrypt-account-key # 存储你 LE 账号私钥的 Secret 名字
solvers:
- dns01:
cloudflare:
email: your-email@laily.net
apiTokenSecretRef: # 这里是一个 secret 用来存储 dns 服务商的信息
name: cloudflare-api-token-secret
key: api-token
对应的 dns api 信息
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: "xxxx"
graph TD
subgraph "管理层 (Global)"
CI[ClusterIssuer: letsencrypt]
end
subgraph "业务层 (Namespace: seafile)"
Cert[Certificate: laily-wildcard-cert]
Sec[Secret: laily-net-tls-secret]
Ing[Ingress: seafile-ingress]
end
subgraph "执行层 (Cert-Manager Pods)"
Webhook[Webhook: 校验语法]
Controller[Controller: 流程控制]
CAJ[CA-Injector: 注入证书]
end
subgraph "外部验证 (Internet)"
LE[Let's Encrypt API]
CF[Cloudflare DNS API]
end
%% 流程连接
Cert -- 引用 --> CI
Cert -- 触发 --> Controller
Controller -- 1.创建 DNS 记录 --> CF
Controller -- 2.请求签发 --> LE
LE -- 3.校验记录 --> CF
LE -- 4.颁发证书 --> Controller
Controller -- 5.写入数据 --> Sec
Ing -- 6.使用证书 --> Sec
Ing -- 7.对外提供服务 --> User((用户浏览器))
2. 证书申请
手动申请
当你需要为多个子域名(如 files.*, git.*)提供证书时,手动申请泛域名证书比让 Ingress 自动生成更专业且更易管理。
不要依赖 Ingress 的自动注入,手动定义 Certificate 对象。
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: laily-wildcard-cert
namespace: seafile # 注意:Secret 无法跨 Namespace,建议在每个业务空间定义一个
spec:
secretName: laily-net-tls-secret # 存储证书的盒子的名字
commonName: "*.laily.net"
dnsNames:
- "laily.net"
- "*.laily.net"
issuerRef:
name: letsencrypt # 指向你的 ClusterIssuer
kind: ClusterIssuer
自动申请
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: seafile-auto-ingress
namespace: seafile
annotations:
# 核心:告诉 cert-manager 自动为此 Ingress 申请证书
kubernetes.io/tls-acme: "true"
# 指定使用哪个 Issuer(必须是集群里已有的)
cert-manager.io/cluster-issuer: "letsencrypt"
spec:
ingressClassName: traefik
rules:
- host: files.hanxiai.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: seafile-service
port:
number: 80
tls:
- hosts:
# 指定这个这个域名去哪个 secret 里获取证书
- "files.laily.net"
# cert-manager 会自动创建这个名字的 Secret 并填入证书
secretName: seafile-auto-tls-secret
通过上面的配置,Cert-Manager 就会自己去创建一个 Certificate 然后申请证书放到对应的 Secret 里。
避坑指南
- Namespace 隔离:由于 K8s 的限制,Ingress 只能引用同命名空间下的 Secret。如果多个 Namespace 需要证书,要么在每个空间建一个
Certificate,要么使用Reflector插件同步。 - Cloudflare 权限:使用 DNS-01 校验时,API Token 必须包含
Zone:Read权限。否则清理 TXT 记录时会报7003: Could not route to /zones//...错误(Zone ID 缺失)。 - 限频问题:Let’s Encrypt 对相同域名的重复签发限制为每周 5 次。只要证书入库了,
cert-manager只会在到期前 30 天续期,不会触发限频。
3. 排查方式
当你在 Certificate 提交后发现证书没 Ready,请按以下顺序由外向内排查。
第一层:Certificate(总开关)
首先看证书对象的总体状态。
kubectl get certificate -n seafile
- 指标:
READY应为True。 - 如果为 False:运行
kubectl describe certificate <name> -n seafile。 - 看什么:看底部的
Events。如果是Issuing表示正在处理;如果有报错,它会指引你去查看CertificateRequest。
第二层:CertificateRequest(签发请求)
这是 Certificate 为了获取证书而自动创建的中间对象。
kubectl get certificaterequest -n seafile
- 看什么:
kubectl describe certificaterequest <name> -n seafile。 - 重点:看
Status里的Approved是否为True。如果卡在Waiting for Order,说明问题在 ACME 订单上。
第三层:Order(订单状态)
cert-manager 会向 Let’s Encrypt 提交一个订单。
kubectl get order -n seafile
- 看什么:
kubectl describe order <name> -n seafile。 - 关键点:看
State是否为pending。它会列出为了完成这个订单,需要通过哪些Challenge(挑战)。
第四层:Challenge(核心:DNS 校验)
这是最容易报错的地方。它负责去 Cloudflare 修改 DNS 记录
kubectl get challenges -n seafile
- 排查命令:
kubectl describe challenge <name> -n seafile。 - 常见报错及含义:
7003: Could not route...:Cloudflare API 权限不足(缺 Zone:Read)。Waiting for DNS-01 challenge propagation:cert-manager自检没过,它在等 DNS 记录生效。Presented: true:表示已经在 Cloudflare 成功创建了 TXT 记录。
强制“重连”
如果你修改了配置(如改了 Cloudflare Token),但发现状态一直不更新,可以执行这套重置逻辑:
- 重启控制器(让它重新读取 Secret 里的新 Token):
kubectl rollout restart deployment cert-manager -n cert-manager - 强制重新触发申请: 删除当前的证书对象(这不会影响已有的 Secret,除非你手动删 Secret):
kubectl delete cert <name> -n seafile然后重新kubectl apply你的 YAML。 - 手动验证: 在
describe challenge提示已 Present 时,手动在本地查一下:nslookup -q=txt _acme-challenge.files.hanxiai.io如果查到了那一串随机字符,说明 API 没问题,只需静候 1-2 分钟即可。
Read other posts