K8s 證書自動化:使用 Cert-manager + alidns-webhook 自動簽發 Let's Encrypt 公有證書


一、背景介紹:為什麼選擇 Cert-manager

1. 為什麼使用 Cert-manager

在傳統的環境中,為服務配置TLS通常意味着:手動生成CSR,提交給CA,等待審核,下載證書,並定時在90天內完成續期。這是一個高風險、重複性的工作。

Cert-manager 是 Kubernetes 上一個強大的證書管理工具,它通過 Custom Resource Definitions (CRDs) 實現了對證書的聲明式管理。它能夠監聽 Certificate 資源,並自動化地與各種證書頒發機構(如 Let's Encrypt)進行交互,確保證書在過期前自動續期並更新到對應的 Secret 中。

2. Cert-manager 在 K8s 中的實現路徑

Cert-manager 支持 HTTP-01 和 DNS-01 兩種 ACME 挑戰方式。

對於公網域名,如果 K8s 集羣沒有暴露在公網或存在複雜的網絡環境,HTTP-01 挑戰可能會遇到困難。此時,DNS-01 挑戰是最佳選擇。

結合 alidns-webhook: 為了讓 Cert-manager 能夠通過 DNS 記錄證明域名所有權,我們需要一箇中間件來適配特定的DNS服務商(阿里雲)。alidns-webhook 就是一個外部的 ACME Solver。它接收 Cert-manager 的指令,使用提供的阿里雲 API 密鑰,在目標域名的 DNS 解析記錄中添加特定的 TXT 記錄,完成所有權驗證,從而實現證書的順利簽發。

二、部署 Cert-manager 核心組件

我們使用 Helm 進行部署,確保組件的版本穩定性和安裝的便捷性。

首先安裝 Cert-manager 所需的 CRDs:

# 1. 安裝 CRDs (必須在 Helm Chart 部署前完成)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml

接着通過 Helm 部署 Cert-manager Controller:

# 2. 添加 Jetstack Helm 倉庫
helm repo add jetstack https://charts.jetstack.io
helm repo update

# 3. 拉取 Chart 到本地(可選,方便查看配置,此處直接使用本地部署)
mkdir cert-manager && cd cert-manager
helm pull --version v1.13.1 jetstack/cert-manager --untar
cd cert-manager/

# 4. 部署 Cert-manager
helm install cert-manager --namespace cert-manager  --create-namespace .

# 驗證部署狀態
kubectl -n cert-manager get pod

三、部署 alidns-webhook 外部 Solver

alidns-webhook 負責處理阿里雲 DNS API 交互邏輯。注意,這裏的 --set groupName 是連接 ClusterIssuer 配置的關鍵橋樑。

helm upgrade --install alidns-webhook alidns-webhook \
    --repo https://wjiec.github.io/alidns-webhook \
    --namespace cert-manager --create-namespace \
    --set groupName=ubusaito.top  # **注意:此處的 groupName 必須與 ClusterIssuer 配置中的 groupName 嚴格一致**

groupName 確保了 Cert-manager 知道當它遇到一個需要 alidns 驗證的訂單時,應該把挑戰任務轉發給哪個 Webhook Deployment 來處理。

四、配置阿里雲 DNS 權限與 ClusterIssuer

1. 獲取阿里雲 DNS Access Key

為了讓 alidns-webhook 有權限操作我們的 DNS 記錄,我們需要創建具有 AliyunDNSFullAccess 權限的 RAM 用户 AccessKey ID 和 AccessKey Secret。

⚠️ 安全警告: 這是一個高權限密鑰。請務必遵循最小權限原則,並在 K8s Secret 中妥善保管,僅授權給操作 DNS 解析的權限。

假設我們已經將獲取到的密鑰設置到環境變量:export id="<Your_ID>", export secret="<Your_Secret>"

2. 創建 Secret 存儲憑證

我們將憑證存儲在一個名為 alidns-secret 的 Secret 中。

# 請將 $id 和 $secret 替換為實際值
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: alidns-secret
  namespace: cert-manager
stringData:
  access-key-id: "${id}"
  access-key-secret: "${secret}"
EOF

# 清理環境變量,防止泄露
unset id && unset secret

3. 創建 ClusterIssuer (集羣證書頒發者)

ClusterIssuer 是 Cert-manager 中定義證書籤發規則的核心資源。我們配置它使用 Let's Encrypt 的生產環境 ACME 服務,並通過 dns01 塊引用我們部署的 alidns-webhook

kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: aliyun-acme # ClusterIssuer 的名稱
spec:
  acme:
    # 接收證書到期通知的郵箱
    email: example@qq.com 
    # 存儲 ACME 賬户私鑰的 Secret 名稱
    privateKeySecretRef:
      name: aliyun-acme
    # Let's Encrypt 生產環境 ACME 服務地址
    server: https://acme-v02.api.letsencrypt.org/directory 
    solvers:
      - dns01:
          webhook:
            # Webhook 的配置細節
            config:
              # 引用 Access Key ID
              accessKeyIdRef:
                key: access-key-id
                name: alidns-secret
              # 引用 Access Key Secret
              accessKeySecretRef:
                key: access-key-secret
                name: alidns-secret
              # 阿里雲的地域 (API 調用需要)
              region: cn-hangzhou
            # 必須與 alidns-webhook 部署時的 groupName 匹配
            groupName: ubusaito.top
            # 必須與 alidns-webhook 內部定義的 solverName 匹配
            solverName: alidns
EOF

五、使用 Cert-manager 簽發公有證書

現在,我們創建一個 Certificate 資源,請求 Cert-manager 使用剛才定義的 aliyun-acme ClusterIssuer 來簽發證書。

kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: es.ubusaito.top-cert-public 
  namespace: default # 證書將生成在這個命名空間
spec:
  secretName: es.ubusaito.top-tls  # 證書籤發成功後,將生成的 Secret 名稱
  dnsNames:
  - "es.ubusaito.top" # 目標域名
  issuerRef:
    name: aliyun-acme    # 引用我們創建的 ClusterIssuer
    kind: ClusterIssuer
EOF

六、簽發狀態追蹤與故障排查

Cert-manager 的簽發流程是多步的,涉及 Certificate -> CertificateRequest -> Order -> Challenge。需要追蹤這些中間狀態進行排查。

1. 追蹤證書籤發流程

使用 kubectl describe 是最強大的追蹤工具。

步驟 1: 檢查 Certificate 狀態

檢查是否成功引用了 Issuer,以及是否創建了請求。

kubectl describe certificate es.ubusaito.top-cert-public -n default

關注 EventsStatus.Conditions。如果一切正常,它會顯示 Ready: False,然後是創建 CertificateRequest 的事件。

步驟 2: 檢查 CertificateRequest (CR)

CR是Cert-manager發起的實際請求。

kubectl describe certificaterequest -n default

CR會引用一個 Order

步驟 3: 檢查 Order (訂單)

Order 資源代表與 ACME 服務器(Let's Encrypt)協商簽發證書的訂單。

kubectl describe order -n default

關注 Status.State。訂單狀態會從 pending 變為 readyvalid。如果卡在 pending,通常是因為挑戰尚未完成。

步驟 4: 檢查 Challenge (挑戰)

Challenge 資源描述了具體的驗證方法(此處為 DNS-01)。這是 Cert-manager 與 Webhook 交互的關鍵點。

kubectl describe challenge  -n default

如果挑戰狀態顯示 Presenting,則説明 Cert-manager 已經將請求轉發給 alidns-webhook,要求它去創建 TXT 記錄。

2. 查看日誌進行故障排查

如果挑戰卡住或失敗,我們需要檢查兩個關鍵 Pod 的日誌:

A. Cert-manager Controller 日誌: 確認 Cert-manager 是否正確處理了資源對象,以及與 ACME 服務器的通信是否正常。

kubectl logs -f -n cert-manager deploy/cert-manager --tail 100

B. alidns-webhook 日誌: 檢查 Webhook 是否接收到了挑戰請求,以及它調用阿里雲 DNS API 時是否遇到了鑑權或網絡錯誤。

kubectl logs -f -n cert-manager deploy/alidns-webhook --tail 100

例如,如果 alidns-webhook 日誌中出現類似 "Forbidden" 或 "InvalidAccessKeyId",則説明 alidns-secret 中的憑證是錯誤的或權限不足。如果看到成功添加 TXT 記錄的日誌,則挑戰很快會完成。

3. 驗證最終 Secret

Certificate 資源的 Ready 狀態變為 True 時,證書籤發成功。

kubectl get secret es.ubusaito.top-tls -n default
# 驗證 Secret 中包含 tls.crt, tls.key, ca.crt 字段

通過這一套流程,我們成功地將證書管理從人工干預中解耦出來,實現了 K8s 證書的自動化生命週期管理。