When we use Grafana Dashboard to display our monitoring charts, we often go to someone else’s Dashboard and change it, but this also causes many people using Grafana to have no idea how to customize a Dashboard, although it is not very difficult. Here we introduce a relatively new tool: DARK, full name Dashboards As Resources in Kubernetes., which means that through the Kubernetes resource object to define Grafana Dashboard, the implementation principle is also very simple, that is, the CRD to define the Dashboard, and then through the interaction with Grafana’s API Token to achieve the CRUD of the Dashboard.

Let’s look at how to use DARK to define a Grafana Dashboard.

First Clone the project code.

1
$ git clone https://github.com/K-Phoen/dark.git

Then install the CRD resources

1
$ kubectl apply -f k8s/crd.yaml

Then create Grafana’s API KEYS from the Secret object. In the Grafana main interface, select the Configuration menu on the left -> API Keys to create API Keys, and select the Editor role to.

Once created, a dialog box will pop up with the corresponding API Keys, use this KEY to create a corresponding Secret object.

1
$ kubectl create secret generic dark-tokens --from-literal=grafana=<替换成APIKEY>

Then modify the k8s/cluster-role.yaml file as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dark
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: dashboards-viewer
rules:
- apiGroups: ["k8s.kevingomez.fr"]
  resources: ["grafanadashboards"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboards-viewer-cluster
subjects:
  - kind: ServiceAccount
    name: dark
    namespace: default
roleRef:
  kind: ClusterRole
  name: dashboards-viewer
  apiGroup: rbac.authorization.k8s.io

Then create the resource object above.

1
$ kubectl apply -f k8s/cluster-role.yaml

Modify the k8s/deployment.yaml file, change the GRAFANA_HOST environment variable to your own Grafana address, and since I have Grafana installed in the Kubernetes cluster here, I directly configure it with DNS, and then add the dark created above ServiceAccount.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dark
  labels:
    app: dark
spec:
  selector:
    matchLabels:
      app: dark
  template:
    metadata:
      labels:
        app: dark
    spec:
      volumes:
      - name: dark-tokens
        secret:
          secretName: dark-tokens
      serviceAccountName: dark
      containers:
      - name: dark
        image: kphoen/dark:latest
        env:
        - name: GRAFANA_HOST
          value: http://grafana.kube-mon:3000
        - name: GRAFANA_TOKEN
          valueFrom:
            secretKeyRef:
              key: grafana
              name: dark-tokens

Create the above Controller directly after the modification is completed.

1
2
3
4
$ kubectl apply -f k8s/deployment.yaml
$ kubectl get pods -l app=dark
NAME                    READY   STATUS    RESTARTS   AGE
dark-6bd956b8d6-755p2   1/1     Running   0          36m

Now that the Controller is defined, we can actually go through the CRD object to define the Grafana Dashboard, as shown below to define a GrafanaDashboard object, in which we can define the content according to our needs, such as defining annotations, variables, graph, table can be defined, but of course the most important thing is to have the correct data source and query statement: (example-dashboards.yaml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
apiVersion: k8s.kevingomez.fr/v1
kind: GrafanaDashboard
metadata:
  name: example-dashboard
folder: "Test folder"
spec:
  title: Awesome dashboard
  editable: true
  shared_crosshair: true
  tags: [generated, yaml]
  auto_refresh: 10s
  tags_annotations:
    - name: Deployments
      datasource: "Prometheus"
      color: "#5794F2"
      tags: ["deploy", "production"]
  variables:
    - interval:
        name: interval
        label: Interval
        values: ["30s", "1m", "5m", "10m", "30m", "1h", "6h", "12h"]
    - query:
        name: status
        label: HTTP status
        datasource: Prometheus
        request: "label_values(prometheus_http_requests_total, code)"
    - const:
        name: percentile
        label: Percentile
        default: 80
        values_map:
          50th: "50"
          75th: "75"
          80th: "80"
          85th: "85"
          90th: "90"
          95th: "95"
          99th: "99"
    - custom:
        name: vX
        default: v2
        values_map:
          v1: v1
          v2: v2
  rows:
    - name: Prometheus
      panels:
        - graph:
            title: HTTP Rate
            height: 400px
            datasource: Prometheus
            targets:
              - prometheus:
                  query: "rate(promhttp_metric_handler_requests_total[$interval])"
                  legend: "{{handler}} - {{ code }}"
        - graph:
            title: Heap allocations
            height: 400px
            datasource: Prometheus
            targets:
              - prometheus:
                  query: "go_memstats_heap_alloc_bytes"
                  legend: "{{job}}"
                  ref: A
        - table:
            title: Threads
            datasource: Prometheus
            targets:
              - prometheus:
                  query: "go_threads"
            hidden_columns: ["Time"]
            time_series_aggregations:
              - label: AVG
                type: avg
              - label: Current
                type: current
        - single_stat:
            title: Heap Allocations
            datasource: Prometheus
            targets:
              - prometheus:
                  query: 'go_memstats_heap_alloc_bytes{job="prometheus"}'
            unit: bytes
            thresholds: ["26000000", "28000000"]
            color: ["value"]
    - name: "Some text, because it might be useful"
      panels:
        - text:
            title: Some awesome text?
            markdown: "Markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n${percentile}"
        - text:
            title: Some awesome html?
            html: "Some <b>awesome</b> html?"

Again, create the above example file directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ kubectl apply -f example-dashboards.yaml
$ kubectl get dashboards
NAME                AGE
example-dashboard   35m
$ kubectl logs -f dark-6bd956b8d6-755p2
W0327 11:10:24.356194       1 client_config.go:543] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I0327 11:10:24.360886       1 controller.go:87] Setting up event handlers
I0327 11:10:24.362305       1 controller.go:118] Starting dark-controller
I0327 11:10:24.362341       1 controller.go:121] Waiting for informer caches to sync
I0327 11:10:24.462733       1 controller.go:126] Starting workers
I0327 11:10:24.462820       1 controller.go:132] Started workers
I0327 11:13:22.641706       1 controller.go:197] Successfully synced 'default/example-dashboard'
I0327 11:13:22.643061       1 event.go:278] Event(v1.ObjectReference{Kind:"GrafanaDashboard", Namespace:"default", Name:"example-dashboard", UID:"efc6f96f-c7fc-40b5-8b8f-831a95b0a042", APIVersion:"k8s.kevingomez.fr/v1", ResourceVersion:"48490732", FieldPath:""}): type: 'Normal' reason: 'Synced' GrafanaDashboard synced successfully

You can also see the corresponding logging information in the Controller. After the resource object is created successfully, you can now check the Grafana page to see that a new Test folder folder and Awesome dashboard have been added.

Looking at the Dashboard, you can see the same information as the various charts defined in the CRD above.

This way we define the Grafana Dashboard using Kubernetes resource objects, which is obviously more elegant than going directly to the page and configuring it manually, and is in line with the everything as code idea 🤯.