1
0
mirror of https://github.com/coreos/prometheus-operator.git synced 2026-02-05 15:46:31 +01:00

feat: add prometheus_operator_feature_gate_info metric

This change also moves the feature gates to the operator config struct.
It means that after a feature gate is enabled/disabled, the operator
will reconcile the managed Prometheus resources which should be the
right thing to do.

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
This commit is contained in:
Simon Pasquier
2024-06-06 15:16:47 +02:00
parent 7c17d9cc92
commit 704206b716
5 changed files with 169 additions and 62 deletions

View File

@@ -52,7 +52,9 @@ Usage of ./operator:
-enable-config-reloader-probes
Enable liveness and readiness for the config-reloader container. Default: false
-feature-gates value
Feature gates are a set of key=value pairs that describe Prometheus-Operator features. Available features: ["PrometheusAgentDaemonSet"].
Feature gates are a set of key=value pairs that describe Prometheus-Operator features.
Available feature gates:
PrometheusAgentDaemonSet: Enables the DaemonSet mode for PrometheusAgent (enabled: false)
-key-file string
- NOT RECOMMENDED FOR PRODUCTION - Path to private TLS certificate file.
-kubelet-node-address-priority value

View File

@@ -120,7 +120,7 @@ var (
kubeletSelector operator.LabelSelector
nodeAddressPriority operator.NodeAddressPriority
featureGates *k8sflag.MapStringBool
featureGates = k8sflag.NewMapStringBool(ptr.To(map[string]bool{}))
)
func parseFlags(fs *flag.FlagSet) {
@@ -173,11 +173,9 @@ func parseFlags(fs *flag.FlagSet) {
fs.Var(&cfg.ThanosRulerSelector, "thanos-ruler-instance-selector", "Label selector to filter ThanosRuler Custom Resources to watch.")
fs.Var(&cfg.SecretListWatchSelector, "secret-field-selector", "Field selector to filter Secrets to watch")
// Auto GOMEMLIMIT Ratio
fs.Float64Var(&memlimitRatio, "auto-gomemlimit-ratio", defaultMemlimitRatio, "The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory. The value should be greater than 0.0 and less than 1.0. Default: 0.0 (disabled).")
featureGates = k8sflag.NewMapStringBool(ptr.To(make(map[string]bool)))
fs.Var(featureGates, "feature-gates", fmt.Sprintf("Feature gates are a set of key=value pairs that describe Prometheus-Operator features. Available features: %q.", operator.AvailableFeatureGates()))
cfg.RegisterFeatureGatesFlags(fs, featureGates)
logging.RegisterFlags(fs, &logConfig)
versionutil.RegisterFlags(fs)
@@ -199,17 +197,14 @@ func run(fs *flag.FlagSet) int {
stdlog.Fatal(err)
}
gates, err := operator.ValidateFeatureGates(featureGates)
if err != nil {
level.Error(logger).Log(
"msg", "error validating feature gates",
"error", err)
if err := cfg.Gates.UpdateFeatureGates(*featureGates.Map); err != nil {
level.Error(logger).Log("error", err)
return 1
}
level.Info(logger).Log("msg", "Starting Prometheus Operator", "version", version.Info())
level.Info(logger).Log("build_context", version.BuildContext())
level.Info(logger).Log("feature_gates", gates)
level.Info(logger).Log("feature_gates", cfg.Gates.String())
goruntime.SetMaxProcs(logger)
goruntime.SetMemLimit(logger, memlimitRatio)
@@ -509,6 +504,7 @@ func run(fs *flag.FlagSet) int {
),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
versioncollector.NewCollector("prometheus_operator"),
cfg.Gates,
)
mux.Handle("/metrics", promhttp.HandlerFor(r, promhttp.HandlerOpts{}))

View File

@@ -15,6 +15,7 @@
package operator
import (
"flag"
"fmt"
"slices"
"sort"
@@ -26,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/version"
k8sflag "k8s.io/component-base/cli/flag"
)
// Config defines configuration parameters for the Operator.
@@ -60,8 +62,11 @@ type Config struct {
ThanosRulerSelector LabelSelector
SecretListWatchSelector FieldSelector
// Controller id for pod ownership
// Controller id for pod ownership.
ControllerID string
// Feature gates.
Gates *FeatureGates
}
// DefaultConfig returns a default operator configuration.
@@ -81,9 +86,25 @@ func DefaultConfig(cpu, memory string) Config {
AlertmanagerConfigAllowList: StringSet{},
ThanosRulerAllowList: StringSet{},
},
Gates: &FeatureGates{
PrometheusAgentDaemonSetFeature: FeatureGate{
description: "Enables the DaemonSet mode for PrometheusAgent",
enabled: false,
},
},
}
}
func (c *Config) RegisterFeatureGatesFlags(fs *flag.FlagSet, flags *k8sflag.MapStringBool) {
fs.Var(
flags,
"feature-gates",
fmt.Sprintf("Feature gates are a set of key=value pairs that describe Prometheus-Operator features.\n"+
"Available feature gates:\n %s", strings.Join(c.Gates.Descriptions(), "\n "),
),
)
}
// ContainerConfig holds some configuration for the ConfigReloader sidecar
// that can be set through prometheus-operator command line arguments.
type ContainerConfig struct {

View File

@@ -19,47 +19,113 @@ import (
"slices"
"strings"
k8sflag "k8s.io/component-base/cli/flag"
"github.com/prometheus/client_golang/prometheus"
)
// At the moment, the are no feature gates available.
var defaultFeatureGates = map[string]bool{
"PrometheusAgentDaemonSet": false,
const (
// PrometheusAgentDaemonSetFeature enables the DaemonSet mode for PrometheusAgent.
PrometheusAgentDaemonSetFeature FeatureGateName = "PrometheusAgentDaemonSet"
)
type FeatureGateName string
type FeatureGates map[FeatureGateName]FeatureGate
type FeatureGate struct {
description string
enabled bool
}
// ValidateFeatureGates merges the feature gate default values with
func (fg FeatureGates) Enabled(name FeatureGateName) bool {
return fg[name].enabled
}
// UpdateFeatureGates merges the current feature gate values with
// the values provided by the user.
func ValidateFeatureGates(flags *k8sflag.MapStringBool) (string, error) {
gates := defaultFeatureGates
if flags.Empty() {
return mapToString(gates), nil
}
imgs := *flags.Map
for k, v := range imgs {
if _, ok := gates[k]; !ok {
return "", fmt.Errorf("feature gate %v is unknown", k)
func (fg *FeatureGates) UpdateFeatureGates(flags map[string]bool) error {
for k := range flags {
f, found := (*fg)[FeatureGateName(k)]
if !found {
return fmt.Errorf("feature gate %q is unknown (supported feature gates: %s)", k, fg.String())
}
gates[k] = v
f.enabled = flags[k]
(*fg)[FeatureGateName(k)] = f
}
return mapToString(gates), nil
return nil
}
func AvailableFeatureGates() []string {
i := 0
gates := make([]string, len(defaultFeatureGates))
for k := range defaultFeatureGates {
gates[i] = k
i++
func (fg *FeatureGates) keyValuePairs() ([]FeatureGateName, []FeatureGate) {
if fg == nil {
return nil, nil
}
slices.Sort(gates)
return gates
var (
names = make([]FeatureGateName, 0, len(*fg))
gates = make([]FeatureGate, 0, len(*fg))
)
for k := range *fg {
names = append(names, k)
}
slices.Sort(names)
for _, v := range names {
gates = append(gates, (*fg)[v])
}
return names, gates
}
func mapToString(m map[string]bool) string {
var s []string
for k, v := range m {
s = append(s, fmt.Sprintf("%s=%t", k, v))
func (fg *FeatureGates) Descriptions() []string {
var (
names, gates = fg.keyValuePairs()
desc = make([]string, 0, len(names))
)
for i := range names {
desc = append(desc, fmt.Sprintf("%s: %s (enabled: %t)", names[i], gates[i].description, gates[i].enabled))
}
return desc
}
func (fg *FeatureGates) String() string {
names, gates := fg.keyValuePairs()
s := make([]string, len(names))
for i := range names {
s[i] = fmt.Sprintf("%s=%t", names[i], gates[i].enabled)
}
return strings.Join(s, ",")
}
var featureGateInfoDesc = prometheus.NewDesc(
"prometheus_operator_feature_gate",
"Reports about the Prometheus operator feature gates. A value of 1 means that the feature gate is enabled. Otherwise the value is 0.",
[]string{"name"},
nil,
)
// Describe implements the prometheus.Collector interface.
func (fg *FeatureGates) Describe(ch chan<- *prometheus.Desc) {
ch <- featureGateInfoDesc
}
// Collect implements the prometheus.Collector interface.
func (fg *FeatureGates) Collect(ch chan<- prometheus.Metric) {
names, gates := fg.keyValuePairs()
for i, v := range names {
var val float64
if gates[i].enabled {
val = 1.0
}
ch <- prometheus.MustNewConstMetric(
featureGateInfoDesc,
prometheus.GaugeValue,
val,
string(v),
)
}
}

View File

@@ -15,31 +15,53 @@
package operator
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
k8sflag "k8s.io/component-base/cli/flag"
)
func TestDefaultFeatureGates(t *testing.T) {
m := AvailableFeatureGates()
func TestUpdateFeatureGates(t *testing.T) {
newFg := func() *FeatureGates {
return &FeatureGates{
FeatureGateName("Foo"): {
description: "foo",
enabled: true,
},
FeatureGateName("Bar"): {
description: "bar",
enabled: false,
},
}
}
require.GreaterOrEqual(t, len(m), 1)
}
func TestMapToString(t *testing.T) {
m := mapToString(defaultFeatureGates)
require.True(t, strings.Contains(m, "PrometheusAgentDaemonSet=false"))
}
func TestValidateFeatureGatesWithNotSupportFeature(t *testing.T) {
m, err := ValidateFeatureGates(k8sflag.NewMapStringBool(&map[string]bool{"NotSupportFeature1": true, "NotSupportFeature2": false}))
require.Error(t, err)
require.Equal(t, "", m)
m, err = ValidateFeatureGates(k8sflag.NewMapStringBool(&map[string]bool{"PrometheusAgentDaemonSet": true}))
require.NoError(t, err)
require.True(t, strings.Contains(m, "PrometheusAgentDaemonSet=true"))
for _, tc := range []struct {
flags map[string]bool
err bool
}{
{
flags: map[string]bool{
"Foo": false,
"Bar": true,
},
},
{
flags: map[string]bool{"Foox": false, "Bar": true},
err: true,
},
} {
t.Run("", func(t *testing.T) {
fg := newFg()
err := fg.UpdateFeatureGates(tc.flags)
if tc.err {
require.Error(t, err)
return
}
require.NoError(t, err)
for k, v := range tc.flags {
require.Equal(t, fg.Enabled(FeatureGateName(k)), v)
}
})
}
}