🐛 pkg/metrics/server: use lazy cert init when cert files don't exist yet - #3568
🐛 pkg/metrics/server: use lazy cert init when cert files don't exist yet#3568ugiordan wants to merge 1 commit into
Conversation
|
Welcome @ugiordan! |
|
|
Hi @ugiordan. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Regular contributors should join the org to skip this step. Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: ugiordan The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
When SecureServing is enabled and CertDir is set, the metrics server performs a one-shot os.Stat check at listener creation time to decide whether to wire a certwatcher or fall back to a self-signed certificate. This creates a startup race: certificate provisioners (cert-controller, cert-manager, etc.) are themselves manager runnables, so they only write cert files after mgr.Start() is called, the same call that starts the metrics server listener. On first start with no pre-existing cert files the one-shot check fails, the server permanently serves a self-signed localhost certificate, and Prometheus scraping with CA-based TLS verification fails. Fix: when the cert files are absent at listener creation time, install a lazy GetCertificate callback instead of falling through to the self-signed path. The callback attempts to create the certwatcher on every TLS handshake until the files appear, then caches the watcher for subsequent calls (which go through normal certwatcher rotation logic). Once the provisioner writes the cert files the next scrape handshake succeeds. The self-signed fallback is only used when no CertDir is configured at all (or when TLSOpts already sets GetCertificate), which is unchanged. Add a unit test covering the before/after behavior. Signed-off-by: Ugo Giordano <ugiordan@redhat.com>
2a20d0e to
79f030c
Compare
|
Where did you encounter this issue?
I'm wondering how these tools would write the certificates into the manager Pod after manager startup |
The cert files arrive via a Kubernetes Secret mounted as a volume. A provisioner like cert-controller runs as a manager runnable: it generates a keypair, writes the cert data into a k8s Secret, and the kubelet then syncs that Secret to the pod's On first boot, when the Secret exists but has no cert data yet, the sequence is:
On subsequent restarts, the Secret already has cert data, so the kubelet pre-populates the volume before the container process starts, the Live cluster validationI built a minimal reproducer to confirm this on a live cluster. The binary is a single
|
Problem
When
SecureServingis enabled andCertDiris configured, the metrics server performs a one-shotos.Statcheck at listener creation time:This creates a startup race: certificate provisioners (cert-controller, cert-manager, etc.) are themselves manager runnables, so they only write cert files after
mgr.Start()is called — the same call that starts the metrics server listener. On first start with no pre-existing cert files on disk, the one-shot check fails and the server permanently serves a self-signedlocalhostcertificate. Prometheus scraping with CA-based TLS verification then fails for the lifetime of the process.After a pod restart (files already on disk from the previous run), it works correctly — which makes the bug hard to notice in practice.
Fix
When the cert files are absent at listener creation time, install a lazy
GetCertificatecallback instead of falling through to the self-signed path. The callback attempts to create the certwatcher on every TLS handshake until the files appear, then caches the watcher for subsequent calls (normal certwatcher rotation from that point on).The self-signed fallback path is unchanged: it only activates when
GetCertificateis still nil after all the above (i.e. noCertDiris configured, or the caller'sTLSOptsalready setGetCertificate).Test
Added a unit test in
pkg/metrics/server/server_test.gothat:CertDirpointing at an empty directoryPrior art
This pattern is used by operators that consume controller-runtime and work around the race themselves (e.g. via a
TLSOptscallback). This PR fixes it at the source so operators don't need the workaround.