diff --git a/README.md b/README.md index ab345d3..aebd198 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ It collects key metrics about: ## Key metrics -📊 Advanced Metrics: Gain deep visibility with advanced metrics for AWS RDS. Monitor performance, query efficiency, and resource utilization like never before. +🥇 Advanced Metrics: Gain deep visibility with advanced metrics for AWS RDS. Monitor performance, query efficiency, and resource utilization like never before. 🧩 AWS Quotas Insights: Stay in control with real-time information about AWS quotas. Ensure you never hit limits unexpectedly. @@ -28,7 +28,7 @@ It collects key metrics about: 🛠️ Simple Setup: Getting started is a breeze! Our clear documentation and examples will have you up and running in no time. -📈 Scalable and Reliable: Prometheus-RDS Exporter scales with your AWS infrastructure, providing reliable monitoring even as you grow. +📊 Dashboards: Prometheus-RDS Exporter export adopts the [USE methodology](https://www.brendangregg.com/usemethod.html) and provides well-designed, ready-to-use dashboards. 🌐 Community-Driven: Join a vibrant community of users and contributors. Collaborate, share knowledge, and shape the future of AWS RDS monitoring together. @@ -119,7 +119,22 @@ It collects key metrics about: ## Dashboards -The following Grafana dashboards are available in `configs/grafana/public/` and Grafana labs: +> [!TIP] +> Grafana dashboards are deployed by default as [GrafanaDashboard CRD](https://grafana.github.io/grafana-operator/docs/dashboards/) when Prometheus RDS exporter is deployed with Helm. If you deployed [Grafana operator](https://grafana.github.io/grafana-operator/) in your Kubernetes cluster, dashboards will be automatically imported and **maintained up-to-date**. + +
+ Why are we recommending Grafana operator? + +We are committed to providing you with the most efficient and user-friendly experience possible. Therefore, we continuously enhance our dashboards and the metrics produced by our exporters to ensure you have access to the most accurate and relevant data. + +To ensure an optimal user experience, it's vital to keep your dashboards up to date. This practice guarantees that you are always working with the latest features and improvements, enabling you to make the most out of the data presented to you. However, maintaining multiple versions of dashboards can be challenging and is not desirable. It introduces complexity and can lead to inconsistencies between what you see and the actual data. + +By leveraging the Grafana Operator, you can rest assured that the version of your dashboard will always match the metrics presented by your exporter. This synchronization between your dashboards and the underlying data ensures a seamless and accurate monitoring experience. This move towards operator-based deployment is designed to streamline your monitoring process, ensuring accuracy and efficiency in your data visualization efforts. + +Kubernetes operators aim to simplify deployments, and as part of this evolution, we will eventually stop publishing dashboards on Grafana Labs. +
+ +For convenience, dashboards are also available in `configs/grafana/public/` folder and Grafana labs: @@ -260,6 +275,12 @@ Terraform users can take example on Terraform code in `configs/terraform/`. +### Helm + +We recommend deployment using helm [Helm](https://helm.sh/). + +See all available configuration parameters in [configs/helm/values.yaml](https://github.com/qonto/prometheus-rds-exporter/blob/main/configs/helm/values.yaml) + ## Installation See the [Development environment](#development-environment) to start the Prometheus RDS exporter, Prometheus, and Grafana with dashboards in a minute. diff --git a/configs/helm/grafana_dashboards b/configs/helm/grafana_dashboards new file mode 120000 index 0000000..00a735d --- /dev/null +++ b/configs/helm/grafana_dashboards @@ -0,0 +1 @@ +../grafana/public \ No newline at end of file diff --git a/configs/helm/templates/grafanadashboard.yaml b/configs/helm/templates/grafanadashboard.yaml new file mode 100644 index 0000000..f7cfefc --- /dev/null +++ b/configs/helm/templates/grafanadashboard.yaml @@ -0,0 +1,20 @@ +{{- if .Values.dashboards.enabled -}} +{{ $currentScope := .}} +{{ range $path, $_ := .Files.Glob "grafana_dashboards/**.json" }} +{{- with $currentScope}} +--- +apiVersion: grafana.integreatly.org/v1beta1 +kind: GrafanaDashboard +metadata: + name: {{ $path | base | trimSuffix ".json" }} +spec: + resyncPeriod: {{ .Values.dashboards.resyncPeriod }} + folder: {{ .Values.dashboards.folderName }} + instanceSelector: + matchLabels: + dashboards: {{ .Values.dashboards.instanceSelector }} + json: | +{{ .Files.Get $path | indent 4 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/configs/helm/tests/grafanadashboard_test.yaml b/configs/helm/tests/grafanadashboard_test.yaml new file mode 100644 index 0000000..f8fb2d6 --- /dev/null +++ b/configs/helm/tests/grafanadashboard_test.yaml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json +--- +suite: grafana dashboard tests +templates: + - grafanadashboard.yaml +tests: + - it: render default Grafana dashboards + asserts: + - isKind: + of: GrafanaDashboard + - hasDocuments: + count: 3 + - documentIndex: 0 + equal: + path: metadata.name + value: prometheus-rds-exporter + - documentIndex: 1 + equal: + path: metadata.name + value: rds-instance + - documentIndex: 2 + equal: + path: metadata.name + value: rds-instances + - equal: + path: spec.resyncPeriod + value: 24h + - equal: + path: spec.folder + value: + - equal: + path: spec.instanceSelector.matchLabels.dashboards + value: grafana + - it: render custom Grafana dashboards settings + values: + - ./values/with_grafanadashboards.yaml + asserts: + - equal: + path: spec.resyncPeriod + value: 1m + - equal: + path: spec.folder + value: dmf + - equal: + path: spec.instanceSelector.matchLabels.dashboards + value: my-grafana + - it: disable Grafana dashboards + values: + - ./values/without_grafanadashboards.yaml + asserts: + - hasDocuments: + count: 0 diff --git a/configs/helm/tests/servicemonitor_test.yaml b/configs/helm/tests/servicemonitor_test.yaml index 815814d..ab3f5c2 100644 --- a/configs/helm/tests/servicemonitor_test.yaml +++ b/configs/helm/tests/servicemonitor_test.yaml @@ -4,7 +4,7 @@ suite: service monitor tests templates: - servicemonitor.yaml tests: - - it: render defaul service monitor + - it: render default service monitor asserts: - isKind: of: ServiceMonitor diff --git a/configs/helm/tests/values/with_grafanadashboards.yaml b/configs/helm/tests/values/with_grafanadashboards.yaml new file mode 100644 index 0000000..82895be --- /dev/null +++ b/configs/helm/tests/values/with_grafanadashboards.yaml @@ -0,0 +1,5 @@ +--- +dashboards: + resyncPeriod: 1m + instanceSelector: my-grafana + folderName: dmf diff --git a/configs/helm/tests/values/without_grafanadashboards.yaml b/configs/helm/tests/values/without_grafanadashboards.yaml new file mode 100644 index 0000000..4306715 --- /dev/null +++ b/configs/helm/tests/values/without_grafanadashboards.yaml @@ -0,0 +1,3 @@ +--- +dashboards: + enabled: false diff --git a/configs/helm/values.yaml b/configs/helm/values.yaml index 97e9f92..cc6c565 100644 --- a/configs/helm/values.yaml +++ b/configs/helm/values.yaml @@ -84,3 +84,9 @@ nodeSelector: {} tolerations: [] affinity: {} + +dashboards: + enabled: true # enabled GrafanaDashboard CRD import + resyncPeriod: 24h # how often the dashboard is refreshed + instanceSelector: grafana # selects Grafana for import + folderName: # folder assignment for dashboard diff --git a/scripts/kubeconform-test.sh b/scripts/kubeconform-test.sh index 10214ed..012366e 100755 --- a/scripts/kubeconform-test.sh +++ b/scripts/kubeconform-test.sh @@ -52,7 +52,7 @@ do -kubernetes-version ${KUBERNETES_VERSION} \ -cache ${KUBECONFORM_CACHE_DIRECTORY} \ -schema-location default \ - -schema-location 'kubeconform/{{ .ResourceKind }}{{ .KindSuffix }}.json' \ + -schema-location 'scripts/kubeconform/{{.Group}}/{{ .ResourceKind }}_{{.ResourceAPIVersion}}.json' \ -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ -summary diff --git a/scripts/kubeconform/grafana.integreatly.org/README.md b/scripts/kubeconform/grafana.integreatly.org/README.md new file mode 100644 index 0000000..59f3c83 --- /dev/null +++ b/scripts/kubeconform/grafana.integreatly.org/README.md @@ -0,0 +1,3 @@ +# Note for later + +Grafana dashboards CRD is required until is merged diff --git a/scripts/kubeconform/grafana.integreatly.org/grafanadashboard_v1beta1.json b/scripts/kubeconform/grafana.integreatly.org/grafanadashboard_v1beta1.json new file mode 100644 index 0000000..052076e --- /dev/null +++ b/scripts/kubeconform/grafana.integreatly.org/grafanadashboard_v1beta1.json @@ -0,0 +1,354 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "annotations": { + "controller-gen.kubebuilder.io/version": "v0.12.0" + }, + "name": "grafanadashboards.grafana.integreatly.org" + }, + "spec": { + "group": "grafana.integreatly.org", + "names": { + "kind": "GrafanaDashboard", + "listKind": "GrafanaDashboardList", + "plural": "grafanadashboards", + "singular": "grafanadashboard" + }, + "scope": "Namespaced", + "versions": [ + { + "additionalPrinterColumns": [ + { + "jsonPath": ".status.NoMatchingInstances", + "name": "No matching instances", + "type": "boolean" + }, + { + "format": "date-time", + "jsonPath": ".status.lastResync", + "name": "Last resync", + "type": "date" + }, + { + "jsonPath": ".metadata.creationTimestamp", + "name": "Age", + "type": "date" + } + ], + "name": "v1beta1", + "schema": { + "openAPIV3Schema": { + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "properties": { + "allowCrossNamespaceImport": { + "type": "boolean" + }, + "configMapRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "contentCacheDuration": { + "type": "string" + }, + "datasources": { + "items": { + "properties": { + "datasourceName": { + "type": "string" + }, + "inputName": { + "type": "string" + } + }, + "required": [ + "datasourceName", + "inputName" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + } + }, + "type": "object" + }, + "type": "array" + }, + "envs": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "folder": { + "type": "string" + }, + "grafanaCom": { + "properties": { + "id": { + "type": "integer" + }, + "revision": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "gzipJson": { + "format": "byte", + "type": "string" + }, + "instanceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "json": { + "type": "string" + }, + "jsonnet": { + "type": "string" + }, + "jsonnetLib": { + "properties": { + "fileName": { + "type": "string" + }, + "gzipJsonnetProject": { + "format": "byte", + "type": "string" + }, + "jPath": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "fileName", + "gzipJsonnetProject" + ], + "type": "object" + }, + "plugins": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "type": "array" + }, + "resyncPeriod": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "instanceSelector" + ], + "type": "object" + }, + "status": { + "properties": { + "NoMatchingInstances": { + "type": "boolean" + }, + "contentCache": { + "format": "byte", + "type": "string" + }, + "contentTimestamp": { + "format": "date-time", + "type": "string" + }, + "contentUrl": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "lastResync": { + "format": "date-time", + "type": "string" + }, + "uid": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "served": true, + "storage": true, + "subresources": { + "status": {} + } + } + ] + } +}