Skip to content

Commit

Permalink
feat: validation of required feature gate at start (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikouaj authored Oct 15, 2024
1 parent f80424d commit 4b53f87
Show file tree
Hide file tree
Showing 14 changed files with 589 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ go.work

bin/
demo-app/target
.vscode
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Kube Startup CPU Boost operator can be configured with environmental variables.
| `ZAP_DEVELOPMENT` | `bool` | `false` | Enables development mode for ZAP logger |
| `HTTP2` | `bool` | `false` | Determines if the HTTP/2 protocol is used for webhook and metrics servers|
| `REMOVE_LIMITS` | `bool` | `true` | Enables operator to remove container CPU limits during the boost time |
| `VALIDATE_FEATURE_ENABLED` | `bool` | `true` | Enables validation of required feature gate on operator's startup |

## Metrics

Expand Down
36 changes: 34 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
package main

import (
"context"
"crypto/tls"
"fmt"
"os"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.

_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"

"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand All @@ -41,15 +44,20 @@ import (
//+kubebuilder:scaffold:imports
)

const (
IN_PLACE_VERTICAL_POD_AUTOSCALING_FG_NAME = "InPlacePodVerticalScaling"
)

var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
leaderElectionID = "8fd077db.x-k8s.io"
)

//+kubebuilder:rbac:urls="/metrics",verbs=get

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))

utilruntime.Must(autoscalingv1alpha1.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}
Expand All @@ -62,6 +70,22 @@ func main() {
}
ctrl.SetLogger(config.Logger(cfg.ZapDevelopment, cfg.ZapLogLevel))
metrics.Register()
restConfig := ctrl.GetConfigOrDie()

if cfg.ValidateFeatureEnabled {
setupLog.Info("validating required feature gates")
featureGates, err := getFeatureGatesFromMetrics(context.Background(), restConfig)
if err == nil {
if !featureGates.IsEnabledAnyStage(IN_PLACE_VERTICAL_POD_AUTOSCALING_FG_NAME) {
setupLog.Error(
fmt.Errorf("%s is not enabled at any stage", IN_PLACE_VERTICAL_POD_AUTOSCALING_FG_NAME),
"required feature gates are not enabled")
os.Exit(1)
}
} else {
setupLog.Error(err, "failed to validate required feature gates, continuing...")
}
}

tlsOpts := []func(*tls.Config){}
if !cfg.HTTP2 {
Expand All @@ -76,7 +100,7 @@ func main() {
Port: 9443,
})

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: cfg.MetricsProbeBindAddr,
Expand Down Expand Up @@ -144,3 +168,11 @@ func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, cfg *config.Conf
}
//+kubebuilder:scaffold:builder
}

func getFeatureGatesFromMetrics(ctx context.Context, cfg *rest.Config) (util.FeatureGates, error) {
fgValidator, err := util.NewMetricsFeatureGateValidatorFromConfig(ctx, cfg)
if err != nil {
return nil, err
}
return fgValidator.GetFeatureGates()
}
4 changes: 4 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
- apiGroups:
- ""
resources:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/open-policy-agent/cert-controller v0.11.0
github.com/prometheus/client_golang v1.20.4
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.55.0
go.uber.org/mock v0.4.0
go.uber.org/zap v1.27.0
gomodules.xyz/jsonpatch/v2 v2.4.0
Expand Down Expand Up @@ -52,7 +53,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
Expand Down
25 changes: 15 additions & 10 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
package config

const (
PodNamespaceDefault = "kube-startup-cpu-boost-system"
MgrCheckIntervalSecDefault = 5
LeaderElectionDefault = false
MetricsProbeBindAddrDefault = ":8080"
HealthProbeBindAddrDefault = ":8081"
SecureMetricsDefault = false
ZapLogLevelDefault = 0 // zapcore.InfoLevel
ZapDevelopmentDefault = false
HTTP2Default = false
RemoveLimitsDefault = true
PodNamespaceDefault = "kube-startup-cpu-boost-system"
MgrCheckIntervalSecDefault = 5
LeaderElectionDefault = false
MetricsProbeBindAddrDefault = ":8080"
HealthProbeBindAddrDefault = ":8081"
SecureMetricsDefault = false
ZapLogLevelDefault = 0 // zapcore.InfoLevel
ZapDevelopmentDefault = false
HTTP2Default = false
RemoveLimitsDefault = true
ValidateFeatureEnabledDefault = true
)

// ConfigProvider provides the Kube Startup CPU Boost configuration
Expand Down Expand Up @@ -57,6 +58,9 @@ type Config struct {
HTTP2 bool
// RemoveLimits determines if CPU resource limits should be removed during boost
RemoveLimits bool
// ValidateFeatureEnabled determines if InPlacePodVerticalScaling feature state
// is validated at operator's start
ValidateFeatureEnabled bool
}

// LoadDefaults loads the default configuration values
Expand All @@ -71,4 +75,5 @@ func (c *Config) LoadDefaults() {
c.ZapDevelopment = ZapDevelopmentDefault
c.HTTP2 = HTTP2Default
c.RemoveLimits = RemoveLimitsDefault
c.ValidateFeatureEnabled = ValidateFeatureEnabledDefault
}
3 changes: 3 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ var _ = Describe("Config", func() {
It("has valid RemoveLimits", func() {
Expect(cfg.RemoveLimits).To(Equal(config.RemoveLimitsDefault))
})
It("has valid RemoveLimits", func() {
Expect(cfg.ValidateFeatureEnabled).To(Equal(config.ValidateFeatureEnabledDefault))
})
})
})
33 changes: 23 additions & 10 deletions internal/config/env_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ import (
)

const (
PodNamespaceEnvVar = "POD_NAMESPACE"
MgrCheckIntervalSecEnvVar = "MGR_CHECK_INTERVAL"
LeaderElectionEnvVar = "LEADER_ELECTION"
MetricsProbeBindAddrEnvVar = "METRICS_PROBE_BIND_ADDR"
HealthProbeBindAddrEnvVar = "HEALTH_PROBE_BIND_ADDR"
SecureMetricsEnvVar = "SECURE_METRICS"
ZapLogLevelEnvVar = "ZAP_LOG_LEVEL"
ZapDevelopmentEnvVar = "ZAP_DEVELOPMENT"
HTTP2EnvVar = "HTTP2"
RemoveLimitsEnvVar = "REMOVE_LIMITS"
PodNamespaceEnvVar = "POD_NAMESPACE"
MgrCheckIntervalSecEnvVar = "MGR_CHECK_INTERVAL"
LeaderElectionEnvVar = "LEADER_ELECTION"
MetricsProbeBindAddrEnvVar = "METRICS_PROBE_BIND_ADDR"
HealthProbeBindAddrEnvVar = "HEALTH_PROBE_BIND_ADDR"
SecureMetricsEnvVar = "SECURE_METRICS"
ZapLogLevelEnvVar = "ZAP_LOG_LEVEL"
ZapDevelopmentEnvVar = "ZAP_DEVELOPMENT"
HTTP2EnvVar = "HTTP2"
RemoveLimitsEnvVar = "REMOVE_LIMITS"
ValidateFeatureEnabledEnvVar = "VALIDATE_FEATURE_ENABLED"
)

type LookupEnvFunc func(key string) (string, bool)
Expand Down Expand Up @@ -59,6 +60,7 @@ func (p *EnvConfigProvider) LoadConfig() (*Config, error) {
errs = p.loadZapDevelopment(&config, errs)
errs = p.loadHTTP2(&config, errs)
errs = p.loadRemoveLimits(&config, errs)
errs = p.loadValidateFeatureEnabled(&config, errs)
var err error
if len(errs) > 0 {
err = errors.Join(errs...)
Expand Down Expand Up @@ -160,3 +162,14 @@ func (p *EnvConfigProvider) loadRemoveLimits(config *Config, curErrs []error) (e
}
return
}

func (p *EnvConfigProvider) loadValidateFeatureEnabled(config *Config, curErrs []error) (errs []error) {
if v, ok := p.lookupFunc(ValidateFeatureEnabledEnvVar); ok {
boolVal, err := strconv.ParseBool(v)
config.ValidateFeatureEnabled = boolVal
if err != nil {
errs = append(curErrs, fmt.Errorf("%s value is not a bool: %s", ValidateFeatureEnabledEnvVar, err))
}
}
return
}
8 changes: 8 additions & 0 deletions internal/config/env_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,13 @@ var _ = Describe("EnvProvider", func() {
Expect(cfg.RemoveLimits).To(BeFalse())
})
})
When("validateFeatureEnabled variable is set", func() {
BeforeEach(func() {
lookupFuncMap[config.ValidateFeatureEnabledEnvVar] = "false"
})
It("has valid validateFeatureEnabled", func() {
Expect(cfg.ValidateFeatureEnabled).To(BeFalse())
})
})
})
})
Loading

0 comments on commit 4b53f87

Please sign in to comment.