added grafana and prometheus

This commit is contained in:
2026-02-21 15:00:49 +01:00
parent d9347ada81
commit d276792607
6 changed files with 825 additions and 0 deletions

View File

@@ -34,3 +34,43 @@ services:
redis: redis:
image: redis:alpine image: redis:alpine
restart: unless-stopped restart: unless-stopped
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- ./data/prometheus:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=15d'
- '--storage.tsdb.retention.size=512MB'
depends_on:
- blackbox-exporter
blackbox-exporter:
image: prom/blackbox-exporter:latest
restart: unless-stopped
volumes:
- ./prometheus/blackbox.yml:/etc/blackbox-exporter/config.yml
grafana:
image: grafana/grafana:latest
restart: unless-stopped
env_file: ./app/.env.db
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./data/grafana:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=changeme
- GF_SECURITY_ADMIN_USER=admin
- GF_USERS_ALLOW_SIGN_UP=false
# Only allow access from localhost/reverse proxy
- GF_SERVER_ROOT_URL=http://localhost:3000
depends_on:
- prometheus
- db

View File

@@ -0,0 +1,11 @@
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: ''
type: file
disableDeletion: false
updateIntervalSeconds: 30
options:
path: /etc/grafana/provisioning/dashboards

View File

@@ -0,0 +1,685 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1,
"links": [],
"panels": [
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"0": {
"color": "red",
"text": "DOWN"
},
"1": {
"color": "green",
"text": "UP"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "green",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "Prometheus",
"expr": "probe_success{job=\"blackbox_http\"}",
"legendFormat": "{{ instance }}",
"refId": "A"
},
{
"datasource": "Prometheus",
"expr": "probe_success{job=\"blackbox_tcp\"}",
"legendFormat": "{{ instance }}",
"refId": "B"
}
],
"title": "Service Health",
"type": "stat"
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 4
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "Prometheus",
"expr": "probe_duration_seconds{job=\"blackbox_http\"} * 1000",
"legendFormat": "{{ instance }}",
"refId": "A"
}
],
"title": "HTTP Response Time (ms)",
"type": "timeseries"
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "yellow",
"value": 95
},
{
"color": "green",
"value": 99
}
]
},
"unit": "percent"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 4
},
"id": 3,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "Prometheus",
"expr": "avg_over_time(probe_success{job=\"blackbox_http\", instance=\"http://tutortool:5000/grafana/health\"}[24h]) * 100",
"legendFormat": "Flask App",
"refId": "A"
}
],
"title": "Uptime % (24h)",
"type": "stat"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 12
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT COUNT(*) as value FROM token",
"refId": "A"
}
],
"title": "Total Registered Users",
"type": "stat"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 12
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT COUNT(*) as value FROM deck",
"refId": "A"
}
],
"title": "Total Decks",
"type": "stat"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 12
},
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT COUNT(*) as value FROM course",
"refId": "A"
}
],
"title": "Active Courses",
"type": "stat"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 12
},
"id": 7,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": true
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT c.name as \"metric\", COUNT(t.id) as \"value\" FROM course c LEFT JOIN token t ON c.id = t.active_course_id GROUP BY c.name",
"refId": "A"
}
],
"title": "Users per Active Course",
"type": "stat"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 16
},
"id": 8,
"options": {
"barRadius": 0,
"barWidth": 0.7,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"orientation": "horizontal",
"showValue": "never",
"stacking": "none",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'GRNVS Tutorium SS 25' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST",
"refId": "A"
}
],
"title": "Users with Access per Deck — GRNVS Tutorium SS 25",
"type": "barchart"
},
{
"datasource": "PostgreSQL",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "Users with Access",
"axisPlacement": "auto",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 26
},
"id": 9,
"options": {
"barRadius": 0,
"barWidth": 0.7,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"orientation": "horizontal",
"showValue": "never",
"stacking": "none",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"orientation": "horizontal",
"showValue": "auto",
"stacking": "none",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xField": "Deck",
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"pluginVersion": "12.3.3",
"targets": [
{
"datasource": "PostgreSQL",
"format": "table",
"rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'IT-Sec Tutorium WS 25/26' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST",
"refId": "A"
}
],
"title": "Users with Access per Deck — IT-Sec Tutorium WS 25/26",
"type": "barchart"
}
],
"preload": false,
"refresh": "1m",
"schemaVersion": 42,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Tutor Tool",
"uid": "tutor-tool",
"version": 3
}

View File

@@ -0,0 +1,26 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
- name: PostgreSQL
type: postgres
access: proxy
url: db:5432
# These must match your .env.db values
user: $POSTGRES_USER
secureJsonData:
password: $POSTGRES_PASSWORD
jsonData:
database: $POSTGRES_DB
sslmode: disable
maxOpenConns: 5
maxIdleConns: 2
connMaxLifetime: 14400
postgresVersion: 1700
editable: false

14
prometheus/blackbox.yml Normal file
View File

@@ -0,0 +1,14 @@
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
valid_status_codes: [200]
method: GET
fail_if_ssl: false
fail_if_not_ssl: false
tcp_connect:
prober: tcp
timeout: 5s

49
prometheus/prometheus.yml Normal file
View File

@@ -0,0 +1,49 @@
global:
scrape_interval: 30s
evaluation_interval: 30s
scrape_configs:
# --- Health checks via Blackbox Exporter ---
- job_name: 'blackbox_http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- http://tutortool:5000/grafana/health
- http://tutortool:5000/
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
# --- TCP check for Postgres ---
- job_name: 'blackbox_tcp'
metrics_path: /probe
params:
module: [tcp_connect]
static_configs:
- targets:
- db:5432
- redis:6379
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
# --- Blackbox exporter itself ---
- job_name: 'blackbox_exporter'
static_configs:
- targets: ['blackbox-exporter:9115']
# --- Prometheus itself ---
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']