feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -74,7 +74,7 @@ type LeaderElectionConfiguration struct {
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling bool
// enableContentionProfiling enables lock contention profiling, if
// enableContentionProfiling enables block profiling, if
// enableProfiling is true.
EnableContentionProfiling bool
}

View File

@@ -60,7 +60,7 @@ type LeaderElectionConfiguration struct {
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling *bool `json:"enableProfiling,omitempty"`
// enableContentionProfiling enables lock contention profiling, if
// enableContentionProfiling enables block profiling, if
// enableProfiling is true.
EnableContentionProfiling *bool `json:"enableContentionProfiling,omitempty"`
}

View File

@@ -17,12 +17,17 @@ limitations under the License.
package v1
import (
"errors"
"flag"
"fmt"
"io"
"math"
"os"
"strings"
"sync/atomic"
"time"
"github.com/google/go-cmp/cmp"
"github.com/spf13/pflag"
"k8s.io/klog/v2"
@@ -31,6 +36,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs/internal/setverbositylevel"
"k8s.io/component-base/logs/klogflags"
)
@@ -54,6 +60,24 @@ func NewLoggingConfiguration() *LoggingConfiguration {
return &c
}
// Applying configurations multiple times is not safe unless it's guaranteed that there
// are no goroutines which might call logging functions. The default for ValidateAndApply
// and ValidateAndApplyWithOptions is to return an error when called more than once.
// Binaries and unit tests can override that behavior.
var ReapplyHandling = ReapplyHandlingError
type ReapplyHandlingType int
const (
// ReapplyHandlingError is the default: calling ValidateAndApply or
// ValidateAndApplyWithOptions again returns an error.
ReapplyHandlingError ReapplyHandlingType = iota
// ReapplyHandlingIgnoreUnchanged silently ignores any additional calls of
// ValidateAndApply or ValidateAndApplyWithOptions if the configuration
// is unchanged, otherwise they return an error.
ReapplyHandlingIgnoreUnchanged
)
// ValidateAndApply combines validation and application of the logging configuration.
// This should be invoked as early as possible because then the rest of the program
// startup (including validation of other options) will already run with the final
@@ -61,19 +85,50 @@ func NewLoggingConfiguration() *LoggingConfiguration {
//
// The optional FeatureGate controls logging features. If nil, the default for
// these features is used.
//
// Logging options must be applied as early as possible during the program
// startup. Some changes are global and cannot be done safely when there are
// already goroutines running.
func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
return ValidateAndApplyAsField(c, featureGate, nil)
return validateAndApply(c, nil, featureGate, nil)
}
// ValidateAndApplyWithOptions is a variant of ValidateAndApply which accepts
// additional options beyond those that can be configured through the API. This
// is meant for testing.
//
// Logging options must be applied as early as possible during the program
// startup. Some changes are global and cannot be done safely when there are
// already goroutines running.
func ValidateAndApplyWithOptions(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
return validateAndApply(c, options, featureGate, nil)
}
// +k8s:deepcopy-gen=false
// LoggingOptions can be used with ValidateAndApplyWithOptions to override
// certain global defaults.
type LoggingOptions struct {
// ErrorStream can be used to override the os.Stderr default.
ErrorStream io.Writer
// InfoStream can be used to override the os.Stdout default.
InfoStream io.Writer
}
// ValidateAndApplyAsField is a variant of ValidateAndApply that should be used
// when the LoggingConfiguration is embedded in some larger configuration
// structure.
func ValidateAndApplyAsField(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
return validateAndApply(c, nil, featureGate, fldPath)
}
func validateAndApply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
errs := Validate(c, featureGate, fldPath)
if len(errs) > 0 {
return errs.ToAggregate()
}
return apply(c, featureGate)
return apply(c, options, featureGate)
}
// Validate can be used to check for invalid settings without applying them.
@@ -156,19 +211,50 @@ func featureEnabled(featureGate featuregate.FeatureGate, feature featuregate.Fea
return enabled
}
func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
contextualLoggingEnabled := contextualLoggingDefault
if featureGate != nil {
contextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
p := &parameters{
C: c,
Options: options,
ContextualLoggingEnabled: contextualLoggingDefault,
}
if featureGate != nil {
p.ContextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
}
oldP := applyParameters.Load()
if oldP != nil {
switch ReapplyHandling {
case ReapplyHandlingError:
return errors.New("logging configuration was already applied earlier, changing it is not allowed")
case ReapplyHandlingIgnoreUnchanged:
if diff := cmp.Diff(oldP, p); diff != "" {
return fmt.Errorf("the logging configuration should not be changed after setting it once (- old setting, + new setting):\n%s", diff)
}
return nil
default:
return fmt.Errorf("invalid value %d for ReapplyHandling", ReapplyHandling)
}
}
applyParameters.Store(p)
// if log format not exists, use nil loggr
format, _ := logRegistry.get(c.Format)
if format.factory == nil {
klog.ClearLogger()
} else {
log, flush := format.factory.Create(*c)
klog.SetLoggerWithOptions(log, klog.ContextualLogger(contextualLoggingEnabled), klog.FlushLogger(flush))
if options == nil {
options = &LoggingOptions{
ErrorStream: os.Stderr,
InfoStream: os.Stdout,
}
}
log, control := format.factory.Create(*c, *options)
if control.SetVerbosityLevel != nil {
setverbositylevel.Mutex.Lock()
defer setverbositylevel.Mutex.Unlock()
setverbositylevel.Callbacks = append(setverbositylevel.Callbacks, control.SetVerbosityLevel)
}
klog.SetLoggerWithOptions(log, klog.ContextualLogger(p.ContextualLoggingEnabled), klog.FlushLogger(control.Flush))
}
if err := loggingFlags.Lookup("v").Value.Set(VerbosityLevelPflag(&c.Verbosity).String()); err != nil {
return fmt.Errorf("internal error while setting klog verbosity: %v", err)
@@ -176,19 +262,87 @@ func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
return fmt.Errorf("internal error while setting klog vmodule: %v", err)
}
klog.StartFlushDaemon(c.FlushFrequency)
klog.EnableContextualLogging(contextualLoggingEnabled)
klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
klog.EnableContextualLogging(p.ContextualLoggingEnabled)
return nil
}
type parameters struct {
C *LoggingConfiguration
Options *LoggingOptions
ContextualLoggingEnabled bool
}
var applyParameters atomic.Pointer[parameters]
// ResetForTest restores the default settings. This is not thread-safe and should only
// be used when there are no goroutines running. The intended users are unit
// tests in other packages.
func ResetForTest(featureGate featuregate.FeatureGate) error {
oldP := applyParameters.Load()
if oldP == nil {
// Nothing to do.
return nil
}
// This makes it possible to call apply again without triggering errors.
applyParameters.Store(nil)
// Restore defaults. Shouldn't fail, but check anyway.
config := NewLoggingConfiguration()
if err := ValidateAndApply(config, featureGate); err != nil {
return fmt.Errorf("apply default configuration: %v", err)
}
// And again...
applyParameters.Store(nil)
return nil
}
// AddFlags adds command line flags for the configuration.
func AddFlags(c *LoggingConfiguration, fs *pflag.FlagSet) {
addFlags(c, fs)
}
// AddGoFlags is a variant of AddFlags for a standard FlagSet.
func AddGoFlags(c *LoggingConfiguration, fs *flag.FlagSet) {
addFlags(c, goFlagSet{FlagSet: fs})
}
// flagSet is the interface implemented by pflag.FlagSet, with
// just those methods defined which are needed by addFlags.
type flagSet interface {
BoolVar(p *bool, name string, value bool, usage string)
DurationVar(p *time.Duration, name string, value time.Duration, usage string)
StringVar(p *string, name string, value string, usage string)
Var(value pflag.Value, name string, usage string)
VarP(value pflag.Value, name, shorthand, usage string)
}
// goFlagSet implements flagSet for a stdlib flag.FlagSet.
type goFlagSet struct {
*flag.FlagSet
}
func (fs goFlagSet) Var(value pflag.Value, name string, usage string) {
fs.FlagSet.Var(value, name, usage)
}
func (fs goFlagSet) VarP(value pflag.Value, name, shorthand, usage string) {
// Ignore shorthand, it's not needed and not supported.
fs.FlagSet.Var(value, name, usage)
}
// addFlags can be used with both flag.FlagSet and pflag.FlagSet. The internal
// interface definition avoids duplicating this code.
func addFlags(c *LoggingConfiguration, fs flagSet) {
formats := logRegistry.list()
fs.StringVar(&c.Format, "logging-format", c.Format, fmt.Sprintf("Sets the log format. Permitted formats: %s.", formats))
// No new log formats should be added after generation is of flag options
logRegistry.freeze()
fs.DurationVar(&c.FlushFrequency, LogFlushFreqFlagName, c.FlushFrequency, "Maximum number of seconds between log flushes")
fs.DurationVar(&c.FlushFrequency.Duration.Duration, LogFlushFreqFlagName, c.FlushFrequency.Duration.Duration, "Maximum number of seconds between log flushes")
fs.VarP(VerbosityLevelPflag(&c.Verbosity), "v", "v", "number for the log level verbosity")
fs.Var(VModuleConfigurationPflag(&c.VModule), "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)")
@@ -210,8 +364,9 @@ func SetRecommendedLoggingConfiguration(c *LoggingConfiguration) {
if c.Format == "" {
c.Format = "text"
}
if c.FlushFrequency == 0 {
c.FlushFrequency = LogFlushFreqDefault
if c.FlushFrequency.Duration.Duration == 0 {
c.FlushFrequency.Duration.Duration = LogFlushFreqDefault
c.FlushFrequency.SerializeAsString = true
}
var empty resource.QuantityValue
if c.Options.JSON.InfoBufferSize == empty {

View File

@@ -36,6 +36,9 @@ type vmoduleConfigurationPFlag struct {
// String returns the -vmodule parameter (comma-separated list of pattern=N).
func (wrapper vmoduleConfigurationPFlag) String() string {
if wrapper.value == nil {
return ""
}
var patterns []string
for _, item := range *wrapper.value {
patterns = append(patterns, fmt.Sprintf("%s=%d", item.FilePattern, item.Verbosity))
@@ -82,10 +85,16 @@ type verbosityLevelPflag struct {
}
func (wrapper verbosityLevelPflag) String() string {
if wrapper.value == nil {
return "0"
}
return strconv.FormatInt(int64(*wrapper.value), 10)
}
func (wrapper verbosityLevelPflag) Get() interface{} {
if wrapper.value == nil {
return VerbosityLevel(0)
}
return *wrapper.value
}

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"sort"
"strings"
"sync"
"github.com/go-logr/logr"
@@ -30,6 +31,7 @@ var logRegistry = newLogFormatRegistry()
// logFormatRegistry stores factories for all supported logging formats.
type logFormatRegistry struct {
mutex sync.Mutex
registry map[string]logFormat
frozen bool
}
@@ -39,14 +41,29 @@ type logFormat struct {
feature featuregate.Feature
}
// +k8s:deepcopy-gen=false
// RuntimeControl provides operations that aren't available through the normal
// Logger or LogSink API.
type RuntimeControl struct {
// Flush ensures that all in-memory data is written.
// May be nil.
Flush func()
// SetVerbosityLevel changes the level for all Logger instances
// derived from the initial one. May be nil.
//
// The parameter is intentionally a plain uint32 instead of
// VerbosityLevel to enable implementations that don't need to import
// the API (helps avoid circular dependencies).
SetVerbosityLevel func(v uint32) error
}
// LogFormatFactory provides support for a certain additional,
// non-default log format.
type LogFormatFactory interface {
// Create returns a logger with the requested configuration.
// Returning a flush function for the logger is optional.
// If provided, the caller must ensure that it is called
// periodically (if desired) and at program exit.
Create(c LoggingConfiguration) (log logr.Logger, flush func())
Create(c LoggingConfiguration, o LoggingOptions) (logr.Logger, RuntimeControl)
}
// RegisterLogFormat registers support for a new logging format. This must be called
@@ -68,6 +85,8 @@ func newLogFormatRegistry() *logFormatRegistry {
// register adds a new log format. It's an error to modify an existing one.
func (lfr *logFormatRegistry) register(name string, format logFormat) error {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
if lfr.frozen {
return fmt.Errorf("log format registry is frozen, unable to register log format %s", name)
}
@@ -83,6 +102,8 @@ func (lfr *logFormatRegistry) register(name string, format logFormat) error {
// get specified log format factory
func (lfr *logFormatRegistry) get(name string) (*logFormat, error) {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
format, ok := lfr.registry[name]
if !ok {
return nil, fmt.Errorf("log format: %s does not exists", name)
@@ -92,6 +113,8 @@ func (lfr *logFormatRegistry) get(name string) (*logFormat, error) {
// list names of registered log formats, including feature gates (sorted)
func (lfr *logFormatRegistry) list() string {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
formats := make([]string, 0, len(lfr.registry))
for name, format := range lfr.registry {
item := fmt.Sprintf(`"%s"`, name)
@@ -106,5 +129,7 @@ func (lfr *logFormatRegistry) list() string {
// freeze prevents further modifications of the registered log formats.
func (lfr *logFormatRegistry) freeze() {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
lfr.frozen = true
}

View File

@@ -17,9 +17,11 @@ limitations under the License.
package v1
import (
"time"
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Supported output formats.
@@ -39,10 +41,11 @@ type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string `json:"format,omitempty"`
// Maximum number of nanoseconds (i.e. 1s = 1000000000) between log
// flushes. Ignored if the selected logging backend writes log
// messages without buffering.
FlushFrequency time.Duration `json:"flushFrequency"`
// Maximum time between log flushes.
// If a string, parsed as a duration (i.e. "1s")
// If an int, the maximum number of nanoseconds (i.e. 1s = 1000000000).
// Ignored if the selected logging backend writes log messages without buffering.
FlushFrequency TimeOrMetaDuration `json:"flushFrequency"`
// Verbosity is the threshold that determines which log messages are
// logged. Default is zero which logs only the most important
// messages. Higher values enable additional messages. Error messages
@@ -58,6 +61,37 @@ type LoggingConfiguration struct {
Options FormatOptions `json:"options,omitempty"`
}
// TimeOrMetaDuration is present only for backwards compatibility for the
// flushFrequency field, and new fields should use metav1.Duration.
type TimeOrMetaDuration struct {
// Duration holds the duration
Duration metav1.Duration
// SerializeAsString controls whether the value is serialized as a string or an integer
SerializeAsString bool `json:"-"`
}
func (t TimeOrMetaDuration) MarshalJSON() ([]byte, error) {
if t.SerializeAsString {
return t.Duration.MarshalJSON()
} else {
// Marshal as integer for backwards compatibility
return json.Marshal(t.Duration.Duration)
}
}
func (t *TimeOrMetaDuration) UnmarshalJSON(b []byte) error {
if len(b) > 0 && b[0] == '"' {
// string values unmarshal as metav1.Duration
t.SerializeAsString = true
return json.Unmarshal(b, &t.Duration)
}
t.SerializeAsString = false
if err := json.Unmarshal(b, &t.Duration.Duration); err != nil {
return fmt.Errorf("invalid duration %q: %w", string(b), err)
}
return nil
}
// FormatOptions contains options for the different logging formats.
type FormatOptions struct {
// [Alpha] JSON contains options for logging format "json".

View File

@@ -58,6 +58,7 @@ func (in *JSONOptions) DeepCopy() *JSONOptions {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in
out.FlushFrequency = in.FlushFrequency
if in.VModule != nil {
in, out := &in.VModule, &out.VModule
*out = make(VModuleConfiguration, len(*in))
@@ -77,6 +78,23 @@ func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TimeOrMetaDuration) DeepCopyInto(out *TimeOrMetaDuration) {
*out = *in
out.Duration = in.Duration
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeOrMetaDuration.
func (in *TimeOrMetaDuration) DeepCopy() *TimeOrMetaDuration {
if in == nil {
return nil
}
out := new(TimeOrMetaDuration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in VModuleConfiguration) DeepCopyInto(out *VModuleConfiguration) {
{

View File

@@ -0,0 +1,34 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package setverbositylevel stores callbacks that will be invoked by logs.GlogLevel.
//
// This is a separate package to avoid a dependency from
// k8s.io/component-base/logs (uses the callbacks) to
// k8s.io/component-base/logs/api/v1 (adds them). Not all users of the logs
// package also use the API.
package setverbositylevel
import (
"sync"
)
var (
// Mutex controls access to the callbacks.
Mutex sync.Mutex
Callbacks []func(v uint32) error
)

View File

@@ -23,10 +23,12 @@ import (
"flag"
"fmt"
"log"
"strconv"
"time"
"github.com/spf13/pflag"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/logs/internal/setverbositylevel"
"k8s.io/component-base/logs/klogflags"
"k8s.io/klog/v2"
)
@@ -182,11 +184,26 @@ func NewLogger(prefix string) *log.Logger {
return log.New(KlogWriter{}, prefix, 0)
}
// GlogSetter is a setter to set glog level.
// GlogSetter modifies the verbosity threshold for the entire program.
// Some components have HTTP-based APIs for invoking this at runtime.
func GlogSetter(val string) (string, error) {
v, err := strconv.ParseUint(val, 10, 32)
if err != nil {
return "", err
}
var level klog.Level
if err := level.Set(val); err != nil {
return "", fmt.Errorf("failed set klog.logging.verbosity %s: %v", val, err)
}
setverbositylevel.Mutex.Lock()
defer setverbositylevel.Mutex.Unlock()
for _, cb := range setverbositylevel.Callbacks {
if err := cb(uint32(v)); err != nil {
return "", err
}
}
return fmt.Sprintf("successfully set klog.logging.verbosity to %s", val), nil
}

View File

@@ -33,6 +33,16 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
return prometheus.ExponentialBuckets(start, factor, count)
}
// ExponentialBucketsRange creates 'count' buckets, where the lowest bucket is
// 'min' and the highest bucket is 'max'. The final +Inf bucket is not counted
// and not included in the returned slice. The returned slice is meant to be
// used for the Buckets field of HistogramOpts.
//
// The function panics if 'count' is 0 or negative, if 'min' is 0 or negative.
func ExponentialBucketsRange(min, max float64, count int) []float64 {
return prometheus.ExponentialBucketsRange(min, max, count)
}
// MergeBuckets merges buckets together
func MergeBuckets(buckets ...[]float64) []float64 {
result := make([]float64, 1)

View File

@@ -29,7 +29,7 @@ const (
func featureGates() map[featuregate.Feature]featuregate.FeatureSpec {
return map[featuregate.Feature]featuregate.FeatureSpec{
ComponentSLIs: {Default: false, PreRelease: featuregate.Alpha},
ComponentSLIs: {Default: true, PreRelease: featuregate.Beta},
}
}

View File

@@ -19,19 +19,28 @@ package metrics
import (
"io"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
processStartedAt time.Time
)
func init() {
processStartedAt = time.Now()
}
// These constants cause handlers serving metrics to behave as described if
// errors are encountered.
const (
// Serve an HTTP status code 500 upon the first error
// HTTPErrorOnError serve an HTTP status code 500 upon the first error
// encountered. Report the error message in the body.
HTTPErrorOnError promhttp.HandlerErrorHandling = iota
// Ignore errors and try to serve as many metrics as possible. However,
// if no metrics can be served, serve an HTTP status code 500 and the
// ContinueOnError ignore errors and try to serve as many metrics as possible.
// However, if no metrics can be served, serve an HTTP status code 500 and the
// last error message in the body. Only use this in deliberate "best
// effort" metrics collection scenarios. In this case, it is highly
// recommended to provide other means of detecting errors: By setting an
@@ -41,7 +50,7 @@ const (
// alerts.
ContinueOnError
// Panic upon the first error encountered (useful for "crash only" apps).
// PanicOnError panics upon the first error encountered (useful for "crash only" apps).
PanicOnError
)
@@ -50,6 +59,7 @@ const (
type HandlerOpts promhttp.HandlerOpts
func (ho *HandlerOpts) toPromhttpHandlerOpts() promhttp.HandlerOpts {
ho.ProcessStartTime = processStartedAt
return promhttp.HandlerOpts(*ho)
}

View File

@@ -18,6 +18,7 @@ package legacyregistry
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
@@ -42,19 +43,25 @@ var (
// Register registers a collectable metric but uses the global registry
Register = defaultRegistry.Register
// Registerer exposes the global registerer
Registerer = defaultRegistry.Registerer
processStart time.Time
)
func init() {
RawMustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
RawMustRegister(collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll)))
defaultRegistry.RegisterMetaMetrics()
processStart = time.Now()
}
// Handler returns an HTTP handler for the DefaultGatherer. It is
// already instrumented with InstrumentHandler (using "prometheus" as handler
// name).
func Handler() http.Handler {
return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(defaultRegistry, promhttp.HandlerOpts{}))
return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(defaultRegistry, promhttp.HandlerOpts{ProcessStartTime: processStart}))
}
// HandlerWithReset returns an HTTP handler for the DefaultGatherer but invokes
@@ -62,7 +69,7 @@ func Handler() http.Handler {
func HandlerWithReset() http.Handler {
return promhttp.InstrumentMetricHandler(
prometheus.DefaultRegisterer,
metrics.HandlerWithReset(defaultRegistry, metrics.HandlerOpts{}))
metrics.HandlerWithReset(defaultRegistry, metrics.HandlerOpts{ProcessStartTime: processStart}))
}
// CustomRegister registers a custom collector but uses the global registry.

View File

@@ -97,9 +97,8 @@ func (r *lazyMetric) lazyInit(self kubeCollector, fqName string) {
// 2. if the metric is manually disabled via a CLI flag.
//
// Disclaimer: disabling a metric via a CLI flag has higher precedence than
//
// deprecation and will override show-hidden-metrics for the explicitly
// disabled metric.
// deprecation and will override show-hidden-metrics for the explicitly
// disabled metric.
func (r *lazyMetric) preprocessMetric(version semver.Version) {
disabledMetricsLock.RLock()
defer disabledMetricsLock.RUnlock()
@@ -167,7 +166,7 @@ func (r *lazyMetric) Create(version *semver.Version) bool {
if deprecatedV != nil {
dv = deprecatedV.String()
}
registeredMetrics.WithLabelValues(string(sl), dv).Inc()
registeredMetricsTotal.WithLabelValues(string(sl), dv).Inc()
return r.IsCreated()
}
@@ -216,7 +215,6 @@ var noopCounterVec = &prometheus.CounterVec{}
var noopHistogramVec = &prometheus.HistogramVec{}
var noopTimingHistogramVec = &promext.TimingHistogramVec{}
var noopGaugeVec = &prometheus.GaugeVec{}
var noopObserverVec = &noopObserverVector{}
// just use a convenience struct for all the no-ops
var noop = &noopMetric{}
@@ -235,22 +233,3 @@ func (noopMetric) Desc() *prometheus.Desc { return nil }
func (noopMetric) Write(*dto.Metric) error { return nil }
func (noopMetric) Describe(chan<- *prometheus.Desc) {}
func (noopMetric) Collect(chan<- prometheus.Metric) {}
type noopObserverVector struct{}
func (noopObserverVector) GetMetricWith(prometheus.Labels) (prometheus.Observer, error) {
return noop, nil
}
func (noopObserverVector) GetMetricWithLabelValues(...string) (prometheus.Observer, error) {
return noop, nil
}
func (noopObserverVector) With(prometheus.Labels) prometheus.Observer { return noop }
func (noopObserverVector) WithLabelValues(...string) prometheus.Observer { return noop }
func (noopObserverVector) CurryWith(prometheus.Labels) (prometheus.ObserverVec, error) {
return noopObserverVec, nil
}
func (noopObserverVector) MustCurryWith(prometheus.Labels) prometheus.ObserverVec {
return noopObserverVec
}
func (noopObserverVector) Describe(chan<- *prometheus.Desc) {}
func (noopObserverVector) Collect(chan<- prometheus.Metric) {}

View File

@@ -31,6 +31,7 @@ type Options struct {
ShowHiddenMetricsForVersion string
DisabledMetrics []string
AllowListMapping map[string]string
AllowListMappingManifest string
}
// NewOptions returns default metrics options
@@ -40,6 +41,10 @@ func NewOptions() *Options {
// Validate validates metrics flags options.
func (o *Options) Validate() []error {
if o == nil {
return nil
}
var errs []error
err := validateShowHiddenMetricsVersion(parseVersion(version.Get()), o.ShowHiddenMetricsForVersion)
if err != nil {
@@ -77,6 +82,10 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
"The map from metric-label to value allow-list of this label. The key's format is <MetricName>,<LabelName>. "+
"The value's format is <allowed_value>,<allowed_value>..."+
"e.g. metric1,label1='v1,v2,v3', metric1,label2='v1,v2,v3' metric2,label1='v1,v2,v3'.")
fs.StringVar(&o.AllowListMappingManifest, "allow-metric-labels-manifest", o.AllowListMappingManifest,
"The path to the manifest file that contains the allow-list mapping. "+
"The format of the file is the same as the flag --allow-metric-labels. "+
"Note that the flag --allow-metric-labels will override the manifest file.")
}
// Apply applies parameters into global configuration of metrics.
@@ -93,6 +102,8 @@ func (o *Options) Apply() {
}
if o.AllowListMapping != nil {
SetLabelAllowListFromCLI(o.AllowListMapping)
} else if len(o.AllowListMappingManifest) > 0 {
SetLabelAllowListFromManifest(o.AllowListMappingManifest)
}
}
@@ -118,7 +129,7 @@ func validateAllowMetricLabel(allowListMapping map[string]string) error {
for k := range allowListMapping {
reg := regexp.MustCompile(metricNameRegex + `,` + labelRegex)
if reg.FindString(k) != k {
return fmt.Errorf("--allow-metric-labels must has a list of kv pair with format `metricName:labelName=labelValue, labelValue,...`")
return fmt.Errorf("--allow-metric-labels must have a list of kv pair with format `metricName:labelName=labelValue, labelValue,...`")
}
}
return nil

View File

@@ -18,13 +18,18 @@ package metrics
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/util/sets"
promext "k8s.io/component-base/metrics/prometheusextension"
"k8s.io/klog/v2"
)
var (
@@ -319,6 +324,7 @@ func (allowList *MetricLabelAllowList) ConstrainToAllowedList(labelNameList, lab
if allowValues, ok := allowList.labelToAllowList[name]; ok {
if !allowValues.Has(value) {
labelValueList[index] = "unexpected"
cardinalityEnforcementUnexpectedCategorizationsTotal.Inc()
}
}
}
@@ -329,6 +335,7 @@ func (allowList *MetricLabelAllowList) ConstrainLabelMap(labels map[string]strin
if allowValues, ok := allowList.labelToAllowList[name]; ok {
if !allowValues.Has(value) {
labels[name] = "unexpected"
cardinalityEnforcementUnexpectedCategorizationsTotal.Inc()
}
}
}
@@ -354,3 +361,20 @@ func SetLabelAllowListFromCLI(allowListMapping map[string]string) {
}
}
}
func SetLabelAllowListFromManifest(manifest string) {
allowListLock.Lock()
defer allowListLock.Unlock()
allowListMapping := make(map[string]string)
data, err := os.ReadFile(filepath.Clean(manifest))
if err != nil {
klog.Errorf("Failed to read allow list manifest: %v", err)
return
}
err = yaml.Unmarshal(data, &allowListMapping)
if err != nil {
klog.Errorf("Failed to parse allow list manifest: %v", err)
return
}
SetLabelAllowListFromCLI(allowListMapping)
}

View File

@@ -30,7 +30,7 @@ var (
Namespace: "kubernetes",
Name: "feature_enabled",
Help: "This metric records the data about the stage and enablement of a k8s feature.",
StabilityLevel: k8smetrics.ALPHA,
StabilityLevel: k8smetrics.BETA,
},
[]string{"name", "stage"},
)

View File

@@ -37,7 +37,7 @@ var (
Namespace: "kubernetes",
Name: "healthcheck",
Help: "This metric records the result of a single healthcheck.",
StabilityLevel: k8smetrics.ALPHA,
StabilityLevel: k8smetrics.STABLE,
},
[]string{"name", "type"},
)
@@ -48,7 +48,7 @@ var (
Namespace: "kubernetes",
Name: "healthchecks_total",
Help: "This metric records the results of all healthcheck.",
StabilityLevel: k8smetrics.ALPHA,
StabilityLevel: k8smetrics.STABLE,
},
[]string{"name", "type", "status"},
)
@@ -57,6 +57,7 @@ var (
func Register(registry k8smetrics.KubeRegistry) {
registry.Register(healthcheck)
registry.Register(healthchecksTotal)
_ = k8smetrics.RegisterProcessStartTime(registry.Register)
}
func ResetHealthMetrics() {

View File

@@ -37,27 +37,35 @@ var (
registriesLock sync.RWMutex
disabledMetrics = map[string]struct{}{}
registeredMetrics = NewCounterVec(
registeredMetricsTotal = NewCounterVec(
&CounterOpts{
Name: "registered_metric_total",
Name: "registered_metrics_total",
Help: "The count of registered metrics broken by stability level and deprecation version.",
StabilityLevel: ALPHA,
StabilityLevel: BETA,
},
[]string{"stability_level", "deprecated_version"},
)
disabledMetricsTotal = NewCounter(
&CounterOpts{
Name: "disabled_metric_total",
Name: "disabled_metrics_total",
Help: "The count of disabled metrics.",
StabilityLevel: ALPHA,
StabilityLevel: BETA,
},
)
hiddenMetricsTotal = NewCounter(
&CounterOpts{
Name: "hidden_metric_total",
Name: "hidden_metrics_total",
Help: "The count of hidden metrics.",
StabilityLevel: BETA,
},
)
cardinalityEnforcementUnexpectedCategorizationsTotal = NewCounter(
&CounterOpts{
Name: "cardinality_enforcement_unexpected_categorizations_total",
Help: "The count of unexpected categorizations during cardinality enforcement.",
StabilityLevel: ALPHA,
},
)
@@ -379,7 +387,8 @@ func NewKubeRegistry() KubeRegistry {
}
func (r *kubeRegistry) RegisterMetaMetrics() {
r.MustRegister(registeredMetrics)
r.MustRegister(registeredMetricsTotal)
r.MustRegister(disabledMetricsTotal)
r.MustRegister(hiddenMetricsTotal)
r.MustRegister(cardinalityEnforcementUnexpectedCategorizationsTotal)
}

View File

@@ -19,11 +19,13 @@ package testutil
import (
"fmt"
"io"
"testing"
"github.com/prometheus/client_golang/prometheus/testutil"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
// CollectAndCompare registers the provided Collector with a newly created
@@ -67,6 +69,13 @@ func CustomCollectAndCompare(c metrics.StableCollector, expected io.Reader, metr
return GatherAndCompare(registry, expected, metricNames...)
}
// ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
// plain text format. Then it compares it with the results that the `expected` would return.
// If the `metricNames` is not empty it would filter the comparison only to the given metric names.
func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) error {
return testutil.ScrapeAndCompare(url, expected, metricNames...)
}
// NewFakeKubeRegistry creates a fake `KubeRegistry` that takes the input version as `build in version`.
// It should only be used in testing scenario especially for the deprecated metrics.
// The input version format should be `major.minor.patch`, e.g. '1.18.0'.
@@ -84,3 +93,62 @@ func NewFakeKubeRegistry(ver string) metrics.KubeRegistry {
return metrics.NewKubeRegistry()
}
func AssertVectorCount(t *testing.T, name string, labelFilter map[string]string, wantCount int) {
metrics, err := legacyregistry.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
counterSum := 0
for _, mf := range metrics {
if mf.GetName() != name {
continue // Ignore other metrics.
}
for _, metric := range mf.GetMetric() {
if !LabelsMatch(metric, labelFilter) {
continue
}
counterSum += int(metric.GetCounter().GetValue())
}
}
if wantCount != counterSum {
t.Errorf("Wanted count %d, got %d for metric %s with labels %#+v", wantCount, counterSum, name, labelFilter)
for _, mf := range metrics {
if mf.GetName() == name {
for _, metric := range mf.GetMetric() {
t.Logf("\tnear match: %s", metric.String())
}
}
}
}
}
func AssertHistogramTotalCount(t *testing.T, name string, labelFilter map[string]string, wantCount int) {
metrics, err := legacyregistry.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
counterSum := 0
for _, mf := range metrics {
if mf.GetName() != name {
continue // Ignore other metrics.
}
for _, metric := range mf.GetMetric() {
if !LabelsMatch(metric, labelFilter) {
continue
}
counterSum += int(metric.GetHistogram().GetSampleCount())
}
}
if wantCount != counterSum {
t.Errorf("Wanted count %d, got %d for metric %s with labels %#+v", wantCount, counterSum, name, labelFilter)
for _, mf := range metrics {
if mf.GetName() == name {
for _, metric := range mf.GetMetric() {
t.Logf("\tnear match: %s\n", metric.String())
}
}
}
}
}

View File

@@ -60,8 +60,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
// NewLazyMetricWithTimestamp is a helper of NewMetricWithTimestamp.
//
// Warning: the Metric 'm' must be the one created by NewLazyConstMetric(),
//
// otherwise, no stability guarantees would be offered.
// otherwise, no stability guarantees would be offered.
func NewLazyMetricWithTimestamp(t time.Time, m Metric) Metric {
if m == nil {
return nil

View File

@@ -68,6 +68,12 @@ func (s *Span) End(logThreshold time.Duration) {
}
}
// RecordError will record err as an exception span event for this span.
// If this span is not being recorded or err is nil then this method does nothing.
func (s *Span) RecordError(err error, attributes ...attribute.KeyValue) {
s.otelSpan.RecordError(err, trace.WithAttributes(attributes...))
}
func attributesToFields(attributes []attribute.KeyValue) []utiltrace.Field {
fields := make([]utiltrace.Field, len(attributes))
for i := range attributes {

View File

@@ -25,6 +25,7 @@ import (
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
oteltrace "go.opentelemetry.io/otel/trace"
"k8s.io/client-go/transport"
@@ -95,9 +96,17 @@ func WithTracing(handler http.Handler, tp oteltrace.TracerProvider, serviceName
otelhttp.WithPropagators(Propagators()),
otelhttp.WithTracerProvider(tp),
}
wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add the http.target attribute to the otelhttp span
// Workaround for https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3743
if r.URL != nil {
oteltrace.SpanFromContext(r.Context()).SetAttributes(semconv.HTTPTarget(r.URL.RequestURI()))
}
handler.ServeHTTP(w, r)
})
// With Noop TracerProvider, the otelhttp still handles context propagation.
// See https://github.com/open-telemetry/opentelemetry-go/tree/main/example/passthrough
return otelhttp.NewHandler(handler, serviceName, opts...)
return otelhttp.NewHandler(wrappedHandler, serviceName, opts...)
}
// WrapperFor can be used to add tracing to a *rest.Config.

77
vendor/k8s.io/component-base/version/dynamic.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package version
import (
"fmt"
"sync/atomic"
utilversion "k8s.io/apimachinery/pkg/util/version"
)
var dynamicGitVersion atomic.Value
func init() {
// initialize to static gitVersion
dynamicGitVersion.Store(gitVersion)
}
// SetDynamicVersion overrides the version returned as the GitVersion from Get().
// The specified version must be non-empty, a valid semantic version, and must
// match the major/minor/patch version of the default gitVersion.
func SetDynamicVersion(dynamicVersion string) error {
if err := ValidateDynamicVersion(dynamicVersion); err != nil {
return err
}
dynamicGitVersion.Store(dynamicVersion)
return nil
}
// ValidateDynamicVersion ensures the given version is non-empty, a valid semantic version,
// and matched the major/minor/patch version of the default gitVersion.
func ValidateDynamicVersion(dynamicVersion string) error {
return validateDynamicVersion(dynamicVersion, gitVersion)
}
func validateDynamicVersion(dynamicVersion, defaultVersion string) error {
if len(dynamicVersion) == 0 {
return fmt.Errorf("version must not be empty")
}
if dynamicVersion == defaultVersion {
// allow no-op
return nil
}
vRuntime, err := utilversion.ParseSemantic(dynamicVersion)
if err != nil {
return err
}
// must match major/minor/patch of default version
var vDefault *utilversion.Version
if defaultVersion == "v0.0.0-master+$Format:%H$" {
// special-case the placeholder value which doesn't parse as a semantic version
vDefault, err = utilversion.ParseSemantic("v0.0.0-master")
} else {
vDefault, err = utilversion.ParseSemantic(defaultVersion)
}
if err != nil {
return err
}
if vRuntime.Major() != vDefault.Major() || vRuntime.Minor() != vDefault.Minor() || vRuntime.Patch() != vDefault.Patch() {
return fmt.Errorf("version %q must match major/minor/patch of default version %q", dynamicVersion, defaultVersion)
}
return nil
}

View File

@@ -31,7 +31,7 @@ func Get() apimachineryversion.Info {
return apimachineryversion.Info{
Major: gitMajor,
Minor: gitMinor,
GitVersion: gitVersion,
GitVersion: dynamicGitVersion.Load().(string),
GitCommit: gitCommit,
GitTreeState: gitTreeState,
BuildDate: buildDate,