昨天,我們學會了 CRD 與 Operator 的原理,還用 Shell Script 寫了一個簡易 Controller。
今天我們要來認識一個 Kubernetes 生態系中非常常見的憑證管理工具 —— cert-manager。
你有沒有遇過這些痛點?
cert-manager 就是為了解決這些問題而生。
它可以透過 Kubernetes 的 Custom Resource,自動完成憑證的申請、儲存與續約,大幅降低 TLS 憑證的手動維護成本。
今天我們會先認識 cert-manager 的架構與核心概念,接著透過兩個由淺入深的實作:
SelfSigned → 內部 CA
一步一步理解憑證簽發流程,最後再看看 cert-manager 的自動續約機制,以及常見的除錯方式。
今天內容涵蓋:
以下操作皆在 master 節點執行。
還記得昨天學的 Operator Pattern 嗎?
可以先簡化理解成:
Custom Resource + Controller + 領域自動化邏輯
cert-manager 就是把這套模式應用在 TLS 憑證管理上。
它透過一組 Custom Resource 描述「我要什麼憑證、要由誰簽發」,再由 Controller 自動完成申請、簽發、儲存與續約。
主要包含:
Certificate、Issuer、ClusterIssuer、CertificateRequest 等資源
| CRD | 用途 | 範圍 |
|---|---|---|
| Issuer | 定義 Namespace 內可使用的憑證簽發者 | Namespace |
| ClusterIssuer | 定義整個 Cluster 可共用的憑證簽發者 | Cluster |
| Certificate | 描述需要的憑證,例如 DNS Name、有效期與使用哪個 Issuer | Namespace |
| CertificateRequest | 代表一次實際的憑證簽發請求 | Namespace |
| Order | ACME 流程中的憑證訂單 | Namespace |
| Challenge | ACME 流程中的網域驗證挑戰 | Namespace |
💡 連結 Day 27
cert-manager 安裝後會加入上述 CRD,讓 Kubernetes API 認得這些新的資源類型。
Controller 再持續監看並 Reconcile 這些資源,這就是前一篇學到的 Custom Resource + Controller 模式在真實工具中的應用。
了解了 cert-manager 的架構後,接下來就動手把它安裝起來吧!
還記得 Day 20–23 學過的 Helm 嗎?這裡正好派上用場。
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
💡
crds.enabled=true是做什麼的?這個設定會讓 Helm 在安裝 cert-manager 時,一併建立所需要的 CRD。
如果你希望將 CRD 與 Helm Release 分開管理,也可以先手動安裝 CRD:
kubectl apply -f \ https://github.com/cert-manager/cert-manager/releases/download/v1.17.1/cert-manager.crds.yaml接著安裝 cert-manager 時設定:
--set crds.enabled=false本篇實作固定使用
v1.17.1對應的 CRD,方便與後續操作和截圖保持一致。
💡 補充
cert-manager 後續版本的官方安裝方式可能會有所調整,例如改用 OCI Helm Chart。實際部署到正式環境時,建議再確認目前版本的官方安裝文件。
先確認 cert-manager 的 Pod 是否正常運行:
kubectl get pods -n cert-manager

正常情況下,應該會看到三個主要元件:
cert-manager:主要的 Controller,負責處理 Certificate、Issuer、CertificateRequest 等資源的 Reconciliationcert-manager-cainjector:負責將 CA Bundle 注入指定的 Kubernetes 資源cert-manager-webhook:負責 cert-manager Custom Resource 的 Admission Webhook,例如驗證與轉換接著確認 CRD 是否已經建立:
kubectl get crd | grep cert-manager
應該會看到像下面這些 CRD:

如果有啟用 ACME 相關功能,也會看到:
challenges.acme.cert-manager.io
orders.acme.cert-manager.io
💡 看到 Pod Running + CRD 存在,基本上就代表 cert-manager 已成功安裝。
如果要再進一步確認,也可以使用:
kubectl get pods -n cert-manager kubectl get crd | grep cert-manager
cert-manager 已經跑起來了。
在開始實作之前,我們先搞懂它最核心的幾個角色:
Certificate:描述「我要什麼樣的憑證」Issuer / ClusterIssuer:描述「由誰來簽發憑證」Secret:儲存最後產生的憑證與 Private Key
Certificate 會透過 issuerRef 指定要使用哪個 Issuer,cert-manager Controller 再完成後續簽發流程,並將憑證存入 secretName 指定的 Secret。
| Issuer | ClusterIssuer | |
|---|---|---|
| 範圍 | 只能被同一個 Namespace 內的資源引用 | 可以被不同 Namespace 的 Certificate 引用 |
| 適用場景 | 各 Namespace 分別管理自己的憑證簽發設定 | 全叢集共用同一套憑證簽發設定 |
| Certificate 引用方式 | issuerRef.kind: Issuer |
issuerRef.kind: ClusterIssuer |
| 類型 | 說明 | 適用場景 |
|---|---|---|
| SelfSigned | 使用憑證自己的 Private Key 進行自簽 | 測試環境、建立 Root CA 的 bootstrap |
| CA | 使用既有 CA 憑證與 Private Key 簽發 | 企業內部 PKI、內部服務憑證 |
| ACME | 使用 ACME 協議向 CA 申請憑證,例如 Let's Encrypt | 公開網站、自動化公開 TLS 憑證 |
概念講完了,來動手!我們從最簡單的 Self-Signed(自簽) 開始,跑一遍完整流程,感受 cert-manager 的運作方式。
vim selfsigned-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
kubectl apply -f selfsigned-issuer.yaml
# 確認 Issuer 狀態
kubectl get clusterissuer selfsigned-issuer

READY 欄位應該顯示 True。
vim my-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-selfsigned-cert
namespace: default
spec:
# 憑證會被存入這個 Secret
secretName: my-selfsigned-tls
# 憑證的有效期
duration: 2160h # 90 天
renewBefore: 360h # 到期前 15 天自動續約
# 主體資訊
subject:
organizations:
- my-company
# 這張憑證要保護的域名
dnsNames:
- myapp.example.com
- www.myapp.example.com
# 使用哪個 Issuer 來簽發
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
kubectl apply -f my-cert.yaml
# 查看 Certificate 狀態
kubectl get certificate my-selfsigned-cert

READY 應該很快變成 True。
# 查看 cert-manager 自動建立的 Secret
kubectl get secret my-selfsigned-tls

# 查看憑證內容
kubectl get secret my-selfsigned-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | head -20

會看到憑證的詳細資訊,例如:
如果 Certificate 有設定 dnsNames,也可以在憑證的 Subject Alternative Name 中看到對應的網域名稱。
💡 整個流程回顧
我們只需要透過
Certificate描述「我要什麼樣的憑證」,cert-manager Controller 就會依照指定的 Issuer 完成簽發流程,並將憑證與 Private Key 寫入 Secret。這正是 Operator Pattern 的實際應用:透過宣告期望狀態,讓 Controller 自動完成後續的維運工作。
cert-manager 在處理 Certificate 時,會建立對應的 CertificateRequest,代表一次實際的憑證簽發請求。
kubectl get certificaterequest

可以把它理解成:
Certificate:描述「我希望拿到什麼樣的憑證」CertificateRequest:代表其中一次具體的簽發請求這和昨天的 Controller 概念很像:高層資源描述期望狀態,Controller 再建立後續所需的資源來完成實際流程。
上一節我們成功使用 SelfSigned Issuer 簽發了憑證,但 Self-Signed 憑證有一個明顯限制:每張憑證都是自己簽署自己,彼此之間沒有共同的信任鏈。
因此,Client 或瀏覽器預設不會信任這些憑證,除非另外將它們加入信任清單。
在企業內部環境中,更常見的做法是建立一個統一的 內部 CA(Certificate Authority),再由這個 CA 為不同服務簽發憑證。
這樣只需要讓 Client 信任這個內部 CA,就可以信任由它簽發的所有服務憑證。
vim internal-ca.yaml
# 先建立 CA 的根憑證
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
namespace: cert-manager
spec:
isCA: true # 這是一張 CA 憑證!
secretName: internal-ca-key-pair
commonName: my-internal-ca
duration: 43800h # 5 年
renewBefore: 720h # 到期前 30 天續約
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
kubectl apply -f internal-ca.yaml
vim ca-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-ca-key-pair
kubectl apply -f ca-issuer.yaml
kubectl get clusterissuer internal-ca-issuer

vim app-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-tls
namespace: default
spec:
secretName: app-tls-secret
duration: 2160h
renewBefore: 360h
dnsNames:
- app.internal.example.com
issuerRef:
name: internal-ca-issuer
kind: ClusterIssuer
kubectl apply -f app-cert.yaml
# 驗證憑證鏈
kubectl get secret app-tls-secret -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | grep -A 2 'Issuer:'
你會看到這張服務憑證的 Issuer 是:

這表示它不是自己簽自己,而是由我們建立的內部 CA 簽發。
接下來其他服務也可以使用同一個 CA Issuer 來申請憑證,形成一致的內部信任體系。

💡 為什麼要分兩層?
SelfSigned Issuer在這裡只用來 bootstrap 一張 Root CA 憑證。接著再建立
CA Issuer,使用這張 CA 憑證與 Private Key 為其他服務簽發憑證。這樣多個服務就可以共用同一個 CA。只要 Client 信任這個內部 CA,就能驗證由它簽發的服務憑證。
到目前為止,我們已經會建立 Issuer 和簽發 Certificate 了。
但憑證都有有效期限,總不能每次到期都手動重新申請。這也是 cert-manager 很重要的功能之一 —— 自動續約。

spec:
duration: 2160h # 希望憑證有效 90 天
renewBefore: 360h # 到期前 15 天開始續約
duration:希望簽發的憑證有效時間renewBefore:距離憑證到期還有多久時開始續約RenewalTime
💡 如果沒有設定
renewBeforecert-manager 會自動計算續約時間,預設約在憑證生命週期經過
2/3時開始續約。例如一張有效期 90 天的憑證,預設大約會在簽發後 60 天,也就是到期前約 30 天開始續約。
# 查看所有憑證
kubectl get certificate -A
# 查看特定憑證的詳細狀態
kubectl describe certificate my-selfsigned-cert
# 查看憑證簽發相關事件
kubectl get events --field-selector reason=Issuing -A
| 問題 | 原因與解法 |
|---|---|
Certificate READY=False |
先用 kubectl describe certificate <name> 查看 Condition 與 Event,常見原因包括 Issuer 未就緒、簽發失敗或 ACME 驗證失敗 |
| HTTP-01 驗證失敗 | 確認 DNS 已指向正確入口、Port 80 可從外部存取,並檢查 Ingress / Gateway 與 Challenge 狀態 |
| Secret 沒有建立 | 檢查 Certificate、CertificateRequest 與 cert-manager Controller log |
| 憑證續約後服務仍使用舊憑證 | cert-manager 會更新 Secret,但應用或 Controller 是否自動重新載入,要看元件本身是否支援 |
| Let's Encrypt 簽發失敗或被限流 | 測試階段優先使用 Staging Endpoint,並查看目前 Let's Encrypt 官方 Rate Limits |
| 刪除 Certificate 後 Secret 還在 | Secret 的刪除行為取決於 cert-manager 設定與資源關係;不要假設一定會跟著刪除 |
# 查看 Issuer / Certificate
kubectl get issuer,clusterissuer,certificate -A
# 查看 CertificateRequest
kubectl get certificaterequest -A
# 如果使用 ACME,再查看 Order / Challenge
kubectl get orders,challenges -A
# 查看特定 Certificate 詳細狀態
kubectl describe certificate <name> -n <namespace>
# 查看 cert-manager Controller log
kubectl logs -n cert-manager deploy/cert-manager --tail=50
今天我們學會了如何使用 cert-manager,在 Kubernetes 中自動化管理 TLS 憑證。
| 學到的東西 | 一句話總結 |
|---|---|
| cert-manager | 採用 Operator Pattern,自動處理 TLS 憑證的申請、儲存與續約 |
| Issuer / ClusterIssuer | 定義「由誰來簽發憑證」,例如 SelfSigned、CA、ACME |
| Certificate | 描述「需要什麼樣的憑證」,cert-manager 會完成簽發並寫入指定的 Secret |
| SelfSigned → CA 兩層架構 | 先用 SelfSigned bootstrap Root CA,再透過 CA Issuer 簽發服務憑證 |
| 自動續約 | cert-manager 會在憑證到期前進行續約,降低憑證過期造成服務中斷的風險 |
今天我們建立了 Certificate、Issuer 與 Secret,而這些 Kubernetes API Resource 的狀態最終都需要被持久化保存。
那麼,Kubernetes 到底把這些資料存在哪裡?
答案就是 etcd —— Kubernetes Control Plane 用來保存叢集狀態的重要 Key-Value Store。
下一篇我們將深入 etcd,了解它在 Kubernetes 中扮演的角色、Raft 共識機制,以及備份與還原等重要維運操作!