前言
在現代 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 | 底層憑證請求物件 |
| Order | ACME 訂單資源 |
| Challenge | ACME 挑戰資源 |
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 類型比較
| 特性 | Issuer | ClusterIssuer |
|---|
| 範圍 | 命名空間 | 整個叢集 |
| 適用場景 | 單一應用程式 | 多租戶環境 |
| 管理複雜度 | 較高(需在每個命名空間建立) | 較低(全域共用) |
| 安全隔離 | 較好 | 需額外考慮 |
4. Let’s Encrypt 整合
Let’s Encrypt 環境
Let’s Encrypt 提供兩個 ACME 伺服器:
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 路徑放置驗證令牌來驗證域名控制權。
優點:
缺點:
- 需要 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 記錄來驗證域名控制權。
優點:
缺點:
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 會自動監控憑證並在到期前進行續期:
- 監控:Controller 持續監控所有 Certificate 資源
- 計算:根據
renewBefore 計算續期時間點 - 觸發:在到期前自動觸發續期流程
- 更新:成功後自動更新 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 失敗
常見原因:
- Ingress Controller 未正確設定
- 防火牆阻擋 80 端口
- 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 失敗
常見原因:
- DNS API 認證失敗
- DNS 傳播延遲
- 錯誤的 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 生態系統中不可或缺的憑證管理解決方案。通過本文,我們學習了:
- Cert-Manager 的架構:了解其核心元件和資源類型
- 安裝與設定:使用 Helm 或 kubectl 進行安裝
- Issuer 與 ClusterIssuer:命名空間和叢集級別的簽發者
- Let’s Encrypt 整合:自動簽發免費的 TLS 憑證
- Challenge 類型:HTTP-01 和 DNS-01 的使用場景
- 自動續期:確保憑證永不過期
- Ingress 整合:無縫整合 Kubernetes Ingress
- 故障排除:快速定位和解決問題
透過 Cert-Manager,您可以實現完全自動化的憑證生命週期管理,大幅降低維運負擔,同時確保應用程式的安全性。
參考資源