cert-manager 是 K8s 证书管理的"标准答案"
K8s 集群对外暴露 HTTPS 服务的痛点:
- 证书要申请、签发、安装
- 90 天过期,要定期续期
- 多域名、泛域名、通配符场景配置复杂
cert-manager 是 CNCF 沙箱项目,自动从 Let’s Encrypt、HashiCorp Vault 等 CA 申请、签发、自动续期证书,完全 K8s-native——证书本身就是 K8s 的 Secret 资源。
适用版本:cert-manager v1.13.3 / K8s 1.28.5 / quay.io 镜像
1. 架构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ┌─────────────────┐
│ Let's Encrypt │
│ ACME Server │
└────────┬────────┘
│ HTTP-01 / DNS-01
│ challenge
┌────────▼────────┐
│ cert-manager │
│ - controller │
│ - webhook │
│ - cainjector │
└────────┬────────┘
│ 创建 / 更新
┌────────▼────────┐
│ K8s Secret │
│ (tls.crt, │
│ tls.key) │
└────────┬────────┘
│ 引用
┌────────▼────────┐
│ Ingress │
│ (nginx) │
└─────────────────┘
|
三个核心组件:
- cert-manager:Controller 监听 Certificate 资源,自动签发 / 续期
- webhook:API 校验
- cainjector:把 CA 注入到 webhook / apiserver
2. 部署 cert-manager
2.1 安装
1
2
3
4
5
| mkdir -p /data/k8scnf/cert-manager && cd /data/k8scnf/cert-manager
# 官方 yaml
curl -L -o cert-manager.yaml \
https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
|
2.2 镜像
quay.io 是红帽的镜像,国内网络直连一般通,不用额外加速:
1
2
3
| image: "quay.io/jetstack/cert-manager-cainjector:v1.13.3"
image: "quay.io/jetstack/cert-manager-webhook:v1.13.3"
image: "quay.io/jetstack/cert-manager-controller:v1.13.3"
|
如果是内网部署,要先 tag 推到私有 harbor:
1
2
3
4
| docker pull quay.io/jetstack/cert-manager-controller:v1.13.3
docker tag quay.io/jetstack/cert-manager-controller:v1.13.3 <harbor>/base/jetstack/cert-manager-controller:v1.13.3
docker push <harbor>/base/jetstack/cert-manager-controller:v1.13.3
# cainjector / webhook 同样
|
2.3 应用
1
| kubectl apply -f /data/k8scnf/cert-manager/cert-manager.yaml
|
2.4 验证
1
2
3
4
5
| kubectl get pod -n cert-manager
# NAME READY STATUS RESTARTS AGE
# cert-manager-5c9d8879fd-rtlwb 1/1 Running 0 106s
# cert-manager-cainjector-6cc9b5f678-5fbqk 1/1 Running 0 106s
# cert-manager-webhook-7bb7b75848-7bqzq 1/1 Running 0 106s
|
3 个 Pod 都 Running,部署成功。
3. ClusterIssuer / Issuer
3.1 概念
- Issuer:单一 namespace 内的证书签发者
- ClusterIssuer:集群范围(cluster-wide),可被所有 namespace 引用
生产用 ClusterIssuer。
3.2 Let’s Encrypt Staging(测试用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
class: nginx
|
3.3 Let’s Encrypt Production(生产用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
|
Staging 配额宽(5 个 / 周 / 域名),适合调试;Production 有速率限制。生产前用 Staging 验证完再切 Production。
应用:
1
2
3
4
5
| kubectl apply -f cluster-issuer.yaml
# 看状态
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod
|
4. 自动签发证书
4.1 简单域名
1
2
3
4
5
6
7
8
9
10
11
12
13
| apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: default
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
|
1
2
3
4
| kubectl apply -f certificate.yaml
kubectl get certificate
# NAME READY SECRET AGE
# example-com True example-com-tls 30s
|
4.2 Ingress 自动关联
让 cert-manager 自动从 Ingress 的 spec.tls.hosts 创建证书——加 annotation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
|
cert-manager 会自动创建 Certificate 资源 + 触发 HTTP-01 challenge + 把证书写到 app-example-com-tls Secret。
5. 泛域名(DNS-01)
Let’s Encrypt 对泛域名 *.example.com 只支持 DNS-01 challenge(不是 HTTP-01)。需要 DNS 提供商 API。
5.1 阿里云 DNS 示例
1
2
3
4
5
6
| # 创建阿里云 RAM AccessKey(DNS 权限)
# AccessKey ID / Secret 保存到 Secret
kubectl create secret generic alidns-secret \
--from-literal=access-key={{ALIYUN_ACCESS_KEY}} \
--from-literal=secret-key={{ALIYUN_SECRET_KEY}} \
-n cert-manager
|
实际密钥用占位符 {{ALIYUN_ACCESS_KEY}} {{ALIYUN_SECRET_KEY}} 替代。
5.2 ClusterIssuer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
rfc2136:
nameserver: dns17.hichina.com:53
tsigAlgorithm: HMACSHA1
|
不同 DNS 提供商插件不同(cert-manager-webhook-*),需要先部署对应插件 Pod。
5.3 申请泛域名证书
1
2
3
4
5
6
7
8
9
10
11
12
13
| apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: default
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-prod-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com
|
6. 自动续期
cert-manager 在证书 剩余有效期 < 30 天 时自动续期。续期过程对用户完全透明:
1
2
3
4
5
6
7
8
| kubectl describe certificate example-com
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal OrderCreated 60s cert-manager Created Order resource "example-com-12345"
# Normal OrderComplete 45s cert-manager Order "example-com-12345" completed successfully
# Normal CertIssued 45s cert-manager Certificate issued successfully
# Normal CertRenewed 5d cert-manager Certificate scheduled for renewal in 29 days
|
7. 与自建 CA 集成(私有场景)
如果内网不连公网 Let’s Encrypt,可以自建 CA(cfssl / step-ca):
1
2
3
4
5
6
7
| apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca
spec:
ca:
secretName: internal-ca-secret
|
先创建 CA 根证书 Secret:
1
2
3
4
5
| cfssl gencert -initca ca-csr.json | cfssljson -bare ca
kubectl create secret tls internal-ca-secret \
--cert=ca.pem \
--key=ca-key.pem \
-n cert-manager
|
8. 实战:HTTPS 反代内网应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| # 1. 部署应用(假设已部署 my-app Service 80 端口)
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
---
# 2. Ingress + cert-manager 自动证书
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "2048m"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
|
1
2
3
4
5
6
| # 等 60 秒
kubectl get certificate -n default
# READY = True
curl https://app.example.com
# 浏览器看到锁标志 + 证书
|
9. 排错
9.1 证书 Pending 不签发
1
2
3
| kubectl describe certificate my-cert
kubectl describe challenge
# 常见:HTTP-01 challenge 失败,因为 80 端口未暴露
|
9.2 “rateLimited” 错误
Let’s Encrypt Production 速率限制(同一域名 50 证书 / 周)。换域名或等下周。
9.3 续期失败
1
2
| kubectl describe order
# 找原因:DNS / 网络 / CA 配置
|
9.4 内网环境无外网
Let’s Encrypt 走 HTTP-01 必须公网可达。改:
- 自建 CA(cfssl + step-ca)
- 或用 DNS-01(DNS TXT 记录验证,不需要 HTTP)
9.5 Pod 启动失败
1
2
| kubectl logs -n cert-manager deploy/cert-manager
# 通常是 webhook 配置问题,看 Events
|
10. 小结
cert-manager 让 K8s 证书管理"零运维":
- HTTP-01 适合公网 Ingress(最简单)
- DNS-01 适合泛域名 + 内网
- 自建 CA 适合纯内网
- cert-manager 自动续期,永远不用手动管理证书
- Staging → Production 两步走避免触发速率限制
下一步:Istio 1.20 服务网格:bookinfo + Kiali/Grafana 面板。