mirror of
https://github.com/enix/x509-certificate-exporter.git
synced 2026-08-19 03:56:21 +00:00
Five user-facing counters had a UX hit: prometheus.NewCounterVec only
materialises a series the first time WithLabelValues(...).Inc() is
called, so an exporter running cleanly produced no series at all for
x509_source_errors_total, x509_kube_transport_errors_total,
x509_cert_collision_dropped_total and the two
x509_{pkcs12,jks}_passphrase_failures_total. Dashboards couldn't tell
'healthy' from 'metric not reporting', rate()/increase() needed two
real events to compute anything, and the chart's
SourceErrors[Sustained] / KubeTransportErrors[Sustained] alerts had
ambiguous PromQL semantics on first event.
PreInitBundleSource(kind, name) and PreInitKubeTransport(name,
resources, namespaceInformer) on Registry materialise the expected
series at zero by calling WithLabelValues without .Inc(). The static
reason sets they iterate (cert.BundleReasons, cert.KubeTransportPerResourceReasons,
cert.ReasonNamespaceSyncFail) live in pkg/cert/reason.go for a single
source of truth — dynamic 'http_NNN' reasons are deliberately
excluded since they can't be enumerated.
cmd/x509-certificate-exporter/main.go calls the right combination
for each config.Source kind right after buildSource: kubernetes
sources get both bundle init (kube-secret, kube-configmap) and
transport init (per-resource + namespace informer when label rules
require it); file/kubeconfig/cabundle sources just get the bundle
init for their respective kind. Cardinality cost is bounded by
declared reasons × source count — about 50 series per source — and
negligible against the per-cert series the exporter produces in
normal operation.
TestPreInitMaterializesZeroSeries in pkg/registry locks in the exact
series count materialised by a representative call and asserts every
emitted counter value is 0.
92 lines
3.6 KiB
Go
92 lines
3.6 KiB
Go
package cert
|
|
|
|
// Canonical reason codes for ItemError. These appear as the "reason" label
|
|
// of x509_source_errors_total, so they form a stable contract.
|
|
const (
|
|
ReasonBadPEM = "bad_pem"
|
|
ReasonBadCRL = "bad_crl"
|
|
ReasonBadDER = "bad_der"
|
|
ReasonBadJKS = "bad_jks"
|
|
ReasonNoCertificateFound = "no_certificate_found"
|
|
ReasonBadPKCS12 = "bad_pkcs12"
|
|
ReasonBadPassphrase = "bad_passphrase"
|
|
ReasonReadFailed = "read_failed"
|
|
ReasonPermissionDenied = "permission_denied"
|
|
ReasonNotFound = "not_found"
|
|
ReasonAPIError = "api_error"
|
|
ReasonDecodeFailed = "decode_failed"
|
|
ReasonBrokenSymlink = "broken_symlink"
|
|
ReasonOutOfScopeSymlink = "out_of_scope_symlink"
|
|
ReasonParseTimeout = "parse_timeout"
|
|
ReasonWalkError = "walk_error"
|
|
ReasonRateLimited = "rate_limited"
|
|
// ReasonHTTPPrefix is concatenated with the HTTP status code (e.g.
|
|
// "http_503", "http_401") to form the reason for kube-apiserver
|
|
// errors that don't map to a more specific cause.
|
|
ReasonHTTPPrefix = "http_"
|
|
|
|
// ReasonNameFilter / ReasonNamespaceFilter are not exposed as
|
|
// Prometheus reasons today — they appear only as the "reason" key
|
|
// of debug logs when an object is rejected by client-side filters.
|
|
// Centralised here so the wording stays consistent.
|
|
ReasonNameFilter = "name_filter"
|
|
ReasonNamespaceFilter = "namespace_filter"
|
|
|
|
// Kubernetes-source transport reasons. Surface as the `reason` label
|
|
// of x509_kube_transport_errors_total. Distinct from the bundle
|
|
// reasons above: these describe what went wrong below the bundle
|
|
// layer (LIST/WATCH/informer), not what went wrong parsing a
|
|
// certificate. Kept here so call sites in pkg/source/k8s and the
|
|
// alerting rules stay in lockstep on spelling.
|
|
ReasonListFailed = "list_failed"
|
|
ReasonWatchStartFailed = "watch_start_failed"
|
|
ReasonWatchErrorEvent = "watch_error_event"
|
|
ReasonWatchFlapped = "watch_flapped"
|
|
ReasonNamespaceSyncFail = "namespace_sync_failed"
|
|
)
|
|
|
|
// BundleReasons enumerates every static reason that may appear as a
|
|
// label on `x509_source_errors_total`. Used by Registry pre-init to
|
|
// materialise counter series at zero so they show up in /metrics and
|
|
// in rate()/increase() the moment the first event lands — without
|
|
// it, the absence-of-series ambiguity ("counter at 0" vs. "metric not
|
|
// reporting") leaks into dashboards and alerts.
|
|
//
|
|
// HTTP-status reasons (`http_NNN`) are deliberately excluded — they're
|
|
// dynamic and would require enumerating every code that the kube
|
|
// apiserver might surface. Alerts aggregating across `reason` (the
|
|
// pattern used by the chart's SourceErrors[Sustained] alert) still see
|
|
// the static-reason baseline series, so the alert query is well-defined
|
|
// before any error has fired.
|
|
var BundleReasons = []string{
|
|
ReasonBadPEM,
|
|
ReasonBadCRL,
|
|
ReasonBadDER,
|
|
ReasonBadJKS,
|
|
ReasonBadPKCS12,
|
|
ReasonBadPassphrase,
|
|
ReasonNoCertificateFound,
|
|
ReasonReadFailed,
|
|
ReasonPermissionDenied,
|
|
ReasonNotFound,
|
|
ReasonAPIError,
|
|
ReasonDecodeFailed,
|
|
ReasonBrokenSymlink,
|
|
ReasonOutOfScopeSymlink,
|
|
ReasonParseTimeout,
|
|
ReasonWalkError,
|
|
ReasonRateLimited,
|
|
}
|
|
|
|
// KubeTransportPerResourceReasons enumerates the static reasons that
|
|
// may appear as the `reason` label of `x509_kube_transport_errors_total`
|
|
// for `resource in {secrets, configmaps}`. The namespace-informer
|
|
// reason is separate (a single `namespace_sync_failed` value bound to
|
|
// `resource="namespaces"`); see Registry.PreInitKubeTransport.
|
|
var KubeTransportPerResourceReasons = []string{
|
|
ReasonListFailed,
|
|
ReasonWatchStartFailed,
|
|
ReasonWatchErrorEvent,
|
|
ReasonWatchFlapped,
|
|
}
|