From d5d08c58dfc82d9bc6da7877dfa8991de8c68c4b Mon Sep 17 00:00:00 2001 From: dongjiang Date: Thu, 30 May 2024 15:13:48 +0800 Subject: [PATCH] feat(env): Add automatic memory limit handling (#6591) * add auto GOMEMLIMIT Signed-off-by: dongjiang1989 --------- Signed-off-by: dongjiang1989 Co-authored-by: Simon Pasquier --- Documentation/operator.md | 2 ++ cmd/admission-webhook/main.go | 12 +++++-- cmd/operator/main.go | 8 +++++ cmd/prometheus-config-reloader/main.go | 5 +++ go.mod | 8 +++++ go.sum | 16 +++++++++ internal/goruntime/memory.go | 50 ++++++++++++++++++++++++++ pkg/operator/feature_gates_test.go | 45 +++++++++++++++++++++++ 8 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 internal/goruntime/memory.go create mode 100644 pkg/operator/feature_gates_test.go diff --git a/Documentation/operator.md b/Documentation/operator.md index 2fa03eeef..9912fc8fc 100644 --- a/Documentation/operator.md +++ b/Documentation/operator.md @@ -29,6 +29,8 @@ Usage of ./operator: API Server addr, e.g. ' - NOT RECOMMENDED FOR PRODUCTION - http://127.0.0.1:8080'. Omit parameter to run in on-cluster mode and utilize the service account token. -as string Username to impersonate. User could be a regular user or a service account in a namespace. + -auto-gomemlimit-ratio float + 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). -ca-file string - NOT RECOMMENDED FOR PRODUCTION - Path to TLS CA file. -cert-file string diff --git a/cmd/admission-webhook/main.go b/cmd/admission-webhook/main.go index 6996c141d..e43ec37be 100644 --- a/cmd/admission-webhook/main.go +++ b/cmd/admission-webhook/main.go @@ -38,17 +38,22 @@ import ( "github.com/prometheus-operator/prometheus-operator/pkg/versionutil" ) +const defaultGOMemlimitRatio = 0.0 + func main() { var ( - serverConfig server.Config = server.DefaultConfig(":8443", true) - flagset = flag.CommandLine - logConfig logging.Config + serverConfig server.Config = server.DefaultConfig(":8443", true) + flagset = flag.CommandLine + logConfig logging.Config + memlimitRatio float64 ) server.RegisterFlags(flagset, &serverConfig) versionutil.RegisterFlags(flagset) logging.RegisterFlags(flagset, &logConfig) + flagset.Float64Var(&memlimitRatio, "auto-gomemlimit-ratio", defaultGOMemlimitRatio, "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).") + _ = flagset.Parse(os.Args[1:]) if versionutil.ShouldPrintVersion() { @@ -62,6 +67,7 @@ func main() { } goruntime.SetMaxProcs(logger) + goruntime.SetMemLimit(logger, memlimitRatio) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 8c6dd7862..9f9b2287a 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -98,6 +98,8 @@ func checkPrerequisites( const ( defaultReloaderCPU = "10m" defaultReloaderMemory = "50Mi" + + defaultMemlimitRatio = 0.0 ) var ( @@ -109,6 +111,8 @@ var ( apiServer string tlsClientConfig rest.TLSClientConfig + memlimitRatio float64 + serverConfig = server.DefaultConfig(":8080", false) // Parameters for the kubelet endpoints controller. @@ -169,6 +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())) @@ -204,6 +211,7 @@ func run(fs *flag.FlagSet) int { level.Info(logger).Log("build_context", version.BuildContext()) level.Info(logger).Log("feature_gates", gates) goruntime.SetMaxProcs(logger) + goruntime.SetMemLimit(logger, memlimitRatio) if len(cfg.Namespaces.AllowList) > 0 && len(cfg.Namespaces.DenyList) > 0 { level.Error(logger).Log( diff --git a/cmd/prometheus-config-reloader/main.go b/cmd/prometheus-config-reloader/main.go index 5c90cec4d..9d60afde5 100644 --- a/cmd/prometheus-config-reloader/main.go +++ b/cmd/prometheus-config-reloader/main.go @@ -50,6 +50,8 @@ const ( defaultRetryInterval = 5 * time.Second // 5 seconds was the value previously hardcoded in github.com/thanos-io/thanos/pkg/reloader. defaultReloadTimeout = 30 * time.Second // 30 seconds was the default value + defaultGOMemlimitRatio = "0.0" + httpReloadMethod = "http" signalReloadMethod = "signal" @@ -69,6 +71,8 @@ func main() { retryInterval := app.Flag("retry-interval", "how long the reloader waits before retrying in case the endpoint returned an error").Default(defaultRetryInterval.String()).Duration() reloadTimeout := app.Flag("reload-timeout", "how long the reloader waits for a response from the reload URL").Default(defaultReloadTimeout.String()).Duration() + memlimitRatio := app.Flag("auto-gomemlimit-ratio", "The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory. Default: 0 (disabled)").Default(defaultGOMemlimitRatio).Float64() + watchedDir := app.Flag("watched-dir", "directory to watch non-recursively").Strings() reloadMethod := app.Flag("reload-method", "method used to reload the configuration").Default(httpReloadMethod).Enum(httpReloadMethod, signalReloadMethod) @@ -138,6 +142,7 @@ func main() { level.Info(logger).Log("msg", "Starting prometheus-config-reloader", "version", version.Info()) level.Info(logger).Log("build_context", version.BuildContext()) goruntime.SetMaxProcs(logger) + goruntime.SetMemLimit(logger, *memlimitRatio) r := prometheus.NewRegistry() r.MustRegister( diff --git a/go.mod b/go.mod index 33c69d234..c75df4129 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 toolchain go1.22.2 require ( + github.com/KimMachineGun/automemlimit v0.6.0 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 @@ -49,16 +50,23 @@ require ( require ( github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect + github.com/cilium/ebpf v0.11.0 // indirect + github.com/containerd/cgroups/v3 v3.0.3 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/opencontainers/runtime-spec v1.0.2 // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect ) require ( diff --git a/go.sum b/go.sum index 921a4aecd..19c41ca10 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU= github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1CPDhLlA3Z37E= +github.com/KimMachineGun/automemlimit v0.6.0/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= @@ -83,10 +85,14 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -132,6 +138,8 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -183,6 +191,7 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -391,10 +400,14 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -454,6 +467,8 @@ github.com/simonpasquier/klog-gokit/v3 v3.4.0/go.mod h1:RREVB5Cc6yYHsweRfhUyM1ZP github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -637,6 +652,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= diff --git a/internal/goruntime/memory.go b/internal/goruntime/memory.go new file mode 100644 index 000000000..3088addf8 --- /dev/null +++ b/internal/goruntime/memory.go @@ -0,0 +1,50 @@ +// Copyright 2024 The prometheus-operator 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 goruntime + +import ( + "runtime/debug" + + "github.com/KimMachineGun/automemlimit/memlimit" + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +func SetMemLimit(logger log.Logger, memlimitRatio float64) { + if memlimitRatio >= 1.0 { + memlimitRatio = 1.0 + } else if memlimitRatio <= 0.0 { + memlimitRatio = 0.0 + } + + // the memlimitRatio argument to 0, effectively disabling auto memory limit for all users. + if memlimitRatio == 0.0 { + return + } + + if _, err := memlimit.SetGoMemLimitWithOpts( + memlimit.WithRatio(memlimitRatio), + memlimit.WithProvider( + memlimit.ApplyFallback( + memlimit.FromCgroup, + memlimit.FromSystem, + ), + ), + ); err != nil { + level.Warn(logger).Log("component", "automemlimit", "msg", "Failed to set GOMEMLIMIT automatically", "err", err) + } + + level.Info(logger).Log("GOMEMLIMIT set to %d", debug.SetMemoryLimit(-1)) +} diff --git a/pkg/operator/feature_gates_test.go b/pkg/operator/feature_gates_test.go new file mode 100644 index 000000000..d908e455c --- /dev/null +++ b/pkg/operator/feature_gates_test.go @@ -0,0 +1,45 @@ +// Copyright 2024 The prometheus-operator 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 operator + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + k8sflag "k8s.io/component-base/cli/flag" +) + +func TestDefaultFeatureGates(t *testing.T) { + m := AvailableFeatureGates() + + 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")) +}