Featured image of post cert-manager 1.13 自动证书管理 + Ingress TLS 自动化

cert-manager 1.13 自动证书管理 + Ingress TLS 自动化

cert-manager v1.13.3 部署、ClusterIssuer、Let's Encrypt 自动签发 + 续期、与 Ingress 集成

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 必须公网可达。改:

  1. 自建 CA(cfssl + step-ca)
  2. 或用 DNS-01(DNS TXT 记录验证,不需要 HTTP)

9.5 Pod 启动失败

1
2
kubectl logs -n cert-manager deploy/cert-manager
# 通常是 webhook 配置问题,看 Events

10. 小结

cert-manager 让 K8s 证书管理"零运维":

  1. HTTP-01 适合公网 Ingress(最简单)
  2. DNS-01 适合泛域名 + 内网
  3. 自建 CA 适合纯内网
  4. cert-manager 自动续期,永远不用手动管理证书
  5. Staging → Production 两步走避免触发速率限制

下一步:Istio 1.20 服务网格:bookinfo + Kiali/Grafana 面板

使用 Hugo 构建
主题 StackJimmy 设计