Grafana

Grafana 5 ist noch eine AngularJS-basierte Applikation, mit einigen React.js-Komponenten. Plugins sind Angular-Controller, die in data/plugins/your-module/dist/module.js abgelegt und von SystemJS geladen werden. Die Migration von AngularJS hin zu React wurde gegen Ende 2018 gestartet, und mit Grafana 7 ein grosser Meilenstein erreicht. Damit ändert sich allerdings auch die Plugin-Entwicklung.

Links

Installation

Installation der OSS-Variante:

/etc/yum.repos.d/grafana.repo
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
dnf -y install grafana
/etc/grafana/grafana.ini
# The public facing domain name used to access grafana from a browser
# set this, otherwise some redirects might not work (for example OAuth)
domain = 192.0.2.26

# default admin user, created on startup
admin_user = grafana-admin

# default admin password, can be changed before first start of grafana,  or in profile settings
admin_password = password

Wird Grafana ohne separate Datenbank wie MariaDB oder PostgreSQL konfiguriert, nutzt es seine integrierte SQLite-Datenbank.

systemctl enable --now grafana-server

Port 3000/tcp freigeben.

http://grafana:3000/login, Login mit „grafana-admin“ und „password“.

Logging:

tail -f /var/log/grafana/grafana.log

Grid

Das Stabdard-Grid besteht aus 24 Spalten (X) und 48 Zeilen (Y). Die Grid-Positionen beginnen bei 0 (Null) an zu zählen.

Panels

Um die Anzahl der Datenpunkte in Panels zu reduzieren (wichtig bei grossen Zeiträumen), empfiehlt sich

  • ein GROUP BY time($interval), ... in den Queries.

  • mit Aggregatsfunktionen wie mean() und distinct(), oder mit Selektor-Funktionen wie first() zu arbeiten.

Kiosk-Mode

Um Grafana-Dashboards ohne störende Menüs im Kiosk-Mode laufen zu lassen, die Dashboard-URL um den Parameter &kiosk ergänzen.

Keycloak (OAuth)

Grafana-Seite

Grafana lässt sich an Keycloak (für Grafana ein OAuth-IdP) anbinden. Die Grafana-Seite muss wie folgt konfiguriert werden:

/etc/grafana/grafana.ini
[auth.generic_oauth]
enabled = true
name = Keycloak IdP
allow_sign_up = true
client_id = grafana
client_secret = e8d78053-fd47-4ec3-b6dc-87357f18f8d5
scopes = openid email profile
;email_attribute_name = email:primary
;email_attribute_path =
;login_attribute_path =
;id_token_attribute_name =
auth_url = http://keycloak:8080/auth/realms/grafana/protocol/openid-connect/auth
token_url = http://keycloak:8080/auth/realms/grafana/protocol/openid-connect/token
api_url = http://keycloak:8080/auth/realms/grafana/protocol/openid-connect/userinfo
;allowed_domains =
;team_ids =
;allowed_organizations =
role_attribute_path = "contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'"
;tls_skip_verify_insecure = false
;tls_client_cert =
;tls_client_key =
;tls_client_ca =

allow_sign_up = true ist notwendig, da ansonsten Grafana den von Keycloak übermittelten Benutzer nicht in seiner eigenen Datenbank anlegen kann (man erhält die Fehlermeldung Not allowing oauth_generic_oauth login, user not found in internal user database and allow signup = false).

Wer mag, einen Admin-User im Keycloak angelegt hat und Keycloak vertraut, kann das klassische Anmelde-Formular per disable_login_form = false abschalten, so dass nur noch eine Anmeldung per Keycloak möglich ist

Keycloak-Seite

Besonderheiten eines Realms für Grafana: der OAuth-Client in Grafana versteht Roles - Keycloak teilt Grafana also die gewünschte Berechtigungsstufe mit. Im Beispiel wird eine admin-Rolle angelegt, der ein Keycloak-User zugewiesen wird.

Neuer Client „Grafana“ mit „openid-connect“.

  • Clients > „grafana“ > Settings

    • Client ID: grafana

    • Name: Grafana (wird im Self-Service-Portal unter „Applications“ verwendet)

    • Description: Any descriptive text (wird im Self-Service-Portal unter „Applications“ verwendet)

    • Client Protocol: openid-connect

    • Access Type: confidential

    • Valid Redirect URIs: http://grafana:3000/login/generic_oauth

    • Base URL: http://grafana:3000/login/generic_oauth (wird im Self-Service-Portal unter „Applications“ verwendet)

  • Clients > „grafana“ > Roles > Add

    • admin

    • editor

    • viewer

Benutzer anlegen und Rolle zuweisen:

  • Users > User suchen, Edit > Role Mappings > Client Roles > „grafana“ > Assign „admin“ role (oder „editor“, oder „viewer“)

Wir möchten im Client-Token nur das mitteilen, was wir explizit definieren:

  • Clients > „grafana“ > Scope > Full Scope Allowed: OFF

Grafana muss anschliessend in der Lage sein, die Berechtigungsstufe aus dem JWT-Token zu extrahieren. Dazu muss in Keycloak ein „Client Mapper“ definiert werden:

  • Clients > „grafana“ > Mappers > Create

    • Name: Keycloak Role to Client Role

    • Mapper Type: User Client Role

    • Client ID: grafana

    • Token Claim Name: roles

    • Claim JSON type: string

    • Add to ID token: ON

    • Add to access token: OFF

    • Add to userinfo: OFF

Der Benutzer wird bei der Anmeldung in Grafana an Keycloak umgeleitet. Ist der Benutzer authentifiziert, wird er in Grafana mit der ihm in Keycloak zugeordneten Rolle angelegt. Wird die Rolle des Benutzers nachträglich in Keycloak geändert, erhält der Benutzer seine neue Gruppenzugehörigkeit beim nächsten Login in Grafana.

Das Grafana-API

Im Beispiel: Export aller Datasources aus Grafana per API (ist auch nicht anders möglich).

Zunächst im Grafana unter Configuration > API Keys einen neuen API-Key erstellen. Den Bearer-TOken unbedingt notieren. Anschliessend lassen sich API-Calls durchführen, beispielsweise:

# Content of Dashboards > Home
curl -H "Authorization: Bearer eyJrIjoiTTZFSjVDdVo1ZTNtOUZkN1Myc3pva3hGcjZCakxhOHIiLCJuIjoiY3VybGVyIiwiaWQiOjF9" http://192.0.2.74:3000/api/dashboards/home | jq

# Export all Datasources (API-Key needs Admin Permissions)
curl -H "Authorization: Bearer eyJrIjoiTTZFSjVDdVo1ZTNtOUZkN1Myc3pva3hGcjZCakxhOHIiLCJuIjoiY3VybGVyIiwiaWQiOjF9" http://192.0.2.74:3000/api/datasources | jq

Das API ist hier beschrieben: https://grafana.com/docs/grafana/latest/http_api/

Plugins

Plugins müssen für bestimmte Grafana-Versionen signiert sein, damit sie dort geladen werden können. Ausnahmen lassen sich so definieren:

/etc/grafana/grafana.ini
[plugins]
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed
# to be loaded even if they lack a valid signature.
allow_loading_unsigned_plugins = simpod-json-datasource

Ab Grafana 7.0 wird die Entwicklung Angular-basierter Plugins nicht mehr empfohlen (gelten jetzt als „Legacy Plugins“), daher React verwenden.

Techniken:

  • Bis Version 6: Angular, node & npm/yarn, Build-Prozess basiert auf grunt

  • Ab Version 7: React/JSX, node & yarn

Plugin Typen:

  • Panel: Custom visualization for your dashboards

  • Data Source: plugin to add support for your own data sources

  • Backend Data Source: lets your data source plugin communicate with a process running on the server instead in the Browser

Grizzly

Mit Grizzly lassen sich Grafana-Elemente wie Datasources oder Dashboards in YAML definieren und „as Code“ pflegen. Grizzly nutzt dabei das Grafana REST-API.

Installation von Grizzly (zum Beispiel auf der lokalen Admin-Workstation):

VER=0.2.0
sudo curl --fail --show-error --location --output "/usr/local/bin/grr" "https://github.com/grafana/grizzly/releases/download/v$VER/grr-linux-amd64"
sudo chmod a+x "/usr/local/bin/grr"

grr --help

In Grafana Authentication auf Basis eines „Service Accounts“ konfigurieren:

  • Grafana > Configuration > Service Accounts > Add Service Account: Name = grizzy, Role = Editor, Add service account token

  • Hinweis: Wer Datasources deployen möchte, benötigt Admin-Rechte.

Auf der Maschine mit Grizzly passende Environment-Variablen setzen:

export GRAFANA_URL=http://192.0.2.74:3000
export GRAFANA_USER=grizzly
export GRAFANA_TOKEN=mytoken

Eine Dashboard-Definition im Beispiel:

dashboard.yml
apiVersion: grizzly.grafana.com/v1alpha1
kind: Dashboard
metadata:
  folder: general
  name: uuid-of-dashboard
spec:
  schemaVersion: 2022122205
  tags:
    - templated with Grizzly
  time:
    from: now-6h
    to: now
  timepicker:
    hidden: false
    refresh_intervals:
      - 20s
      - 30s
      - 45s
  timezone: browser
  title: My Title
  uid: uuid-of-dashboard
  description: My Description
  editable: true
  liveNow: true
  refresh: 45s

  panels:

    # text panel
    - gridPos:
        x: 4
        "y": 0
        h: 3
        w: 2
      options:
        content: '<p style="text-align: center; font-size: 2em;">My Text</p>'
        mode: html
      type: text

    # json datasource panel
    - gridPos:
        x: 4
        "y": 6
        h: 3
        w: 1
      datasource:
        type: simpod-json-datasource
        uid: uuid-of-datasource
      fieldConfig:
        defaults:
          color:
            mode: thresholds
          noValue: "0"
          thresholds:
            steps:
              - color: green
                value: null
              - color: red
                value: 1
          unit: none
      options:
        colorMode: background
        reduceOptions:
          calcs:
            - count
          fields: /^attrs_name$/
          values: false
      targets:
        - data: |+
            {
              "myapp_api_endpoint": "/v1/objects/hosts",
              "myapp_request_data": {
                "attrs": [ "name", "address" ],
                "filter": "host.state != 0.0 && host.downtime_depth == 0.0 && host.acknowledgement == 0.0"
              }
            }
          refId: A
          target: myapp
          type: table
      title: " "
      type: stat

Beispiel-Definition einer Datasource:

datasource.yml
apiVersion: grizzly.grafana.com/v1alpha1
kind: Datasource
metadata:
    name: uuid-of-datasource
spec:
    access: proxy
    basicAuth: false
    basicAuthUser: ""
    database: ""
    isDefault: false
    jsonData:
        httpHeaderName1: myapp-user
        httpHeaderName2: myapp-password
        httpHeaderName3: myapp-url
    name: uuid-of-datasource
    orgId: 4711
    readOnly: false
    secureJsonFields:
        httpHeaderValue1: true
        httpHeaderValue2: true
        httpHeaderValue3: true
    type: simpod-json-datasource
    typeLogoUrl: ""
    uid: uuid-of-datasource
    url: http://myhost:port/api/
    user: ""
    withCredentials: false

Gibt es Unterschiede zum bestehenden Dashboard?

grr diff dashboard.yml

Datasource und Dashboard uploaden:

grr apply datasource.yml
grr apply dashboard.yml

Definitionen downloaden, um eine Vorlage für weitere Anpassungen zu erhalten:

grr get Datasource.my-uid
grr get Dashboard.my-uid

Built on 2023-03-17