Server Metrics mit InfluxDB und Grafana

Beitrag vom 03.01.2017

Nachdem ich vor ein paar Monaten meinen Homeserver zusammen gebaut hatte, wollte ich diesen ein wenig Überwachen und im Auge behalten. Gerade was Temperatur und Festplatten anging.

Eisenbart

Mit InfluxDB können mit simplen HTTP Requests Messdaten gespeichert werden. Das System ist recht dumm, dafür aber sau schnell und einfach zu bedienen.

Grafana stellt eine schöne klicky-bunti Oberfläche bereit, womit die Daten in Diagrammen dargestellt werden können. Die Software nutzt zum Beispiel das Network Operation Center des Chaos Communication Congress.

Siehe hier und hier.

Installation

Wie man die Software genau installiert erkläre ich nicht. Die Docs von InfluxDB und Grafana sind eigentlich ganz gut. Auch mit der Absicherung habe ich mich wenig bis gar nicht beschäftigt, da ich beides nur im lokalen Netzwerk auf einer eigenen VM laufen habe.

Datenerfassung

Wie schon erwähnt kann InfluxDB einfach mit HTTP Requests gefüttert werden. Die Anmeldung erfolgt über Basic Authentication. In der freien Wildbahn ist also SSL empfehlenswert.

Die meisten Werte meines Servers werden einfach via Bash Code ermittelt.

In einer separaten Datei mit dem Namen influxlib.sh befindet sich die Schnittstelle zu InfluxDB, welche dann in die eigentlichen Scripte eingebunden wird:

INFLUXHOST="http://influxdb.example.com:8086"
INFLUXUSER="user"
INFLUXPWD="password"
INFLUXDB="database"

# String maskieren, sofern dieser Sonderzeichen enthaelt
urlencode() {
    perl -MURI::Escape -lne 'print uri_escape($_)'
}

# Wert in InfluxDB schreiben
influxw() {
    curl -u "${INFLUXUSER}:${INFLUXPWD}" -i -XPOST "$INFLUXHOST/write?db=$INFLUXDB" --data-binary "$1 $(date +%s%N)"
}

Hier werden zum Beispiel die Temperaturdaten der Festplatten und der Verbrauch meiner USV geschrieben:

#!/bin/bash

cd "$(dirname "$0")"

# InfluxDB laden
. influxlib.sh

CHOST=$(hostname)

# Hilsfunktion: Property aus SMART auslesen
smartprop() {
    smartctl -a "$1" | grep -P "^$2\s" | awk -v ORS= "{$3}" | tr -d '[[:space:]]'
}

# Hilsfunktion: Property aus der APC USV via apcupsd auslesen
apcprop() {
    apcaccess -u -p "$1"
}

# Alle Platten durchlaufen
while read -r device
do

    # Temeratur, Seriennummer und Typ
    TEMPERAT=$(smartprop "$device" "[0-9]+ Temperature_Celsius" "print \$10" | urlencode)
    MODELNUM=$(smartprop "$device" "Device Model:" "for(i=3;i<=NF;++i)print \$i \" \"" | urlencode)

    ISSSD=0
    ROTATION=$(smartprop "$device" "Rotation Rate:" "print \$3")
    if [ "$ROTATION" == "Solid" ]; then
        ISSSD=1
    fi

    DATA="hddtemp,host=${CHOST},device=${device},model=${MODELNUM},isssd=${ISSSD} value=$TEMPERAT"
    echo "$DATA"

    influxw "$DATA"

done < <(smartctl -i --scan | awk '{print $1}')

# APC USV
APCNAME=$(apcprop "UPSNAME")
influxw "apcloadperc,device=$APCNAME value=$(apcprop "LOADPCT")"
influxw "apcnomwatts,device=$APCNAME value=$(apcprop "NOMPOWER")"

# InfluxDB kann nur begrenzt rechnen, also muss hier in der Shell schon gerechnet werden
# Es wird der aktuelle Wattverbrauch auf 2 Nachkommastellen berechnet
influxw "apcusagewatts,device=$APCNAME value=$(bc <<< "scale=2;$(apcprop "LOADPCT")*$(apcprop "NOMPOWER")/100")"

Folgende Werte werden hier ausgelesen:

  • Temperatur jeder Festplatte/SSD
  • Aktuelle Last auf der USV
  • Auslastung in Prozent der USV
  • Der reale Wattverbrauch

Grafana

Das Ergebnis sieht in Grafana am Ende so aus:

USV Stats

Die Diagramme können einfach zusammengeklickt werden. Es besteht auch die Möglichkeit, den JSON Code hinter einem Diagramm manuell zu editieren. Dies ist aber IMHO sehr umständlich und auch nicht notwenig.

JSON Code für die USV Auswertung:

{
  "aliasColors": {},
  "bars": false,
  "datasource": "public_eisenbart",
  "decimals": 2,
  "editable": true,
  "error": false,
  "fill": 1,
  "height": "300",
  "id": 9,
  "interval": ">5m",
  "legend": {
    "alignAsTable": true,
    "avg": true,
    "current": true,
    "max": true,
    "min": true,
    "show": true,
    "total": false,
    "values": true
  },
  "lines": true,
  "linewidth": 2,
  "links": [],
  "nullPointMode": "connected",
  "percentage": false,
  "pointradius": 5,
  "points": false,
  "renderer": "flot",
  "seriesOverrides": [
    {
      "alias": "Usage Watts",
      "yaxis": 2
    },
    {
      "alias": "Fail Charge Minutes",
      "yaxis": 2
    },
    {
      "alias": "Charge Left Minutes",
      "yaxis": 2
    },
    {
      "alias": "Minimale Ladung Minuten",
      "yaxis": 2
    },
    {
      "alias": "Kapazität Minuten",
      "yaxis": 2
    },
    {
      "alias": "Shutdown Trigger Minuten",
      "yaxis": 2
    }
  ],
  "span": 6,
  "stack": false,
  "steppedLine": false,
  "targets": [
    {
      "alias": "Kapazität Minuten",
      "dsType": "influxdb",
      "groupBy": [
        {
          "params": [
            "$interval"
          ],
          "type": "time"
        },
        {
          "params": [
            "null"
          ],
          "type": "fill"
        }
      ],
      "measurement": "apctimeleftminutes",
      "policy": "default",
      "refId": "E",
      "resultFormat": "time_series",
      "select": [
        [
          {
            "params": [
              "value"
            ],
            "type": "field"
          },
          {
            "params": [],
            "type": "mean"
          }
        ]
      ],
      "tags": []
    },
    {
      "alias": "Auslastung %",
      "dsType": "influxdb",
      "groupBy": [
        {
          "params": [
            "$interval"
          ],
          "type": "time"
        },
        {
          "params": [
            "null"
          ],
          "type": "fill"
        }
      ],
      "measurement": "apcloadperc",
      "policy": "default",
      "refId": "A",
      "resultFormat": "time_series",
      "select": [
        [
          {
            "params": [
              "value"
            ],
            "type": "field"
          },
          {
            "params": [],
            "type": "mean"
          }
        ]
      ],
      "tags": []
    },
    {
      "alias": "Batterie geladen %",
      "dsType": "influxdb",
      "groupBy": [
        {
          "params": [
            "$interval"
          ],
          "type": "time"
        },
        {
          "params": [
            "null"
          ],
          "type": "fill"
        }
      ],
      "measurement": "apcbattchargeperc",
      "policy": "default",
      "refId": "B",
      "resultFormat": "time_series",
      "select": [
        [
          {
            "params": [
              "value"
            ],
            "type": "field"
          },
          {
            "params": [],
            "type": "mean"
          }
        ]
      ],
      "tags": []
    },
    {
      "alias": "Shutdown Trigger %",
      "dsType": "influxdb",
      "groupBy": [
        {
          "params": [
            "$interval"
          ],
          "type": "time"
        },
        {
          "params": [
            "null"
          ],
          "type": "fill"
        }
      ],
      "measurement": "apcfailbatperc",
      "policy": "default",
      "refId": "C",
      "resultFormat": "time_series",
      "select": [
        [
          {
            "params": [
              "value"
            ],
            "type": "field"
          },
          {
            "params": [],
            "type": "mean"
          }
        ]
      ],
      "tags": []
    },
    {
      "alias": "Shutdown Trigger Minuten",
      "dsType": "influxdb",
      "groupBy": [
        {
          "params": [
            "$interval"
          ],
          "type": "time"
        },
        {
          "params": [
            "null"
          ],
          "type": "fill"
        }
      ],
      "measurement": "apcfailminutes",
      "policy": "default",
      "refId": "D",
      "resultFormat": "time_series",
      "select": [
        [
          {
            "params": [
              "value"
            ],
            "type": "field"
          },
          {
            "params": [],
            "type": "mean"
          }
        ]
      ],
      "tags": []
    }
  ],
  "thresholds": [],
  "timeFrom": null,
  "timeShift": null,
  "title": "Status",
  "tooltip": {
    "msResolution": false,
    "shared": true,
    "sort": 0,
    "value_type": "individual"
  },
  "type": "graph",
  "xaxis": {
    "mode": "time",
    "name": null,
    "show": true,
    "values": []
  },
  "yaxes": [
    {
      "format": "percent",
      "label": "Prozent",
      "logBase": 1,
      "max": null,
      "min": null,
      "show": true
    },
    {
      "format": "m",
      "label": "Minuten",
      "logBase": 1,
      "max": null,
      "min": null,
      "show": true
    }
  ]
}

Hallo Internet

Mein Name ist Christian, vom Beruf bin ich Anwendungsentwickler.

In meiner Freizeit beschäftige ich mich mit verschiedensten Technologien. Hier sammele ich Dinge, die für mich interessant waren oder sind.