最新消息:在TwitterMastodon獲取專案更新

cert-manager Webhook Pod 的權威除錯指南

上次驗證:2022 年 9 月 8 日

cert-manager webhook 是一個 pod,它作為您的 cert-manager 安裝的一部分執行。當使用 kubectl 套用資訊清單時,Kubernetes API 伺服器會透過 TLS 呼叫 cert-manager webhook 來驗證您的資訊清單。本指南可協助您除錯 Kubernetes API 伺服器和 cert-manager webhook pod 之間的通訊問題。

此頁面中列出的錯誤訊息會在安裝或升級 cert-manager 時,或在嘗試建立 Certificate、Issuer 或任何其他 cert-manager 自訂資源後不久遇到。

在下圖中,我們展示了除錯 cert-manager webhook 問題時的常見模式:當建立 cert-manager 自訂資源時,API 伺服器會透過 TLS 連接到 cert-manager webhook pod。紅色叉號表示 API 伺服器無法與 webhook 通訊。

Diagram that shows a kubectl command that aims to create an issuer resource, and an arrow towards the Kubernetes API server, and an arrow between the API server and the webhook that indicates that the API server tries to connect to the webhook. This last arrow is crossed in red.

本文件的其餘部分將介紹您可能遇到的錯誤訊息。

錯誤:connect: connection refused

此問題已在 4 個 GitHub 問題中回報 (#2736#3133#3445#4425),在外部專案的 1 個 GitHub 問題中回報 (aws-load-balancer-controller#1563),在 Stack Overflow 上 (serverfault#1076563),並且在 13 則 Slack 訊息中提到,可以使用搜尋 in:#cert-manager in:#cert-manager-dev ":443: connect: connection refused" 列出。這個錯誤訊息也可能在其他正在建立 webhook 的專案中找到 (kubewarden-controller#110)。

在安裝或升級 cert-manager 後不久,當您建立 Certificate、Issuer 或任何其他 cert-manager 自訂資源時,可能會遇到此錯誤。例如,使用以下命令建立 Issuer 資源

kubectl apply -f- <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: example
spec:
selfSigned: {}
EOF

會顯示以下錯誤訊息

Error from server (InternalError): error when creating "STDIN":
Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook:
Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s":
dial tcp 10.96.20.99:443: connect: connection refused

當使用 Helm 安裝或升級 cert-manager 1.5.0 及更高版本時,執行 helm installhelm upgrade 時,可能會出現非常相似的錯誤訊息

Error: INSTALLATION FAILED: Internal error occurred:
failed calling webhook "webhook.cert-manager.io": failed to call webhook:
Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s":
dial tcp 10.96.20.99:443: connect: connection refused

當 API 伺服器嘗試與 cert-manager-webhook 建立 TCP 連線時,會發生「連線遭拒」訊息。在 TCP 術語中,API 伺服器傳送了 SYN 封包以啟動 TCP 交握,並收到 RST 封包作為回應。

如果我們要在執行 API 伺服器的控制平面節點內使用 tcpdump,我們會看到傳回給 API 伺服器的封包

192.168.1.43 (apiserver) -> 10.96.20.99 (webhook pod) TCP 59466 → 443 [SYN]
10.96.20.99 (webhook pod) -> 192.168.1.43 (apiserver) TCP 443 → 59466 [RST, ACK]

當沒有任何程式在接聽要求的連接埠時,Linux 核心會傳送 RST 封包。RST 封包也可能由其中一個 TCP 躍點傳回,例如,防火牆,如 Stack Overflow 頁面 連線遭拒錯誤的原因可能有哪些? 中所述。

請注意,防火牆通常不會傳回 RST 封包;它們通常會完全丟棄 SYN 封包,最終您會收到錯誤訊息 i/o timeoutcontext deadline exceeded。如果是這種情況,請繼續查看 錯誤:i/o timeout (連線問題)錯誤:context deadline exceeded 部分進行調查。

讓我們從最接近 TCP 連線來源 (API 伺服器) 到其目的地 (pod cert-manager-webhook) 的可能原因進行排除。

讓我們假設名稱 cert-manager-webhook.cert-manager.svc 已解析為 10.43.183.232。這是一個叢集 IP。執行 API 伺服器處理程序的控制平面節點會使用其 iptables 來使用 pod IP 重寫 IP 目的地。這可能是第一個問題:有時,沒有 pod IP 與給定的叢集 IP 相關聯,因為只要就緒探測器不起作用,kubelet 就不會使用 pod IP 填寫 Endpoint 資源。

讓我們首先檢查它是否是 Endpoint 資源的問題

kubectl get endpoints -n cert-manager cert-manager-webhook

有效的輸出會像這樣

NAME ENDPOINTS AGE
cert-manager-webhook 10.244.0.2:10250 27d ✅

如果您有這個有效的輸出,並且有 connect: connection refused,則問題會更深入網路堆疊。我們不會深入研究此案例,但您可能會想使用 tcpdump 和 Wireshark 來查看流量是否正確地從 API 伺服器流向節點的主機命名空間。從主機命名空間到 pod 命名空間的流量已經正常運作,因為 kubelet 已經能夠到達就緒端點。

常見問題包括防火牆丟棄從控制平面到工作站的流量;例如,GKE 上的 API 伺服器只允許透過連接埠 10250 與工作站節點 (執行 cert-manager webhook 的位置) 通訊。在 EKS 中,您的安全性群組可能會拒絕從您的控制平面 VPC 到您的工作站 VPC 透過 TCP 10250 的流量。

如果您看到 <none>,表示 cert-manager webhook 正在正常執行,但無法到達其就緒端點

NAME ENDPOINTS AGE
cert-manager-webhook <none> 236d ❌

若要修正 <none>,您必須檢查 cert-manager-webhook 部署是否健康。當 cert-manager-webhook 未標示為 healthy 時,端點會保持在 <none>

kubectl get pod -n cert-manager -l app.kubernetes.io/name=webhook

您應該會看到 pod 正在 Running,並且就緒的容器數目為 0/1

NAME READY STATUS RESTARTS AGE
cert-manager-76578c9687-24kmr 0/1 Running 7 (8h ago) 28d ❌

我們不會詳細說明您取得 1/1Running 的情況,因為這表示 Kubernetes 中的狀態不一致。

繼續使用 0/1,這表示就緒端點沒有回應。當發生這種情況時,不會建立任何端點。下一步是找出為什麼就緒端點沒有回應。讓我們看看 kubelet 在點擊就緒端點時使用哪個連接埠

kubectl -n cert-manager get deploy cert-manager-webhook -oyaml | grep -A5 readiness

在我們的範例中,kubelet 將嘗試點擊的連接埠是 6080

readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 6080 # ✨
scheme: HTTP

現在,讓我們將連接埠轉送至該連接埠,看看 /healthz 是否正常運作。在 shell 工作階段中,執行

kubectl -n cert-manager port-forward deploy/cert-manager-webhook 6080

在另一個 shell 工作階段中,執行

curl -sS --dump-header - 127.0.0.1:6080/healthz

正常的輸出是

HTTP/1.1 200 OK ✅
Date: Tue, 07 Jun 2022 17:16:56 GMT
Content-Length: 0

如果就緒端點無法運作,您會看到

curl: (7) Failed to connect to 127.0.0.1 port 6080 after 0 ms: Connection refused ❌

此時,請驗證就緒端點是否在同一個連接埠上設定。讓我們看看記錄,以檢查我們的 webhook 是否正在接聽連接埠 6080 以取得其就緒端點

$ kubectl logs -n cert-manager -l app.kubernetes.io/name=webhook | head -10
I0607 webhook.go:129] "msg"="using dynamic certificate generating using CA stored in Secret resource"
I0607 server.go:133] "msg"="listening for insecure healthz connections" "address"=":6081" ❌
I0607 server.go:197] "msg"="listening for secure connections" "address"=":10250"
I0607 dynamic_source.go:267] "msg"="Updated serving TLS certificate"
...

在上面的範例中,問題是就緒連接埠的設定錯誤。在 webhook 部署中,引數 --healthz-port=6081 與就緒設定不符。

錯誤:i/o timeout (連線問題)

這個錯誤訊息在 Slack 上回報了 26 次。若要列出這些訊息,請使用 in:#cert-manager in:#cert-manager-dev "443: i/o timeout" 進行搜尋。這個錯誤訊息在 2 個 GitHub issue 中被回報 (#2811, #4073)

Error from server (InternalError): error when creating "STDIN": Internal error occurred:
failed calling webhook "webhook.cert-manager.io": failed to call webhook:
Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s":
dial tcp 10.0.0.69:443: i/o timeout

當 API 伺服器嘗試與 cert-manager webhook 通訊時,SYN 封包永遠不會被回應,導致連線逾時。如果我們在 webhook 的網路命名空間內執行 tcpdump,我們會看到

192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP 44772 → 443 [SYN]
192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN]
192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN]
192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN]

這個問題是由於 SYN 封包在某處被丟棄所造成的。

原因 1:GKE 私人叢集

預設的 Helm 設定應該適用於 GKE 私人叢集,但更改 securePort 可能會破壞它。

在上下文中,與公有 GKE 叢集(控制平面可以透過任何 TCP 連接埠自由地與 Pod 通訊)不同,私人 GKE 叢集中的控制平面只能透過 TCP 連接埠 10250443 與工作節點中的 Pod 通訊。這兩個開放的連接埠是指 Pod 內的 containerPort,而不是 Service 資源中稱為 port 的連接埠。

為了使其運作,Deployment 內的 containerPort 必須符合 10250443containerPort 是由 Helm 值 webhook.securePort 所設定的。預設情況下,webhook.securePort 設定為 10250

若要查看 containerPort 是否有問題,讓我們先從 Service 資源開始查看

kubectl get svc -n cert-manager cert-manager-webhook -oyaml

查看輸出,我們看到 targetPort 設定為 "https"

apiVersion: v1
kind: Service
metadata:
name: cert-manager-webhook
spec:
ports:
- name: https
port: 443 # ❌ This port is not the cause.
protocol: TCP
targetPort: "https" # 🌟 This port might be the cause.

上述 port: 443 不可能是原因的原因是,kube-proxy (也在控制平面節點上運行) 會將 webhook 的叢集 IP 轉換為 pod IP,並將上述 port: 443 轉換為 containerPort 中的值。

若要查看目標連接埠 "https" 背後是什麼,我們查看 Deployment 資源

kubectl get deploy -n cert-manager cert-manager-webhook -oyaml | grep -A3 ports:

輸出顯示 containerPort 未設定為 10250,這表示需要在 Google Cloud 中新增防火牆規則。

ports:
- containerPort: 12345 # 🌟 This port matches neither 10250 nor 443.
name: https
protocol: TCP

總結一下,如果上述 containerPort 不是 44310250,而且您不希望將 containerPort 變更為 10250,則您必須新增防火牆規則。您可以閱讀 Google 文件中的 在 GKE 私人叢集中新增防火牆規則章節。

在上下文中,我們沒有將 securePort 預設為 443 的原因是,繫結到 443 需要一個額外的 Linux 功能 (NET_BIND_SERVICE);另一方面,10250 不需要任何額外功能。

原因 2:在自訂 CNI 上使用 EKS

如果您在 EKS 上使用 Weave 或 Calico 等自訂 CNI,Kubernetes API 伺服器(位於自己的節點中)可能無法連線到 webhook Pod。發生這種情況的原因是,控制平面無法設定為在 EKS 上使用自訂 CNI 執行,這表示 CNI 無法啟用 API 伺服器與工作節點中運行的 Pod 之間的連線。

假設您正在使用 Helm,解決方法是在您的 values.yaml 檔案中新增以下值

webhook:
hostNetwork: true
securePort: 10260

或者,如果您從命令列使用 Helm,請使用以下標誌

--set webhook.hostNetwork=true --set webhook.securePort=10260

透過將 hostNetwork 設定為 true,webhook Pod 將在主機的網路命名空間中運行。透過在主機的網路命名空間中運行,webhook Pod 可以透過節點的 IP 存取,這表示您將解決 kube-apiserver 無法連線到任何 Pod IP 或叢集 IP 的問題。

透過將 securePort 設定為 10260,而不是依賴預設值 (為 10250),您將防止 webhook 與 kubelet 之間發生衝突。kubelet 是在每個 Kubernetes 工作節點上直接在主機上運行的代理程式,它使用連接埠 10250 向 kube-apiserver 公開其內部 API。

若要了解 hostnetworksecurePort 如何互動,我們必須查看 TCP 連線如何建立。當 kube-apiserver 程序嘗試連線到 webhook Pod 時,kube-proxy(也在控制平面節點上運行,即使沒有 CNI)會啟動並將 webhook 的叢集 IP 轉換為 webhook 的主機 IP

https://cert-manager-webhook.cert-manager.svc:443/validate
|
|Step 1: resolve to the cluster IP
v
https://10.43.103.211:443/validate
|
|Step 2: send TCP packet
v
src: 172.28.0.1:43021
dst: 10.43.103.211:443
|
|Step 3: kube-proxy rewrite (cluster IP to host IP)
v
src: 172.28.0.1:43021
dst: 172.28.0.2:10260
|
| control-plane node
| (host IP: 172.28.0.1)
------------|--------------------------------------------------
| (host IP: 172.28.0.2)
v worker node
+-------------------+
| webhook pod |
| listens on |
| 172.28.0.2:10260 |
+-------------------+

使用 10250 作為預設 securePort 的原因是,它可以解決 GKE 私人叢集的另一個限制,如上述 GKE 私人叢集章節所述。

原因 3:網路原則,Calico

假設您正在使用 Helm 圖表,並且您正在使用 webhook.securePort 的預設值(為 10250),並且您正在使用 Calico 等網路原則控制器,請檢查是否存在允許從 API 伺服器到 webhook Pod 的 TCP 連接埠 10250 的原則。

原因 4:EKS 和安全群組

假設您正在使用 Helm 圖表,並且您正在使用 webhook.securePort 的預設值(為 10250),您可能需要檢查您的 AWS 安全群組是否允許從控制平面的 VPC 到工作程式 VPC 的 TCP 連接埠 10250 的流量。

其他原因

如果以上原因都不適用,您將需要找出 webhook 無法連線的原因。

若要偵錯連線問題(即封包被丟棄),我們建議在每個 TCP 跳躍點使用 tcpdump 以及 Wireshark。您可以參考文章 偵錯 Kubernetes 網路:我的 kube-dns 無法運作!,了解如何使用 tcpdump 與 Wireshark 偵錯網路問題。

錯誤:x509: 憑證對 xxx.internal 有效,而非 cert-manager-webhook.cert-manager.svc (使用 Fargate Pod 的 EKS)

Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
x509: certificate is valid for ip-192-168-xxx-xxx.xxx.compute.internal,
not cert-manager-webhook.cert-manager.svc

此問題首次回報於 #3237

這可能是因為您在啟用 Fargate 的 EKS 上執行。Fargate 為每個 Pod 建立一個微型 VM,而 VM 的核心用於在其自身的命名空間中執行容器。問題在於每個微型 VM 都有自己的 kubelet。對於任何 Kubernetes 節點,VM 的埠 10250 由 kubelet 程序監聽。並且 10250 也是 cert-manager webhook 監聽的埠。

但這不是問題:kubelet 程序和 cert-manager webhook 程序在兩個獨立的網路命名空間中執行,並且埠不會衝突。無論是在傳統的 Kubernetes 節點中,還是在 Fargate 微型 VM 內部,情況都是如此。

當 API 伺服器嘗試訪問 Fargate pod 時,問題就出現了:微型 VM 的主機網路命名空間被配置為轉發每個可能的埠,以最大限度地與傳統 pod 相容,如 Stack Overflow 頁面 EKS Fargate 連接到本地 kubelet 中所示。但是埠 10250 已被微型 VM 的 kubelet 使用,因此任何訪問此埠的內容都不會被轉發,而是會訪問 kubelet。

總而言之,cert-manager webhook 看起來是健康的,並且能夠根據其日誌監聽埠 10250,但是微型 VM 的主機不會將 10250 轉發到 webhook 的網路命名空間。這就是為什麼在執行 TLS 交握時,您會看到有關出現意外網域的消息:雖然 cert-manager webhook 正在正確執行,但響應 API 伺服器的是 kubelet。

這是 Fargate 微型 VM 的限制:pod 的 IP 和節點的 IP 是相同的。它給您與傳統 pod 相同的體驗,但它帶來了網路方面的挑戰。

要解決此問題,訣竅是更改 cert-manager webhook 正在監聽的埠。使用 Helm,我們可以使用參數 webhook.securePort

helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.16.1 \
--set webhook.securePort=10260

錯誤:找不到服務 "cert-managercert-manager-webhook"

Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred:
failed calling webhook "webhook.cert-manager.io": failed to call webhook:
Post "https://cert-managercert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s":
service "cert-managercert-manager-webhook" not found

此錯誤已在 2 個 GitHub 問題中回報 (#3195#4999)。

我們不知道此錯誤的原因,如果您碰巧遇到此錯誤,請在上面的其中一個 GitHub 問題上發表評論。

錯誤:服務 "cert-manager-webhook" 沒有可用的端點 (OVHCloud)

Error: INSTALLATION FAILED: Internal error occurred:
failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
no endpoints available for service "cert-manager-webhook"

此問題首次在 Slack 中回報一次 (1)。

此錯誤很少見,只在 OVHcloud 管理的 Kubernetes 叢集中出現過,其中 etcd 資源配額相當低。etcd 是儲存 Kubernetes 資源 (例如 pod 和部署) 的資料庫。OVHCloud 限制了 etcd 中資源使用的磁碟空間。當達到限制時,整個叢集開始出現不穩定的行為,其中一個症狀是 kubelet 不會建立 Endpoint 資源。

若要驗證它是否確實是配額問題,您應該能夠在您的 kube-apiserver 日誌中看到以下訊息

rpc error: code = Unknown desc = ETCD storage quota exceeded
rpc error: code = Unknown desc = quota computation: etcdserver: not capable
rpc error: code = Unknown desc = The OVHcloud storage quota has been reached

解決方法是移除一些資源 (例如 CertificateRequest 資源) 以低於限制,如 OVHCloud 的 ETCD 配額錯誤,疑難排解 頁面中所述。

錯誤:x509: 憑證已過期或尚未生效

此錯誤訊息在 Slack 中回報一次 (1)。

當使用 kubectl apply

Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://kubernetes.default.svc:443/apis/webhook.cert-manager.io/v1beta1/mutations?timeout=30s:
x509: certificate has expired or is not yet valid

此錯誤訊息在 Slack 中回報一次 (1)。

請回覆上述 Slack 訊息,因為我們仍然不確定是什麼原因導致此問題;若要存取 Kubernetes Slack,請造訪 https://slack.k8s.io/

錯誤:net/http:等待連線時取消請求

Error from server (InternalError): error when creating "STDIN":
Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

此錯誤訊息在 Slack 中回報一次 (1)。

錯誤:context deadline exceeded

此錯誤訊息在 GitHub 問題中回報 (23192706 51895004),以及一次 在 Stack Overflow 上

當在安裝或升級 cert-manager 後嘗試套用發行者或任何其他 cert-manager 自訂資源時,此錯誤會出現在 cert-manager 0.12 及更高版本中

Error from server (InternalError): error when creating "STDIN":
Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
context deadline exceeded

ℹ️ 在較舊版本的 cert-manager (0.11 及更低版本) 中,webhook 依賴 APIService 機制,訊息看起來有點不同,但原因相同

Error from server (InternalError): error when creating "STDIN":
Internal error occurred: failed calling webhook "webhook.certmanager.k8s.io":
Post https://kubernetes.default.svc:443/apis/webhook.certmanager.k8s.io/v1beta1/mutations?timeout=30s:
context deadline exceeded

ℹ️ 當使用 cmctl check api 時,也會出現訊息 context deadline exceeded。原因相同,您可以繼續閱讀本節以進行除錯。

Not ready: Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook:
Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s":
context deadline exceeded

訊息 context deadline exceeded 的問題在於它模糊了超時的 HTTP 連線部分。當出現此訊息時,我們無法判斷 HTTP 互動的哪個部分超時。可能是 DNS 解析、TCP 交握、TLS 交握、傳送 HTTP 請求或接收 HTTP 回應。

ℹ️ 為了說明,您可以在上述錯誤訊息中看到的查詢參數 ?timeout=30s 是 API 伺服器在呼叫 webhook 時決定的超時。它通常設定為 10 或 30 秒。

除錯此問題的第一步是確保 cert-manager 變更和驗證 webhook 配置上的 timeoutSeconds 欄位設定為 30 秒 (最大值)。預設情況下,它設定為 10 秒,這表示 context deadline exceeded 可能會隱藏其他超時訊息。若要檢查 timeoutSeconds 欄位的值,請執行

$ kubectl get mutatingwebhookconfigurations,validatingwebhookconfigurations cert-manager-webhook \
-ojsonpath='{.items[*].webhooks[*].timeoutSeconds}'
10 10

這表示兩個 webhook 都設定了 10 秒的內容超時。若要將其設定為 30 秒,請執行

kubectl patch mutatingwebhookconfigurations,validatingwebhookconfigurations cert-manager-webhook \
--type=json -p '[{"op": "replace", "path": "/webhooks/0/timeoutSeconds", "value": 30}]'

下圖顯示了在 30 秒後擲出的全方位 context deadline exceeded 錯誤訊息 (由外框表示) 背後可能隱藏的三個錯誤

context deadline exceeded
|
30 seconds |
timeout v
+-------------------------------------------------------------------------+
| |
| i/o timeout |
| | net/http: TLS handshake timeout |
| 10 seconds | | |
| timeout v | |
|------------+ 30 seconds | net/http: request canceled |
|TCP | timeout v while awaiting headers |
|handshake +---------------------+ | |
|------------| TLS | | |
| | handshake +------------+ 10 seconds | |
| +---------------------| sending | timeout v |
| | request +------------+ |
| +------------|receiving |------+ |
| |resp. header| recv.| |
| +------------+ resp.| |
| | body +-----+
| +------|other|
| |logic|
| +-----+
+-------------------------------------------------------------------------+

在本節的其餘部分,我們將嘗試觸發三個「更具體」的錯誤之一

  • i/o timeout 是 TCP 握手逾時,來自 Kubernetes apiserver 中的 DialTimeout。名稱解析可能是原因,但通常,此訊息會在 API 伺服器發送 SYN 封包並等待 10 秒以從 cert-manager webhook 接收 SYN-ACK 封包後出現。
  • net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 是 HTTP 回應逾時,來自 這裡,並設定為 30 秒。Kubernetes API 伺服器已經發送 HTTP 請求,並且正在等待 HTTP 回應標頭(例如,HTTP/1.1 200 OK)。
  • net/http: TLS handshake timeout 是在 TCP 握手完成時,且 Kubernetes API 伺服器已發送初始 TLS 握手封包(ClientHello)並等待 10 秒,讓 cert-manager webhook 以 ServerHello 封包回應。

我們可以將這三則訊息分為兩類:一種是連線問題(SYN 被丟棄),另一種是 webhook 問題(即 TLS 憑證錯誤,或 webhook 沒有回傳任何 HTTP 回應)。

逾時訊息類別
i/o timeout連線問題
net/http: TLS handshake timeoutwebhook 端問題
net/http: request canceled while awaiting headerswebhook 端問題

第一步是排除 webhook 端問題。在您的 shell 工作階段中,執行以下命令

kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250

在另一個 shell 工作階段中,檢查您是否可以連線到 webhook

curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \
--service-name cert-manager-webhook-ca \
--cacert <(kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' | base64 -d) \
https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //'
{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}}
EOF

正確的輸出看起來像這樣

POST /validate HTTP/1.1
Host: cert-manager-webhook.cert-manager.svc:10250
User-Agent: curl/7.83.0
Accept: */*
Content-Length: 1299
Content-Type: application/x-www-form-urlencoded
HTTP/1.1 200 OK
Date: Wed, 08 Jun 2022 14:52:21 GMT
Content-Length: 2029
Content-Type: text/plain; charset=utf-8
...
"response": {
"uid": "",
"allowed": true
}

如果回應顯示 200 OK,我們可以排除 webhook 端問題。由於初始錯誤訊息是 context deadline exceeded,而不是 apiserver 端問題,例如 x509: certificate signed by unknown authorityx509: certificate has expired or is not yet valid,我們可以得出結論,問題是連線問題:Kubernetes API 伺服器無法建立到 cert-manager webhook 的 TCP 連線。請依照上方「錯誤:i/o timeout (連線問題)」章節中的指示繼續偵錯。

錯誤:net/http: TLS handshake timeout

此錯誤訊息曾在 1 個 GitHub 問題中回報 (#2602)。

Error from server (InternalError): error when creating "STDIN":
Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
net/http: TLS handshake timeout

查看以上圖表,此錯誤訊息表示 Kubernetes API 伺服器已成功建立到與 cert-manager webhook 關聯的 Pod IP 的 TCP 連線。TLS 握手逾時表示 cert-manager webhook 程序並非結束 TCP 連線的那方:中間有一些 HTTP Proxy,可能正在等待純 HTTP 請求而不是 ClientHello 封包。

我們不知道此錯誤的原因。如果您注意到此錯誤,請在上述 GitHub 問題中留言。

錯誤:HTTP probe failed with statuscode: 500

此錯誤訊息曾在 2 個 GitHub 問題中回報 (#3185, #4557)。

此錯誤訊息在 cert-manager webhook 上顯示為事件

Warning Unhealthy <invalid> (x13 over 15s) kubelet, node83
Readiness probe failed: HTTP probe failed with statuscode: 500

我們不知道此錯誤的原因。如果您注意到此錯誤,請在上述 GitHub 問題中留言。

錯誤:Service Unavailable

此錯誤曾在 1 個 GitHub 問題中回報 (#4281)

Error from server (InternalError): error when creating "STDIN": Internal error occurred:
failed calling webhook "webhook.cert-manager.io":
Post "https://my-cert-manager-webhook.default.svc:443/mutate?timeout=10s":
Service Unavailable

上述訊息會在使用 Weave CNI 的 Kubernetes 叢集中出現。

我們不知道此錯誤的原因。如果您注意到此錯誤,請在上述 GitHub 問題中留言。

錯誤:failed calling admission webhook: the server is currently unable to handle the request

此問題曾在 4 個 GitHub 問題中回報 (1369, 1425 3542, 4852)

Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred:
failed calling admission webhook "issuers.admission.certmanager.k8s.io":
the server is currently unable to handle the request

我們不知道此錯誤的原因。如果您能夠重現此錯誤,請在上述其中一個 GitHub 問題中留言。

錯誤:x509: certificate signed by unknown authority

在 GitHub 問題中回報 (2602)

當安裝或升級 cert-manager 並使用不是 cert-manager 的命名空間時

Error: UPGRADE FAILED: release core-l7 failed, and has been rolled back due to atomic being set:
failed to create resource: conversion webhook for cert-manager.io/v1alpha3, Kind=ClusterIssuer failed:
Post https://cert-manager-webhook.core-l7.svc:443/convert?timeout=30s:
x509: certificate signed by unknown authority

當建立 Issuer 或任何其他 cert-manager 自訂資源時,可能會顯示非常相似的錯誤訊息

Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s:
x509: certificate signed by unknown authority`

使用 cmctl installcmctl check api,您可能會看到以下錯誤訊息

2022/06/06 15:36:30 Not ready: the cert-manager webhook CA bundle is not injected yet
(Internal error occurred: conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed:
Post "https://<company_name>-cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s":
x509: certificate signed by unknown authority)

如果您使用 cert-manager 0.14 及更早版本搭配 Helm,且您安裝在與 cert-manager 不同的命名空間中,則 CRD 清單的命名空間名稱會硬式編碼為 cert-manager。您可以在以下註釋中看到硬式編碼的命名空間

kubectl get crd issuers.cert-manager.io -oyaml | grep inject

您將看到以下內容

cert-manager.io/inject-ca-from-secret: cert-manager/cert-manager-webhook-ca
# ^^^^^^^^^^^^
# hardcoded

注意 1: cert-manager Helm 圖表中的這個錯誤在 cert-manager 0.15 中已修復

注意 2:自 cert-manager 1.6 起,由於不再需要轉換,此註釋已不再使用於 cert-manager CRD 上。

如果您仍在使用 cert-manager 0.14 或更早版本,解決方案是使用 helm template 呈現清單,然後編輯註釋以使用正確的命名空間,然後使用 kubectl apply 安裝 cert-manager。

如果您使用 cert-manager 1.6 及更早版本,問題可能是由於 cainjector 卡住,試圖將 cert-manager webhook 建立並儲存在 Secret 資源 cert-manager-webhook-ca 中的自簽憑證注入到 cert-manager CRD 的 spec.caBundle 欄位中。第一步是檢查 cainjector 是否正常執行

$ kubectl -n cert-manager get pods -l app.kubernetes.io/name=cainjector
NAME READY STATUS RESTARTS AGE
cert-manager-cainjector-5c55bb7cb4-6z4cf 1/1 Running 11 (31h ago) 28d

查看日誌,您將能夠判斷領導者選舉是否成功。領導者選舉完成最多可能需要一分鐘。

I0608 start.go:126] "starting" version="v1.8.0" revision="e466a521bc5455def8c224599c6edcd37e86410c"
I0608 leaderelection.go:248] attempting to acquire leader lease kube-system/cert-manager-cainjector-leader-election...
I0608 leaderelection.go:258] successfully acquired lease kube-system/cert-manager-cainjector-leader-election
I0608 controller.go:186] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting Controller"
I0608 controller.go:186] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting Controller"
I0608 controller.go:220] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting workers" "worker count"=1
I0608 controller.go:220] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting workers" "worker count"=1

正常的輸出會包含像這樣的行

I0608 sources.go:184] cert-manager/secret/customresourcedefinition/generic-inject-reconciler
"msg"="Extracting CA from Secret resource" "resource_name"="issuers.cert-manager.io" "secret"="cert-manager/cert-manager-webhook-ca"
I0608 controller.go:178] cert-manager/secret/customresourcedefinition/generic-inject-reconciler
"msg"="updated object" "resource_name"="issuers.cert-manager.io"

現在,尋找任何表明由 cert-manager webhook 建立的 Secret 資源無法載入的訊息。可能會出現的兩個錯誤訊息是

E0608 sources.go:201] cert-manager/secret/customresourcedefinition/generic-inject-reconciler
"msg"="unable to fetch associated secret" "error"="Secret \"cert-manager-webhook-caq\" not found"

以下訊息表示已略過給定的 CRD,因為缺少註釋。您可以忽略這些訊息

I0608 controller.go:156] cert-manager/secret/customresourcedefinition/generic-inject-reconciler
"msg"="failed to determine ca data source for injectable" "resource_name"="challenges.acme.cert-manager.io"

如果 cainjector 日誌看起來沒有任何問題,您會想要檢查驗證、變更和轉換配置中的 spec.caBundle 欄位是否正確。Kubernetes API 伺服器會使用該欄位的內容來信任 cert-manager webhook。caBundle 包含 cert-manager webhook 啟動時建立的自簽 CA。

$ kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig'
{
"caBundle": "LS0tLS1...LS0tLS0K",
"service": {
"name": "cert-manager-webhook",
"namespace": "cert-manager",
"path": "/validate",
"port": 443
}
}
$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig'
{
"caBundle": "LS0tLS1...RFLS0tLS0K",
"service": {
"name": "cert-manager-webhook",
"namespace": "cert-manager",
"path": "/validate",
"port": 443
}
}

讓我們看看 caBundle 的內容

$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson \
| jq '.webhooks[].clientConfig.caBundle' -r | base64 -d \
| openssl x509 -noout -text -in -
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b
Signature Algorithm: ecdsa-with-SHA384
Issuer: CN = cert-manager-webhook-ca
Validity
Not Before: May 10 16:13:37 2022 GMT
Not After : May 10 16:13:37 2023 GMT
Subject: CN = cert-manager-webhook-ca

讓我們檢查 caBundle 的內容是否可用於連接到 webhook

$ kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' \
| base64 -d | openssl x509 -noout -text -in -
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b
Signature Algorithm: ecdsa-with-SHA384
Issuer: CN = cert-manager-webhook-ca
Validity
Not Before: May 10 16:13:37 2022 GMT
Not After : May 10 16:13:37 2023 GMT
Subject: CN = cert-manager-webhook-ca

我們的最後一個測試是嘗試使用此信任套件連接到 webhook。讓我們將 port-forward 連接到 webhook pod

kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250

在另一個 shell 工作階段中,使用以下命令發送 /validate HTTP 請求

curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \
--service-name cert-manager-webhook-ca \
--cacert <(kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig.caBundle' -r | base64 -d) \
https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //'
{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}}
EOF

您應該會看到成功的 HTTP 請求和回應

POST /validate HTTP/1.1
Host: cert-manager-webhook.cert-manager.svc:10250
User-Agent: curl/7.83.0
Accept: */*
Content-Length: 1299
Content-Type: application/x-www-form-urlencoded
HTTP/1.1 200 OK
Date: Wed, 08 Jun 2022 16:20:45 GMT
Content-Length: 2029
Content-Type: text/plain; charset=utf-8
...

錯誤:cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied

此訊息在 GitHub 問題 3717 中回報。

在 GKE Autopilot 上安裝 cert-manager 時,您會看到以下訊息

Error: rendered manifests contain a resource that already exists. Unable to continue with install:
could not get information about the resource:
mutatingwebhookconfigurations.admissionregistration.k8s.io "cert-manager-webhook" is forbidden:
User "XXXX" cannot get resource "mutatingwebhookconfigurations" in API group "admissionregistration.k8s.io" at the cluster scope:
GKEAutopilot authz: cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied

當在 GKE Autopilot 上使用 Kubernetes 1.20 及更舊版本時,會出現此錯誤訊息。這是由於 GKE Autopilot 中對變更 admission webhooks 的限制所致。

截至 2021 年 10 月,「快速」Autopilot 發布通道已為 Kubernetes 主節點推出 1.21 版本。透過 Helm chart 安裝可能會導致錯誤訊息,但據某些使用者回報,cert-manager 運作正常。歡迎提供意見反應和 PR。

錯誤:the namespace "kube-system" is managed and the request's verb "create" is denied

當在 GKE Autopilot 上使用 Helm 安裝 cert-manager 時,您會看到以下錯誤訊息

Not ready: the cert-manager webhook CA bundle is not injected yet

在此失敗之後,您應該仍然會看到三個 pod 正常運行

$ kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-76578c9687-24kmr 1/1 Running 0 47m
cert-manager-cainjector-b7d47f746-4799n 1/1 Running 0 47m
cert-manager-webhook-7f788c5b6-mspnt 1/1 Running 0 47m

但是查看任何一個日誌,您都會看到以下錯誤訊息

E0425 leaderelection.go:334] error initially creating leader election record:
leases.coordination.k8s.io is forbidden: User "system:serviceaccount:cert-manager:cert-manager-webhook"
cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system":
GKEAutopilot authz: the namespace "kube-system" is managed and the request's verb "create" is denied

這是由於 GKE Autopilot 的限制所致。無法在 kube-system 命名空間中建立資源,而 cert-manager 使用廣為人知的 kube-system 來管理領導者選舉。為了繞過此限制,您可以告訴 Helm 使用不同的命名空間進行領導者選舉

helm install cert-manager jetstack/cert-manager --version 1.8.0 \
--namespace cert-manager --create-namespace \
--set global.leaderElection.namespace=cert-manager