用 k3s 輕鬆管理 SSL 證書

2020-04-13 15:32:00

如何在樹莓派上使用 k3s 和 Let's Encrypt 來加密你的網站。

在中,我們在 k3s 叢集上部署了幾個簡單的網站。那些是未加密的網站。不錯,它們可以工作,但是未加密的網站有點太過時了!如今,大多數網站都是加密的。在本文中,我們將安裝 cert-manager 並將其用於在叢集上以部署採用 TLS 加密的網站。這些網站不僅會被加密,而且還會使用有效的公共證書,這些證書會從 Let's Encrypt 自動獲取和更新!讓我們開始吧!

準備

要繼續閱讀本文,你將需要我們在上一篇文章中構建的 。另外,你需要擁有一個公用靜態 IP 地址,並有一個可以為其建立 DNS 記錄的域名。如果你有一個動態 DNS 提供程式為你提供域名,可能也行。但是,在本文中,我們使用靜態 IP 和 CloudFlare 來手動建立 DNS 的 A 記錄。

我們在本文中建立組態檔時,如果你不想鍵入它們,則可以在此處進行下載。

我們為什麼使用 cert-manager?

Traefik(在 k3s 預先綑綁了)實際上具有內建的 Let's Encrypt 支援,因此你可能想知道為什麼我們要安裝第三方軟體包來做同樣的事情。在撰寫本文時,Traefik 中的 Let's Encrypt 支援檢索證書並將其儲存在檔案中。而 cert-manager 會檢索證書並將其儲存在 Kubernetes 的 “機密資訊secret” 中。我認為,“機密資訊”可以簡單地按名稱參照,因此更易於使用。這就是我們在本文中使用 cert-manager 的主要原因。

安裝 cert-manager

通常,我們只是遵循 cert-manager 的文件在 Kubernetes 上進行安裝。但是,由於我們使用的是 ARM 體系結構,因此我們需要進行一些更改,以便我們可以完成這個操作。

第一步是建立 cert-manager 名稱空間。名稱空間有助於將 cert-manager 的 Pod 排除在我們的預設名稱空間之外,因此當我們使用自己的 Pod 執行 kubectl get pods 之類的操作時,我們不必看到它們。建立名稱空間很簡單:

kubectl create namespace cert-manager

安裝說明會讓你下載 cert-manager 的 YAML 組態檔並將其一步全部應用到你的叢集。我們需要將其分為兩個步驟,以便為基於 ARM 的樹莓派修改檔案。我們將下載檔案並一步一步進行轉換:

curl -sL \https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml |\sed -r 's/(image:.*):(v.*)$/\1-arm:\2/g' > cert-manager-arm.yaml

這會下載組態檔,並將包含的所有 docker 映象更新為 ARM 版本。來檢查一下它做了什麼:

$ grep image: cert-manager-arm.yaml          image: "quay.io/jetstack/cert-manager-cainjector-arm:v0.11.0"          image: "quay.io/jetstack/cert-manager-controller-arm:v0.11.0"          image: "quay.io/jetstack/cert-manager-webhook-arm:v0.11.0"

如我們所見,三個映象現在在映象名稱上新增了 -arm。現在我們有了正確的檔案,我們只需將其應用於叢集:

kubectl apply -f cert-manager-arm.yaml

這將安裝 cert-manager 的全部。我們可以通過 kubectl --namespace cert-manager get pods 來檢查安裝何時完成,直到所有 Pod 都處於 Running 狀態。

這就完成了 cert-manager 的安裝!

Let's Encrypt 概述

Let's Encrypt 的好處是,它免費為我們提供了經過公共驗證的 TLS 證書!這意味著我們可以擁有一個完全有效的、可供任何人存取的 TLS 加密網站,這些家庭或業餘的愛好活動掙不到錢,也無需自己掏腰包購買 TLS 證書!以及,當通過 cert-manager 使用 Let's Encrypt 的證書時,獲得證書的整個過程是自動化的,證書的續訂也是自動的!

但它是如何工作的?下面是該過程的簡化說明。我們(或代表我們的 cert-manager)向 Let's Encrypt 發出我們擁有的域名的證書請求。Let's Encrypt 通過使用 ACME DNS 或 HTTP 驗證機制來驗證我們是否擁有該域。如果驗證成功,則 Let's Encrypt 將向我們提供證書,這些證書將由 cert-manager 安裝在我們的網站(或其他 TLS 加密的端點)中。在需要重複此過程之前,這些證書可以使用 90 天。但是,cert-manager 會自動為我們更新證書。

在本文中,我們將使用 HTTP 驗證方法,因為它更易於設定並且適用於大多數情況。以下是幕後發生的基本過程。cert-manager 向 Let's Encrypt 發出證書請求。作為回應,Let's Encrypt 發出所有權驗證的質詢challenges。這個質詢是將一個 HTTP 資源放在請求證書的域名下的一個特定 URL 上。從理論上講,如果我們可以將該資源放在該 URL 上,並且讓 Let's Encrypt 可以遠端獲取它,那麼我們實際上必須是該域的所有者。否則,要麼我們無法將資源放置在正確的位置,要麼我們無法操縱 DNS 以使 Let's Encrypt 存取它。在這種情況下,cert-manager 會將資源放在正確的位置,並自動建立一個臨時的 Ingress 記錄,以將流量路由到正確的位置。如果 Let's Encrypt 可以讀到該質詢要求的資源並正確無誤,它將把證書發回給 cert-manager。cert-manager 將證書儲存為“機密資訊”,然後我們的網站(或其他任何網站)將使用這些證書通過 TLS 保護我們的流量。

為該質詢設定網路

我假設你要在家庭網路上進行設定,並擁有一個以某種方式連線到更廣泛的網際網路的路由器/接入點。如果不是這種情況,則可能不需要以下過程。

為了使質詢過程正常執行,我們需要一個我們要申請證書的域名,以將其路由到埠 80 上的 k3s 叢集。為此,我們需要告訴世界上的 DNS 系統它的位置。因此,我們需要將域名對映到我們的公共 IP 地址。如果你不知道你的公共 IP 地址是什麼,可以存取 WhatsMyIP 之類的地方,它會告訴你。接下來,我們需要輸入 DNS 的 A 記錄,該記錄將我們的域名對映到我們的公共 IP 地址。為了使此功能可靠地工作,你需要一個靜態的公共 IP 地址,或者你可以使用動態 DNS 提供商。一些動態 DNS 提供商會向你頒發一個域名,你可以按照以下說明使用它。我沒有嘗試過,所以不能肯定地說它適用於所有提供商。

對於本文,我們假設有一個靜態公共 IP,並使用 CloudFlare 來設定 DNS 的 A 記錄。如果願意,可以使用自己的 DNS 伺服器。重要的是你可以設定 A 記錄。

在本文的其餘部分中,我將使用 k3s.carpie.net 作為範例域名,因為這是我擁有的域。你顯然會用自己擁有的任何域名替換它。

為範例起見,假設我們的公共 IP 地址是 198.51.100.42。我們轉到我們的 DNS 提供商的 DNS 記錄部分,並新增一個名為 k3s.carpie.net 的型別為 A 的記錄(CloudFlare 已經假定了域的部分,因此我們只需輸入 k3s),然後輸入 198.51.100.42 作為 IPv4 地址。

請注意,有時 DNS 更新要傳播一段時間。你可能需要幾個小時才能解析該名稱。在繼續之前該名稱必須可以解析。否則,我們所有的證書請求都將失敗。

我們可以使用 dig 命令檢查名稱是否解析:

$ dig +short k3s.carpie.net198.51.100.42

繼續執行以上命令,直到可以返回 IP 才行。關於 CloudFlare 有個小注釋:ClouldFlare 提供了通過代理流量來隱藏你的實際 IP 的服務。在這種情況下,我們取回的是 CloudFlare 的 IP,而不是我們的 IP。但對於我們的目的,這應該可以正常工作。

網路設定的最後一步是設定路由器,以將埠 80 和 443 上的傳入流量路由到我們的 k3s 叢集。可悲的是,路由器設定頁面的差異很大,因此我無法確切地說明你的外觀是什麼樣子。大多數時候,我們需要的管理頁面位於“埠轉發”或類似內容下。我甚至看到過它列在“遊戲”之下(顯然是埠轉發主要用於的遊戲)!讓我們看看我的路由器的設定如何。

如果你和我的環境一樣,則轉到 192.168.0.1 登入到路由器管理應用程式。對於此路由器,它位於 “ NAT/QoS” -> “埠轉發”。在這裡,我們將埠 80/TCP 協定設定為轉發到 192.168.0.50(主節點 kmaster 的 IP)的埠 80。我們還設定埠 443 也對映到 kmaster。從技術上講,這對於質詢來說並不是必需的,但是在本文的結尾,我們將部署一個啟用 TLS 的網站,並且需要對映 443 來進行存取。因此,現在進行對映很方便。我們儲存並應用更改,應該一切順利!

設定 cert-manager 來使用 Let's Encrypt(暫存環境)

現在,我們需要設定 cert-manager 來通過 Let's Encrypt 頒發證書。Let's Encrypt 為我們提供了一個暫存(例如用於測試)環境,以便審視我們的設定。這樣它更能容忍錯誤和請求的頻率。如果我們對生產環境做了錯誤的操作,我們很快就會發現自己被暫時禁止存取了!因此,我們將使用暫存環境手動測試請求。

建立一個檔案 letsencrypt-issuer-staging.yaml,內容如下:

apiVersion: cert-manager.io/v1alpha2kind: ClusterIssuermetadata:  name: letsencrypt-stagingspec:  acme:    # The ACME server URL    server: https://acme-staging-v02.api.letsencrypt.org/directory    # Email address used for ACME registration    email: <your_email>@example.com    # Name of a secret used to store the ACME account private key    privateKeySecretRef:      name: letsencrypt-staging    # Enable the HTTP-01 challenge provider    solvers:    - http01:        ingress:          class: traefik

請確保將電子郵件地址更新為你的地址。如果出現問題或我們弄壞了一些東西,這就是 Let's Encrypt 與我們聯絡的方式!

現在,我們使用以下方法建立發行者issuer

kubectl apply -f letsencrypt-issuer-staging.yaml

我們可以使用以下方法檢查發行者是否已成功建立:

kubectl get clusterissuers

clusterissuers 是由 cert-manager 建立的一種新的 Kubernetes 資源型別。

現在讓我們手動請求一個測試證書。對於我們的網站,我們不需要這樣做;我們只是在測試這個過程,以確保我們的設定正確。

建立一個包含以下內容的證書請求檔案 le-test-certificate.yaml

apiVersion: cert-manager.io/v1alpha2kind: Certificatemetadata:  name: k3s-carpie-net  namespace: defaultspec:  secretName: k3s-carpie-net-tls  issuerRef:    name: letsencrypt-staging    kind: ClusterIssuer  commonName: k3s.carpie.net  dnsNames:  - k3s.carpie.net

該記錄僅表示我們要使用名為 letsencrypt-staging(我們在上一步中建立的)的 ClusterIssuer 來請求域 k3s.carpie.net 的證書,並在 Kubernetes 的機密資訊中名為 k3s-carpie-net-tls 的檔案中儲存該證書。

像平常一樣應用它:

kubectl apply -f le-test-certificate.yaml

我們可以通過以下方式檢視狀態:

kubectl get certificates

如果我們看到類似以下內容:

NAME                    READY   SECRET                  AGEk3s-carpie-net          True    k3s-carpie-net-tls      30s

我們走在幸福之路!(這裡的關鍵是 READY 應該是 True)。

解決證書頒發問題

上面是幸福的道路。如果 READYFalse,我們可以等等它,然後再次花點時間檢查狀態。如果它一直是 False,那麼我們就有需要解決的問題。此時,我們可以遍歷 Kubernetes 資源鏈,直到找到一條告訴我們問題的狀態訊息。

假設我們執行了上面的請求,而 READYFalse。我們可以從以下方面開始故障排除:

kubectl describe certificates k3s-carpie-net

這將返回很多資訊。通常,有用的內容位於 Events: 部分,該部分通常位於底部。假設最後一個事件是 Created new CertificateRequest resource "k3s-carpie-net-1256631848。然後我們描述describe一下該請求:

kubectl describe certificaterequest k3s-carpie-net-1256631848

現在比如說最後一個事件是 Waiting on certificate issuance from order default/k3s-carpie-net-1256631848-2342473830

那麼,我們可以描述該順序:

kubectl describe orders default/k3s-carpie-net-1256631848-2342473830

假設有一個事件,事件為 Created Challenge resource "k3s-carpie-net-1256631848-2342473830-1892150396" for domain "k3s.carpie.net"。讓我們描述一下該質詢:

kubectl describe challenges k3s-carpie-net-1256631848-2342473830-1892150396

從這裡返回的最後一個事件是 Presented challenge using http-01 challenge mechanism。看起來沒問題,因此我們瀏覽一下描述的輸出,並看到一條訊息 Waiting for http-01 challenge propagation: failed to perform self check GET request ... no such host。終於!我們發現了問題!在這種情況下,no such host 意味著 DNS 查詢失敗,因此我們需要返回並手動檢查我們的 DNS 設定,正確解析域的 DNS,並進行所需的任何更改。

清理我們的測試證書

我們實際上想要使用的是域名的真實證書,所以讓我們繼續清理證書和我們剛剛建立的機密資訊:

kubectl delete certificates k3s-carpie-netkubectl delete secrets k3s-carpie-net-tls

設定 cert-manager 以使用 Let's Encrypt(生產環境)

現在我們已經有了測試證書,是時候移動到生產環境了。就像我們在 Let's Encrypt 暫存環境中設定 cert-manager 一樣,我們現在也需要對生產環境進行同樣的操作。建立一個名為 letsencrypt-issuer-production.yaml 的檔案(如果需要,可以複製和修改暫存環境的檔案),其內容如下:

apiVersion: cert-manager.io/v1alpha2kind: ClusterIssuermetadata:name: letsencrypt-prodspec:acme: # The ACME server URL  server: https://acme-v02.api.letsencrypt.org/directory  # Email address used for ACME registration  email: <your_email>@example.com  # Name of a secret used to store the ACME account private key  privateKeySecretRef:    name: letsencrypt-prod  # Enable the HTTP-01 challenge provider  solvers:  - http01:      ingress:        class: traefik

(如果要從暫存環境進行複製,則唯一的更改是 server: URL。也請不要忘記修改電子郵件!)

應用它:

kubectl apply -f letsencrypt-issuer-production.yaml

申請我們網站的證書

重要的是需要注意,我們到目前為止完成的所有步驟都只需要進行一次!而對於將來的任何其他申請,我們可以從這個說明開始!

讓我們部署在中部署的同樣站點。(如果仍然可用,則可以修改 YAML 檔案。如果沒有,則可能需要重新建立並重新部署它)。

我們只需要將 mysite.yamlIngress 部分修改為:

---apiVersion: networking.k8s.io/v1beta1kind: Ingressmetadata:  name: mysite-nginx-ingress  annotations:    kubernetes.io/ingress.class: "traefik"    cert-manager.io/cluster-issuer: letsencrypt-prodspec:  rules:  - host: k3s.carpie.net    http:      paths:      - path: /        backend:          serviceName: mysite-nginx-service          servicePort: 80  tls:  - hosts:    - k3s.carpie.net    secretName: k3s-carpie-net-tls

請注意,上面僅顯示了 mysite.yamlIngress 部分。所做的更改是新增了註解 cert-manager.io/cluster-issuer: letsencrypt-prod。這告訴 traefik 建立證書時使用哪個發行者。 其他唯一增加的是 tls: 塊。這告訴 traefik 我們希望在主機 k3s.carpie.net 上具有 TLS 功能,並且我們希望 TLS 證書檔案儲存在機密資訊 k3s-carpie-net-tls 中。

請記住,我們沒有建立這些證書!(好吧,我們建立了名稱相似的測試證書,但我們刪除了這些證書。)Traefik 將讀取這些設定並繼續尋找機密資訊。當找不到時,它會看到注釋說我們想使用 letsencrypt-prod 發行者來獲取它。由此,它將提出請求並為我們安裝證書到機密資訊之中!

大功告成! 讓我們嘗試一下。

它現在具有了加密 TLS 所有優點!恭喜你!