Kubernetes Cert-Manager 自動憑證

Kubernetes Cert-Manager Automatic Certificate Management

前言

在現代 Kubernetes 環境中,TLS/SSL 憑證管理是一項關鍵但繁瑣的任務。Cert-Manager 是一個強大的 Kubernetes 原生憑證管理控制器,能夠自動化憑證的申請、簽發與續期流程。本文將深入介紹 Cert-Manager 的架構、安裝設定,以及如何與 Let’s Encrypt 整合實現自動化憑證管理。


1. Cert-Manager 概述與架構

什麼是 Cert-Manager?

Cert-Manager 是一個 Kubernetes 附加元件,用於自動管理和簽發來自各種簽發來源的 TLS 憑證。它確保憑證有效且是最新的,並在到期前嘗試續期憑證。

核心架構元件

Cert-Manager 由以下核心元件組成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
┌─────────────────────────────────────────────────────────────────┐
│                         Cert-Manager                            │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐ │
│  │  Controller │  │   Webhook   │  │       CA Injector       │ │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│                    Custom Resource Definitions                   │
│  ┌──────────┐ ┌───────────────┐ ┌─────────────┐ ┌───────────┐  │
│  │  Issuer  │ │ ClusterIssuer │ │ Certificate │ │ Challenge │  │
│  └──────────┘ └───────────────┘ └─────────────┘ └───────────┘  │
└─────────────────────────────────────────────────────────────────┘
  • Controller:核心控制器,監控 Certificate 資源並觸發簽發流程
  • Webhook:驗證和變異 webhook,確保資源配置正確
  • CA Injector:自動將 CA 憑證注入到 webhook 配置中

資源類型

資源類型說明
Issuer命名空間級別的憑證簽發者
ClusterIssuer叢集級別的憑證簽發者
Certificate憑證請求資源
CertificateRequest底層憑證請求物件
OrderACME 訂單資源
ChallengeACME 挑戰資源

2. 安裝與設定

使用 Helm 安裝

推薦使用 Helm 進行安裝,這是最常見且最靈活的方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 新增 Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io

# 更新 Helm repository
helm repo update

# 安裝 Cert-Manager(包含 CRDs)
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.13.2 \
  --set installCRDs=true

使用 kubectl 安裝

如果不使用 Helm,也可以直接使用 kubectl:

1
2
3
4
5
# 安裝 CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml

# 安裝 Cert-Manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml

驗證安裝

1
2
3
4
5
6
7
8
# 檢查 Cert-Manager pods 狀態
kubectl get pods -n cert-manager

# 預期輸出
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-5c6866597-zw7kh              1/1     Running   0          2m
cert-manager-cainjector-577f6d9fd7-tr77l  1/1     Running   0          2m
cert-manager-webhook-787858fcdb-nlzsq     1/1     Running   0          2m

測試安裝

建立測試資源來驗證 Cert-Manager 是否正常運作:

 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
# test-resources.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager-test
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: test-selfsigned
  namespace: cert-manager-test
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: selfsigned-cert
  namespace: cert-manager-test
spec:
  dnsNames:
    - example.com
  secretName: selfsigned-cert-tls
  issuerRef:
    name: test-selfsigned
1
2
3
4
5
6
7
8
# 套用測試資源
kubectl apply -f test-resources.yaml

# 檢查憑證狀態
kubectl get certificate -n cert-manager-test

# 清理測試資源
kubectl delete namespace cert-manager-test

3. Issuer 與 ClusterIssuer

Issuer(命名空間級別)

Issuer 是命名空間範圍的資源,只能為同一命名空間中的 Certificate 資源簽發憑證。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-staging
  namespace: my-namespace
spec:
  acme:
    # Let's Encrypt staging 環境
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

ClusterIssuer(叢集級別)

ClusterIssuer 是叢集範圍的資源,可以為任何命名空間中的 Certificate 資源簽發憑證。

 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
spec:
  acme:
    # Let's Encrypt 生產環境
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

Issuer 類型比較

特性IssuerClusterIssuer
範圍命名空間整個叢集
適用場景單一應用程式多租戶環境
管理複雜度較高(需在每個命名空間建立)較低(全域共用)
安全隔離較好需額外考慮

4. Let’s Encrypt 整合

Let’s Encrypt 環境

Let’s Encrypt 提供兩個 ACME 伺服器:

  • Staginghttps://acme-staging-v02.api.letsencrypt.org/directory

    • 用於測試,速率限制較寬鬆
    • 簽發的憑證不被瀏覽器信任
  • Productionhttps://acme-v02.api.letsencrypt.org/directory

    • 用於生產環境
    • 簽發的憑證被主流瀏覽器信任
    • 有較嚴格的速率限制

Staging 環境設定

建議先使用 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: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

Production 環境設定

確認 staging 環境正常運作後,切換到 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: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

5. DNS-01 與 HTTP-01 Challenge

ACME 協議使用挑戰機制來驗證您對域名的控制權。Cert-Manager 支援兩種主要的挑戰類型。

HTTP-01 Challenge

HTTP-01 挑戰通過在特定 URL 路徑放置驗證令牌來驗證域名控制權。

優點:

  • 設定簡單
  • 不需要 DNS 供應商整合

缺點:

  • 需要 Ingress 可從網際網路存取
  • 不支援萬用字元憑證
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-http01
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-http01-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
          # 可選:指定特定的 service 類型
          serviceType: ClusterIP

DNS-01 Challenge

DNS-01 挑戰通過在 DNS 中新增 TXT 記錄來驗證域名控制權。

優點:

  • 支援萬用字元憑證
  • 不需要服務暴露於網際網路

缺點:

  • 需要 DNS 供應商 API 整合
  • 設定相對複雜

AWS Route53 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns01-route53
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-dns01-account-key
    solvers:
    - dns01:
        route53:
          region: ap-northeast-1
          hostedZoneID: Z04XXXXXXXXXX  # 您的 hosted zone ID
          # 使用 IAM role for service account(推薦)
          # 或使用 accessKeyID 和 secretAccessKeySecretRef

Cloudflare 範例

 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
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-token: <your-cloudflare-api-token>
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns01-cloudflare
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-dns01-cloudflare-account-key
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token

混合使用 DNS-01 和 HTTP-01

可以根據不同域名使用不同的挑戰類型:

 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
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-mixed
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-mixed-account-key
    solvers:
    # 萬用字元憑證使用 DNS-01
    - selector:
        dnsNames:
        - "*.example.com"
      dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token
    # 其他域名使用 HTTP-01
    - selector: {}
      http01:
        ingress:
          class: nginx

6. 憑證資源與自動續期

建立 Certificate 資源

 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
39
40
41
42
43
44
45
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: default
spec:
  # 憑證儲存的 Secret 名稱
  secretName: example-com-tls-secret

  # 憑證有效期限(預設 90 天)
  duration: 2160h  # 90 天

  # 在到期前多久開始續期(預設 30 天前)
  renewBefore: 360h  # 15 天前

  # 主體資訊
  subject:
    organizations:
    - Example Inc.

  # 私鑰設定
  privateKey:
    algorithm: RSA
    encoding: PKCS1
    size: 2048

  # 用途
  usages:
  - server auth
  - client auth

  # DNS 名稱
  dnsNames:
  - example.com
  - www.example.com

  # 可選:IP 地址
  # ipAddresses:
  # - 192.168.1.1

  # 指定 Issuer
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
    group: cert-manager.io

萬用字元憑證

 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
  dnsNames:
  - "*.example.com"
  - example.com  # 通常也包含根域名
  issuerRef:
    name: letsencrypt-dns01-cloudflare
    kind: ClusterIssuer

自動續期機制

Cert-Manager 會自動監控憑證並在到期前進行續期:

  1. 監控:Controller 持續監控所有 Certificate 資源
  2. 計算:根據 renewBefore 計算續期時間點
  3. 觸發:在到期前自動觸發續期流程
  4. 更新:成功後自動更新 Secret 中的憑證
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看憑證狀態
kubectl get certificate -A

# 查看詳細資訊
kubectl describe certificate example-com-tls

# 查看憑證請求
kubectl get certificaterequest -A

# 查看挑戰狀態
kubectl get challenge -A

手動觸發續期

如需手動觸發續期,可以刪除對應的 Secret:

1
2
3
4
5
# 刪除 Secret 會觸發重新簽發
kubectl delete secret example-com-tls-secret

# 或者使用 cmctl 工具
cmctl renew example-com-tls -n default

7. Ingress 整合

自動憑證簽發(使用 Annotation)

Cert-Manager 可以自動為 Ingress 資源簽發憑證:

 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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: default
  annotations:
    # 指定使用的 ClusterIssuer
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    # 或使用命名空間級別的 Issuer
    # cert-manager.io/issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    - www.example.com
    secretName: example-com-tls  # Cert-Manager 會自動建立此 Secret
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example-service
            port:
              number: 80
  - host: www.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example-service
            port:
              number: 80

進階 Ingress 設定

 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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: advanced-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    # ACME challenge 專用路徑設定
    cert-manager.io/acme-challenge-type: "http01"
    # Nginx Ingress 特定設定
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
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: app-service
            port:
              number: 8080

使用現有 Certificate 資源

也可以先建立 Certificate,再在 Ingress 中引用:

 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
# 先建立 Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-example-com
  namespace: default
spec:
  secretName: api-example-com-tls
  dnsNames:
  - api.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
---
# 在 Ingress 中使用
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: default
  # 不需要 cert-manager annotation
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-example-com-tls  # 引用 Certificate 建立的 Secret
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 3000

8. 故障排除與最佳實務

常見問題排除

檢查 Cert-Manager 狀態

1
2
3
4
5
6
7
8
# 檢查 pods 狀態
kubectl get pods -n cert-manager

# 查看 controller 日誌
kubectl logs -n cert-manager -l app=cert-manager -f

# 查看 webhook 日誌
kubectl logs -n cert-manager -l app=webhook -f

憑證簽發失敗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 檢查 Certificate 狀態
kubectl describe certificate <certificate-name> -n <namespace>

# 檢查 CertificateRequest
kubectl get certificaterequest -n <namespace>
kubectl describe certificaterequest <request-name> -n <namespace>

# 檢查 Order
kubectl get order -n <namespace>
kubectl describe order <order-name> -n <namespace>

# 檢查 Challenge
kubectl get challenge -n <namespace>
kubectl describe challenge <challenge-name> -n <namespace>

HTTP-01 Challenge 失敗

常見原因:

  1. Ingress Controller 未正確設定
  2. 防火牆阻擋 80 端口
  3. DNS 未正確指向叢集
1
2
3
4
5
# 檢查 challenge pod
kubectl get pods -n <namespace> | grep acme

# 測試 challenge 路徑
curl http://example.com/.well-known/acme-challenge/test

DNS-01 Challenge 失敗

常見原因:

  1. DNS API 認證失敗
  2. DNS 傳播延遲
  3. 錯誤的 hosted zone 設定
1
2
3
4
5
# 檢查 DNS TXT 記錄
dig TXT _acme-challenge.example.com

# 查看 challenge 詳情
kubectl describe challenge <challenge-name> -n <namespace>

最佳實務

1. 使用 Staging 環境測試

1
2
3
4
5
6
7
8
9
# 先使用 staging 環境驗證設定
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # ...

2. 設定適當的資源限制

1
2
3
4
5
6
7
8
# 在 Helm values 中設定
resources:
  requests:
    cpu: 10m
    memory: 32Mi
  limits:
    cpu: 100m
    memory: 128Mi

3. 監控與告警

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 使用 Prometheus 監控
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cert-manager-alerts
spec:
  groups:
  - name: cert-manager
    rules:
    - alert: CertificateExpiringSoon
      expr: certmanager_certificate_expiration_timestamp_seconds - time() < 604800
      for: 1h
      labels:
        severity: warning
      annotations:
        summary: "Certificate {{ $labels.name }} expires in less than 7 days"

4. 備份 ACME 帳戶金鑰

1
2
# 備份帳戶金鑰
kubectl get secret letsencrypt-prod-account-key -n cert-manager -o yaml > acme-account-backup.yaml

5. 使用 Pod Security Standards

1
2
3
4
5
6
7
8
9
# 確保 cert-manager namespace 使用適當的安全策略
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

6. 定期檢查憑證狀態

1
2
3
# 列出所有即將到期的憑證
kubectl get certificates -A -o custom-columns=\
'NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,EXPIRY:.status.notAfter'

有用的 cmctl 命令

cmctl 是 cert-manager 的官方 CLI 工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 安裝 cmctl
curl -fsSL -o cmctl.tar.gz https://github.com/cert-manager/cert-manager/releases/latest/download/cmctl-linux-amd64.tar.gz
tar xzf cmctl.tar.gz
sudo mv cmctl /usr/local/bin

# 檢查安裝狀態
cmctl check api

# 手動觸發續期
cmctl renew <certificate-name> -n <namespace>

# 轉換資源
cmctl convert -f old-certificate.yaml

# 檢查憑證狀態
cmctl status certificate <certificate-name> -n <namespace>

總結

Cert-Manager 是 Kubernetes 生態系統中不可或缺的憑證管理解決方案。通過本文,我們學習了:

  1. Cert-Manager 的架構:了解其核心元件和資源類型
  2. 安裝與設定:使用 Helm 或 kubectl 進行安裝
  3. Issuer 與 ClusterIssuer:命名空間和叢集級別的簽發者
  4. Let’s Encrypt 整合:自動簽發免費的 TLS 憑證
  5. Challenge 類型:HTTP-01 和 DNS-01 的使用場景
  6. 自動續期:確保憑證永不過期
  7. Ingress 整合:無縫整合 Kubernetes Ingress
  8. 故障排除:快速定位和解決問題

透過 Cert-Manager,您可以實現完全自動化的憑證生命週期管理,大幅降低維運負擔,同時確保應用程式的安全性。


參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy