K8s 下的 TLS 證書管理 — 瞭解 Cert-Manager

Wi1dcard發表於2020-04-28

在 Kubernetes 中實現 TLS termination 非常容易。Ingress 資源包含一 secretName 屬性,用於指定 Secret 資源名稱。在取得證照後,通過 kubectl create secret tls tls-secret --key tls.key --cert tls.crt 建立 Secret 儲存證照,便可以被 Ingress 使用了。

唯獨有些不方便的是,證照的申請以及建立 Secret 的過程需要手動執行。在證照即將過期前,還需要人工續期。在傳統 VM 部署的場景下,可以使用例如 certbotacme.sh 等專案,配合 Let’s Encrypt 自動申請並定期續簽證照。而在 K8s 叢集中如何降低證照維護成本?來看看我們是怎麼做的。

認識 Cert-Manager

Cert-manager 顧名思義,是一款管理證照的工具。根據官網的描述:

cert-manager builds on top of Kubernetes, introducing certificate authorities and certificates as first-class resource types in the Kubernetes API. This makes it possible to provide ‘certificates as a service’ to developers working within your Kubernetes cluster.

簡單來說,cert-manager 利用 Kubernetes 的 CRD 特性提供了名為 Certificate 等資源,實現「證照即服務」。請看接下來的例子。

安裝 Cert-Manager

Cert-manager 的安裝方法有多種,可通過 kubectl 直接 apply 所有 manifest;也可以先安裝 CRD,再通過 Helm 安裝其它資源。

我個人傾向於後者。一鍵 apply 固然簡單,但沒有機會調整任何配置。使用 Helm 安裝 cert-manager 的 Chart 則可以根據需要調整部分選項。具體過程限於篇幅不再詳述,請查閱官方文件,僅展示我們使用的 helmfile.yaml 以供參考:

repositories:
  - name: jetstack
    url: https://charts.jetstack.io

releases:
  - name: cert-manager
    namespace: cert-manager
    chart: jetstack/cert-manager
    version: ^0.14.0
    values:
      - values/cert-manager/values.yaml

配置 Cert-Manager

建立 Issuer

Issuer 的作用主要是指定證照以何種方式簽發。目前 cert-manager 支援的 Issuer 型別有 ACMESelfSigned 等。其中,ACME 支援的驗證型別包括 HTTP01 和 DNS01。

使用 HTTP01 驗證 cert-manager 將會修改或建立新的 Ingress 資源用來處理 ACME 服務的驗證請求。而 DNS01 則只需配置 DNS 服務提供商的金鑰,cert-manager 負責維護對應的驗證記錄即可。我們使用的是 AWS 的 Route53 服務,正好也在 cert-manager 內建支援的 DNS provider 列表 內。因此我們選用了後者。

另外,cert-manager 提供了兩種 Issuer — IssuerClusterIssuer,前者僅限於 Issuer 所在的名稱空間內使用,而 ClusterIssuer 可在叢集內的任意名稱空間通用。

我們建立了兩個 ClusterIssuer,名叫 letsencrypt-prdletsencrypt-stg,例如:

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prd
spec:
  acme:
    email: user@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prd # 用於儲存 ACME Account 私鑰的 Secret
    solvers:
      - selector: {}
        dns01:
          route53: # Route53 相關引數
            region: us-east-2
            accessKeyID: ... # AWS Access Key ID
            secretAccessKeySecretRef:
              name: route53-secret-access-key # 儲存 AWS Access Key Secret 的 Kubernetes Secret 名稱
              key: secret_access_key # 儲存 AWS Access Key Secret 值的欄位名

修改 Chart Values

在上面的 helmfile.yaml 中,可以看到我們配置了 values 檔案 values/cert-manager/values.yaml,內容如下:

ingressShim:
  defaultIssuerKind: ClusterIssuer
  defaultIssuerName: letsencrypt-prd # 剛剛建立的 ClusterIssuer 名稱

我們配置了針對 ingressShim 的預設 Issuer。這樣我們就可以不手動建立 Certificate,cert-manager 將會「監視」叢集內的 Ingress 資源,當有 Ingress 配置了 spec.tls 時,自動讀取 hosts、建立證照並將證照儲存至 secretName 指定的 Secret 內,實現完全自動化。

建立並使用證照

由於開啟了 ingressShim 功能,因此我們只要按照通常思路使用 Ingress 即可。例如:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: api
  namespace: staging
spec:
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              serviceName: api
              servicePort: http
            path: /
  tls:
    - secretName: api-tls-certificate

spec.tls.secretName 設定為想要儲存證照的 Secret 名稱(例如例子中的 api-tls-certificate),cert-manager 將讀取這個名稱並將申請好的證照儲存到該 Secert 內,以供 Ingress 使用。

小結

根據上文的介紹,似乎這是一套完美的解決方案?不盡然。

目前 cert-manager 專案還處於 beta 階段,每次釋出新的 release 都可能包含 breaking change。在我們使用的這段時間內,每隔幾個版本就需要手動升級一次,升級過程中有時會出現一些意料之外的小問題。例如最近的 0.13 -> 0.14 升級過程中,發現新版本的 CRD manifests 硬編碼要求相關資源必須安裝到 cert-manager 名稱空間。希望 cert-manager 官方能儘快推進正式版的釋出,減少手動升級的次數和成本。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Former WinForm and PHP engineer. Now prefer Golang and Rust, and mainly working on DevSecOps and Kubernetes.

相關文章