diff --git a/README.md b/README.md index 1443b47b..bcd25b28 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ helm install --create-namespace --namespace pvc-autoresizer pvc-autoresizer pvc- See the Chart [README.md](./charts/pvc-autoresizer/README.md) for detailed documentation on the Helm Chart. +For instructions on installing on OpenShift refer to the doc [openshift_monitoring.md](./docs/openshift_monitoring.md). + ### How to use To allow auto volume expansion, the StorageClass of PVC need to allow volume expansion and diff --git a/charts/pvc-autoresizer/templates/controller/deployment.yaml b/charts/pvc-autoresizer/templates/controller/deployment.yaml index 0cefb154..94c6c688 100644 --- a/charts/pvc-autoresizer/templates/controller/deployment.yaml +++ b/charts/pvc-autoresizer/templates/controller/deployment.yaml @@ -36,6 +36,7 @@ spec: - /pvc-autoresizer args: - --prometheus-url={{ .Values.controller.args.prometheusURL }} + - --bearer-token={{ .Values.controller.args.bearerToken }} - --interval={{ .Values.controller.args.interval }} {{- if .Values.controller.args.useK8sMetricsApi }} - --use-k8s-metrics-api={{ .Values.controller.args.useK8sMetricsApi }} @@ -48,7 +49,7 @@ spec: {{- end }} {{- if not .Values.webhook.pvcMutatingWebhook.enabled }} - --pvc-mutating-webhook-enabled=false - {{- end}} + {{- end }} image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}" {{- with .Values.image.pullPolicy }} imagePullPolicy: {{ . }} diff --git a/charts/pvc-autoresizer/values.yaml b/charts/pvc-autoresizer/values.yaml index d1e79039..f1a03b8d 100644 --- a/charts/pvc-autoresizer/values.yaml +++ b/charts/pvc-autoresizer/values.yaml @@ -22,6 +22,9 @@ controller: # Used as "--prometheus-url" option prometheusURL: http://prometheus-prometheus-oper-prometheus.prometheus.svc:9090 + # controller.args.bearerToken -- Specify bearer token, useful for accessing OpenShift Monitoring Prometheus. + bearerToken: "" + # controller.args.namespaces -- Specify namespaces to control the pvcs of. Empty for all namespaces. # Used as "--namespaces" option namespaces: [] diff --git a/cmd/main.go b/cmd/main.go index c952542e..29d34e8d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,6 +20,7 @@ var config struct { namespaces []string watchInterval time.Duration prometheusURL string + bearerToken string useK8sMetricsApi bool skipAnnotation bool development bool @@ -56,6 +57,7 @@ func init() { "Namespaces to resize PersistentVolumeClaims within. Empty for all namespaces.") fs.DurationVar(&config.watchInterval, "interval", 1*time.Minute, "Interval to monitor pvc capacity.") fs.StringVar(&config.prometheusURL, "prometheus-url", "", "Prometheus URL to query volume stats.") + fs.StringVar(&config.bearerToken, "bearer-token", "", "bearer token for Prometheus authentication.") fs.BoolVar(&config.useK8sMetricsApi, "use-k8s-metrics-api", false, "Use Kubernetes metrics API instead of Prometheus") fs.BoolVar(&config.skipAnnotation, "no-annotation-check", false, "Skip annotation check for StorageClass") fs.BoolVar(&config.development, "development", false, "Use development logger config") diff --git a/cmd/run.go b/cmd/run.go index 17f3bfa2..94c9f57d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -116,7 +116,8 @@ func subMain() error { if config.useK8sMetricsApi { metricsClient, err = runners.NewK8sMetricsApiClient() } else if config.prometheusURL != "" { - metricsClient, err = runners.NewPrometheusClient(config.prometheusURL) + setupLog.Info("enable prometheus url", "url", config.prometheusURL) + metricsClient, err = runners.NewPrometheusClient(config.prometheusURL, config.bearerToken, ctrl.Log.WithName("prometheus-client")) } else { setupLog.Error(err, "enable use-k8s-metrics-api or provide prometheus-url") return err diff --git a/docs/openshift_monitoring.md b/docs/openshift_monitoring.md new file mode 100644 index 00000000..a9bd75b8 --- /dev/null +++ b/docs/openshift_monitoring.md @@ -0,0 +1,14 @@ +# Integration with OpenShift Monitoring + +https://docs.redhat.com/en/documentation/openshift_container_platform/4.19/html/monitoring/accessing-metrics#about-accessing-monitoring-web-service-apis_accessing-monitoring-apis-by-using-the-cli + +``` +helm install --create-namespace --namespace pvc-autoresizer pvc-autoresizer pvc-autoresizer/pvc-autoresizer --set "controller.args.prometheusURL=https://prometheus-k8s-openshift-monitoring.apps.[clustername.domain]/" --set "controller.args.bearerToken=$(oc create token cluster-monitoring-operator --duration=8760h -n openshift-monitoring) + +oc get pods -n pvc-autoresizer +``` + +## TODO +[ ] Support proper TLS not just `InsecureSkipVerify: true` +[ ] Add support for other authentication method than bearer token +[ ] Document how to configure the `controller-pvc-autoresizer` serviceAccount for [cluster-monitoring-view](https://www.redhat.com/en/blog/custom-grafana-dashboards-red-hat-openshift-container-platform-4) diff --git a/internal/runners/metrics_client_test.go b/internal/runners/metrics_client_test.go index d0c283e1..03fc13ff 100644 --- a/internal/runners/metrics_client_test.go +++ b/internal/runners/metrics_client_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "sync" + "github.com/go-logr/logr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/types" @@ -40,7 +41,7 @@ var _ = Describe("test prometheusClient", func() { ts := httptest.NewServer(http.HandlerFunc(http.NotFound)) defer ts.Close() - c, err := NewPrometheusClient(ts.URL) + c, err := NewPrometheusClient(ts.URL, "", logr.Discard()) Expect(err).ToNot(HaveOccurred()) _, err = c.GetMetrics(context.TODO()) Expect(err).To(HaveOccurred()) diff --git a/internal/runners/prometheus_client.go b/internal/runners/prometheus_client.go index 6a3bd966..0e45692a 100644 --- a/internal/runners/prometheus_client.go +++ b/internal/runners/prometheus_client.go @@ -2,9 +2,13 @@ package runners import ( "context" + "crypto/tls" "fmt" + "net/http" + "strings" "time" + "github.com/go-logr/logr" "github.com/prometheus/client_golang/api" prometheusv1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -13,20 +17,51 @@ import ( ) // NewPrometheusClient returns a new prometheusClient -func NewPrometheusClient(url string) (MetricsClient, error) { - client, err := api.NewClient(api.Config{ - Address: url, - }) +func NewPrometheusClient(url string, bearerToken string, log logr.Logger) (MetricsClient, error) { + log.Info("creating new prometheus client", "url", url) + + // Create base transport with TLS config if needed + var baseTransport http.RoundTripper = http.DefaultTransport + if strings.HasPrefix(url, "https") { + log.Info("using https, creating transport with tls config") + baseTransport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + + // Wrap transport with bearer token if provided + var rt http.RoundTripper = baseTransport + if bearerToken != "" { + rt = roundTripperFunc(func(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+bearerToken) + return baseTransport.RoundTrip(req) + }) + } + + config := api.Config{ + Address: url, + RoundTripper: rt, + } + + client, err := api.NewClient(config) if err != nil { + log.Error(err, "failed to create prometheus client") return nil, err } v1api := prometheusv1.NewAPI(client) + log.Info("created prometheus client successfully") return &prometheusClient{ prometheusAPI: v1api, }, nil } +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + type prometheusClient struct { prometheusAPI prometheusv1.API }