defer metric registration to runtime entry point via constructors

Signed-off-by: Richa Banker <richabanker@google.com>

Kubernetes-commit: 8721c830cf013a92a805b46eb163736f97aba2aa
This commit is contained in:
Richa Banker
2026-05-08 13:37:32 -07:00
committed by Kubernetes Publisher
parent 68262a1cf9
commit 1d2651da7d
6 changed files with 51 additions and 1 deletions

View File

@@ -159,6 +159,7 @@ func (s *sometimes) Do(f func()) {
// GetAuthenticator returns an exec-based plugin for providing client credentials.
func GetAuthenticator(config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) {
metrics.EnsureRegistered()
return newAuthenticator(globalCache, term.IsTerminal, config, cluster)
}

View File

@@ -32,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
clientfeatures "k8s.io/client-go/features"
"k8s.io/client-go/tools/metrics"
"k8s.io/client-go/util/flowcontrol"
)
@@ -110,6 +111,8 @@ type RESTClient struct {
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths.
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ClientContentConfig, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
metrics.EnsureRegistered()
base := *baseURL
if !strings.HasSuffix(base.Path, "/") {
base.Path += "/"

View File

@@ -37,6 +37,7 @@ import (
"k8s.io/client-go/features"
"k8s.io/client-go/pkg/version"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/metrics"
"k8s.io/client-go/transport"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/flowcontrol"
@@ -355,6 +356,8 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
// Note that the http client takes precedence over the transport values configured.
// The http client defaults to the `http.DefaultClient` if nil.
func RESTClientForConfigAndClient(config *Config, httpClient *http.Client) (*RESTClient, error) {
metrics.EnsureRegistered()
if config.GroupVersion == nil {
return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
}

View File

@@ -23,6 +23,7 @@ import (
"k8s.io/client-go/pkg/apis/clientauthentication"
"k8s.io/client-go/plugin/pkg/client/auth/exec"
"k8s.io/client-go/tools/metrics"
"k8s.io/client-go/transport"
)
@@ -82,6 +83,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
// TransportConfig converts a client config to an appropriate transport config.
func (c *Config) TransportConfig() (*transport.Config, error) {
metrics.EnsureRegistered()
conf := &transport.Config{
UserAgent: c.UserAgent,
Transport: c.Transport,

View File

@@ -25,7 +25,37 @@ import (
"time"
)
var registerMetrics sync.Once
var (
registerMetrics sync.Once
ensureRegisteredOnce sync.Once
// ensureRegisteredFn, if set via RegisterOpts.RegisterFn, is invoked
// exactly once before any rest client is constructed. Adapter packages
// (e.g. k8s.io/component-base/metrics/prometheus/restclient) install
// this callback in their init() so that the actual registration with
// legacyregistry — and the metric Create() that reads
// feature-gate-derived options like NativeHistograms — happens at
// runtime rather than at init() time. See EnsureRegistered for the
// caller-side contract.
ensureRegisteredFn func()
)
// EnsureRegistered invokes the callback installed via RegisterOpts.RegisterFn (if
// any) exactly once. Callers should treat it as idempotent; subsequent calls are
// effectively free.
//
// New public constructors or entry points for packages in client-go that create
// a REST client, HTTP transport, or credential provider should invoke EnsureRegistered()
// at the very beginning of the function. Adapter Observe methods
// also call EnsureRegistered(), so no observations are lost if a constructor
// forgets to invoke it, but if invoked, the entrypoint call shifts registration from
// "first-observation" to "first client construction", meaning the metric
// series is visible to Promteheus scrapes from process startup rather than
// appearing only after the first request.
func EnsureRegistered() {
if ensureRegisteredFn != nil {
ensureRegisteredOnce.Do(ensureRegisteredFn)
}
}
// DurationMetric is a measurement of some amount of time.
type DurationMetric interface {
@@ -163,6 +193,12 @@ type RegisterOpts struct {
TransportCAReloads TransportCAReloadsMetric
TransportCertRotationGCCalls TransportCertRotationGCCallsMetric
TransportCacheGCCalls TransportCacheGCCallsMetric
// RegisterFn, if non-nil, is invoked exactly once by EnsureRegistered().
// before the first rest client is constructed. Adapters use this to defer
// registrations that depend on runtime state (eg., feature gates read my metric
// Create() without changing the import side contract of the adapter package.)
RegisterFn func()
}
// Register registers metrics for the rest client to use. This can
@@ -217,6 +253,9 @@ func Register(opts RegisterOpts) {
if opts.TransportCacheGCCalls != nil {
TransportCacheGCCalls = opts.TransportCacheGCCalls
}
if opts.RegisterFn != nil {
ensureRegisteredFn = opts.RegisterFn
}
})
}

View File

@@ -29,12 +29,14 @@ import (
utilnet "k8s.io/apimachinery/pkg/util/net"
clientgofeaturegate "k8s.io/client-go/features"
"k8s.io/client-go/tools/metrics"
"k8s.io/klog/v2"
)
// New returns an http.RoundTripper that will provide the authentication
// or transport level security defined by the provided Config.
func New(config *Config) (http.RoundTripper, error) {
metrics.EnsureRegistered()
// Set transport level security
if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.HasCertCallback() || config.TLS.Insecure) {
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")