新着情報: プロジェクトの最新情報をTwitterMastodonで入手できます。

cert-manager Webhook Podの決定版デバッグガイド

最終確認日: 2022年9月8日

cert-manager webhookは、cert-managerのインストールの一部として実行されるPodです。 kubectl でマニフェストを適用すると、Kubernetes APIサーバーはTLS経由でcert-manager webhookを呼び出してマニフェストを検証します。このガイドでは、Kubernetes APIサーバーとcert-manager webhook Pod間の通信問題をデバッグする方法について説明します。

このページに記載されているエラーメッセージは、cert-managerのインストールまたはアップグレード中、または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 issue(#2736, #3133, #3445, #4425)、外部プロジェクトの1つのGitHub issue(aws-load-balancer-controller#1563)、Stack Overflow(serverfault#1076563)で報告されており、Slackメッセージでは in:#cert-manager in:#cert-manager-dev ":443: connect: connection refused" という検索で一覧表示できる13件のメッセージで言及されています。このエラーメッセージは、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 install または helm 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

"connection refused"メッセージは、APIサーバーがcert-manager-webhookとのTCP接続を確立しようとしたときに発生します。TCPの用語では、APIサーバーはTCPハンドシェイクを開始するために SYN パケットを送信し、代わりに 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]

RST パケットは、要求されたポートをリッスンしているものがない場合に、Linuxカーネルによって送信されます。RST パケットは、Stack Overflowのページ What can be the reasons of connection refused errors? で詳しく説明されているように、ファイアウォールなどのTCPホップのいずれかによっても返される可能性があります。

ファイアウォールは通常、RST パケットを返さないことに注意してください。通常、SYN パケットを完全にドロップし、i/o timeout または context 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宛先を書き換えます。それが最初の問題である可能性があります。kubeletは、準備プローブが機能している限り、Pod IPでEndpointリソースを埋めないため、特定のクラスターIPにPod IPが関連付けられていない場合があります。

まず、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 が発生する場合は、ネットワークスタックの奥深くで問題が発生しています。このケースについては詳しく説明しませんが、APIサーバーからノードのホスト名前空間へのトラフィックが適切に流れているかどうかを確認するために、tcpdump と Wiresharkを使用するとよいでしょう。kubeletはすでに準備エンドポイントに到達できたため、ホスト名前空間からPod名前空間へのトラフィックはすでに正常に機能しています。

一般的な問題としては、コントロールプレーンからワーカーへのトラフィックをドロップするファイアウォールが挙げられます。たとえば、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が 正常 とマークされるまで、エンドポイントは <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/1 および Running が表示されるケースについては、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 が機能するかどうかを確認します。シェルセッションで、次を実行します。

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

別のシェルセッションで、次を実行します。

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ポートでポッドと自由に通信できるのに対し、プライベートGKEクラスターでは、コントロールプレーンはワーカーノードのポッドとTCPポート10250443でのみ通信できます。これらの2つの開いているポートは、Serviceリソースのportというポートではなく、ポッド内のcontainerPortを参照します。

動作させるには、Deployment内のcontainerPort10250または443のいずれかに一致する必要があります。containerPortは、Helmの値webhook.securePortで構成されます。デフォルトでは、webhook.securePort10250に設定されています。

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をポッドIPに変換し、上記のport: 443containerPortの値に変換するためです。

ターゲットポート"https"の背後にあるものを確認するために、Deploymentリソースを見てみましょう。

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

出力から、containerPort10250に設定されていないことがわかります。つまり、Google Cloudで新しいファイアウォールルールを追加する必要があるということです。

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

まとめると、上記のcontainerPort443または10250以外の値で、containerPort10250に変更したくない場合は、新しいファイアウォールルールを追加する必要があります。GoogleドキュメントのGKEプライベートクラスターでのファイアウォールルールの追加セクションを参照してください。

参考までに、securePort443にデフォルト設定しなかった理由は、443へのバインドには、追加のLinux機能(NET_BIND_SERVICE)が1つ必要になるためです。一方、10250には追加の機能は必要ありません。

原因2:カスタムCNI上のEKS

EKSを使用しており、WeaveやCalicoなどのカスタムCNIを使用している場合、Kubernetes APIサーバー(独自のノードにある)がwebhookポッドに到達できない可能性があります。これは、EKSでコントロールプレーンをカスタムCNIで実行するように構成できないためです。つまり、CNIはAPIサーバーとワーカーノードで実行されているポッド間の接続を有効にできません。

Helmを使用していると仮定すると、回避策はvalues.yamlファイルに次の値を追加することです。

webhook:
hostNetwork: true
securePort: 10260

または、コマンドラインからHelmを使用している場合は、次のフラグを使用します。

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

hostNetworktrueに設定すると、webhookポッドはホストのネットワーク名前空間で実行されます。ホストのネットワーク名前空間で実行することにより、webhookポッドはノードのIP経由でアクセス可能になります。つまり、kube-apiserverがポッドIPにもクラスターIPにも到達できないという事実を回避できます。

securePortをデフォルト値(10250)に頼るのではなく、10260に設定すると、webhookとkubeletの間の競合を防ぐことができます。すべてのKubernetesワーカーノードで実行され、ホスト上で直接実行されるエージェントであるkubeletは、ポート10250を使用して、内部APIをkube-apiserverに公開します。

hostnetworksecurePortがどのように相互作用するかを理解するには、TCP接続がどのように確立されるかを確認する必要があります。kube-apiserverプロセスがwebhookポッドに接続しようとすると、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などのネットワークポリシーコントローラーを使用している場合は、TCPポート10250を介してAPIサーバーからwebhookポッドへのトラフィックを許可するポリシーが存在することを確認してください。

原因4:EKSとセキュリティグループ

Helmチャートを使用しており、webhook.securePortのデフォルト値(10250)を使用している場合は、AWSセキュリティグループが、コントロールプレーンのVPCからワーカーのVPCへの10250経由のTCPトラフィックを許可していることを確認してください。

その他の原因

上記の原因のいずれも当てはまらない場合は、webhookが到達不能な理由を突き止める必要があります。

到達可能性の問題(つまり、パケットがドロップされる問題)をデバッグするには、すべてのTCPホップでtcpdumpとWiresharkを使用することをお勧めします。ネットワークの問題をデバッグするためにtcpdumpとWiresharkをどのように使用するかについては、Debugging Kubernetes Networking: my kube-dns is not working! という記事を参照してください。

エラー: x509: certificate is valid for xxx.internal, not cert-manager-webhook.cert-manager.svc (Fargateポッドを使用した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はポッドごとにマイクロVMを作成し、VMのカーネルを使用して、独自の名前空間でコンテナを実行します。問題は、各マイクロVMが独自のkubeletを取得することです。Kubernetesノードと同様に、VMのポート10250はkubeletプロセスによってリッスンされます。そして、10250は、cert-manager webhookがリッスンするポートでもあります。

しかし、それは問題ではありません。kubeletプロセスとcert-manager webhookプロセスは、2つの異なるネットワーク名前空間で実行されており、ポートが衝突することはありません。これは、従来のKubernetesノード内でも、FargateマイクロVM内でも同様です。

問題は、APIサーバーがFargateポッドにアクセスしようとするときに発生します。マイクロVMのホストネットワーク名前空間は、従来のポッドとの最大の互換性のために、すべての可能なポートをポート転送するように構成されています。これは、Stack OverflowのページEKS Fargate connect to local kubeletで説明されています。ただし、ポート10250はマイクロVMのkubeletですでに使用されているため、このポートにアクセスするものはポート転送されず、代わりにkubeletにアクセスすることになります。

要約すると、cert-manager webhookは正常に見え、ログどおりポート10250をリッスンできますが、マイクロVMのホストは10250をwebhookのネットワーク名前空間にポート転送しません。TLSハンドシェイク時に予期しないドメインが表示されるというメッセージが表示されるのはそのためです。cert-manager webhookは正しく実行されていますが、APIサーバーに応答しているのはkubeletです。

これは、FargateのマイクロVMの制限です。ポッドのIPとノードのIPは同じです。これにより、従来のポッドと同じエクスペリエンスが得られますが、ネットワークに関する課題が発生します。

この問題を解決するには、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

エラー: service "cert-managercert-manager-webhook" not found

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 issueで報告されました(#3195#4999)。

このエラーの原因は不明です。もし遭遇した場合は、上記のGitHub issueのいずれかにコメントしてください。

エラー: no endpoints available for service "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回(1)最初に報告されました。

このエラーはまれで、etcdリソースクォータが非常に低いOVHcloudマネージドKubernetesクラスターでのみ発生しました。etcdは、Kubernetesリソース(ポッドやデプロイメントなど)が保存されるデータベースです。OVHCloudは、etcd内のリソースが使用するディスクスペースを制限しています。制限に達すると、クラスター全体が不安定な動作を開始し、1つの症状として、kubeletによってエンドポイントリソースが作成されなくなります。

それが実際にクォータの問題であることを確認するには、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

回避策は、OVHCloudのETCD Quotas error, troubleshootingページで説明されているように、CertificateRequestリソースなどの一部のリソースを削除して、制限を下回ることです。

エラー: x509: certificate has expired or is not yet valid

このエラーメッセージは、Slackで1回(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回(1)報告されました。

この問題の原因をまだ特定できていないため、上記のSlackメッセージに回答してください。Kubernetes Slackにアクセスするには、https://slack.k8s.io/にアクセスしてください。

エラー: net/http: request canceled while waiting for connection

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回(1)報告されました。

エラー: context deadline exceeded

このエラーメッセージは、GitHub issue(23192706 51895004)およびStack Overflowで1回報告されています。

このエラーは、cert-managerをインストールまたはアップグレードした後、Issuerまたはその他の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

ℹ️ context deadline exceededというメッセージは、cmctl check apiを使用する場合にも表示されます。原因は同じであり、このセクションを読み進めてデバッグできます。

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 エラーメッセージの背後に隠れている可能性のある 3 つのエラーを示しています。

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|
| +-----+
+-------------------------------------------------------------------------+

このセクションの残りの部分では、3 つの「より具体的な」エラーのいずれかをトリガーしようとします。

  • i/o timeout は TCP ハンドシェイクタイムアウトであり、Kubernetes API サーバーの DialTimeout に由来します。名前解決が原因である可能性がありますが、通常、このメッセージは、API サーバーが SYN パケットを送信し、cert-manager Webhook から SYN-ACK パケットを受信するのを 10 秒間待った後に表示されます。
  • 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)を送信し、cert-manager Webhook が ServerHello パケットで応答するのを 10 秒間待った場合に発生します。

これらの 3 つのメッセージは、接続性の問題(SYN がドロップされた)か、Webhook の問題(つまり、TLS 証明書が間違っているか、Webhook が HTTP レスポンスを返していない)の 2 つのカテゴリに分類できます。

タイムアウトメッセージカテゴリ
i/o timeout接続性の問題
net/http: TLS handshake timeoutWebhook側の問題
net/http: request canceled while awaiting headersWebhook側の問題

まず、Webhook側の問題を排除します。シェルセッションで、以下を実行します。

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

別のシェルセッションで、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 であり、x509: certificate signed by unknown authorityx509: certificate has expired or is not yet valid などの apiserver 側の問題ではないため、問題は接続性の問題であると結論付けることができます。つまり、Kubernetes API サーバーは cert-manager Webhook への TCP 接続を確立できません。上記にある エラー:i/o timeout (接続性の問題) のセクションの手順に従って、デバッグを続けてください。

エラー:net/http: TLS handshake timeout

このエラーメッセージは、1 件の GitHub issue(#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 接続を終了するものではないことを意味します。おそらく、ClientHello パケットではなく、プレーンな HTTP リクエストを待機している HTTP プロキシが間にある可能性があります。

このエラーの原因は不明です。このエラーが発生した場合は、上記の GitHub issue にコメントしてください。

エラー:HTTP probe failed with statuscode: 500

このエラーメッセージは、2 件の GitHub issue(#3185#4557)で報告されました。

このエラーメッセージは、cert-manager Webhook のイベントとして表示されます。

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

このエラーの原因は不明です。このエラーが発生した場合は、上記の GitHub issue にコメントしてください。

エラー:Service Unavailable

このエラーは、1 件の GitHub issue(#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 issue にコメントしてください。

エラー:failed calling admission webhook: the server is currently unable to handle the request

この問題は、4 件の GitHub issue(1369142535424852)で報告されました。

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 issue にコメントしてください。

エラー:x509: certificate signed by unknown authority

GitHub issue(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 install および cmctl 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)

Helm で cert-manager 0.14 以下を使用しており、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 以下を使用している場合、問題は、cert-manager Webhook が作成し、Secret リソース cert-manager-webhook-ca に保存した自己署名証明書を、cert-manager CRD の spec.caBundle フィールドに挿入しようとして、cainjector がスタックしていることが原因である可能性があります。最初のステップは、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

ログを見ると、リーダー選出が機能したかどうかを判断できます。リーダー選出の完了には最大 1 分かかる場合があります。

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リソースがロードできないことを示すメッセージを探してください。表示される可能性のあるエラーメッセージは次の2つです。

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のログに問題がないようであれば、validation、mutation、conversionの設定にある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への接続を試みます。webhookポッドにポートフォワードしましょう。

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

別のシェルセッションで、次のコマンドを使用して/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 issue 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

このエラーメッセージは、Kubernetes 1.20以下のバージョンでGKE Autopilotを使用している場合に表示されます。これは、GKE Autopilotにおけるmutating admission webhookの制限によるものです。

2021年10月現在、「rapid」Autopilotリリースチャネルでは、Kubernetesマスターのバージョン1.21が展開されています。Helmチャートによるインストールはエラーメッセージで終わる可能性がありますが、cert-managerは一部のユーザーによって動作することが報告されています。フィードバックとPRを歓迎します。

エラー: the namespace "kube-system" is managed and the request's verb "create" is denied

Helmを使用してGKE Autopilotにcert-managerをインストールすると、次のエラーメッセージが表示されます。

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

この失敗の後も、3つのポッドは正常に実行されているはずです。

$ 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