feat(monitoring): add grafana, prometheus, node_exporter roles
- grafana: postgres backend, provisioning (prometheus datasource + node/NUT dashboards) - prometheus: native service, scrape config via host_vars, localhost bind - node_exporter: host metrics exporter (localhost:9100)
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
# Grafana Role
|
||||
|
||||
Installs and configures Grafana as a system service with PostgreSQL backend.
|
||||
|
||||
## Description
|
||||
|
||||
This role:
|
||||
|
||||
- Installs Grafana from official repositories (Arch Linux or Debian/Ubuntu)
|
||||
- Creates a dedicated PostgreSQL database and user
|
||||
- Configures Grafana to use PostgreSQL
|
||||
- Optionally configures Nginx reverse proxy
|
||||
- Follows the shared services pattern
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `postgres` role (for database backend)
|
||||
|
||||
## Variables
|
||||
|
||||
### Required Variables (Override with Ansible Vault!)
|
||||
|
||||
```yaml
|
||||
grafana_db_password: changeme
|
||||
grafana_admin_password: changeme
|
||||
grafana_secret_key: changeme
|
||||
```
|
||||
|
||||
### Server Configuration
|
||||
|
||||
```yaml
|
||||
grafana_http_port: 3000
|
||||
grafana_domain: localhost
|
||||
grafana_root_url: "%(protocol)s://%(domain)s:%(http_port)s/"
|
||||
```
|
||||
|
||||
### Database Configuration
|
||||
|
||||
```yaml
|
||||
grafana_db_type: postgres
|
||||
grafana_db_name: grafana
|
||||
grafana_db_user: grafana_user
|
||||
grafana_db_host: localhost
|
||||
grafana_db_port: 5432
|
||||
grafana_db_ssl_mode: disable
|
||||
```
|
||||
|
||||
### Admin User
|
||||
|
||||
```yaml
|
||||
grafana_admin_user: admin
|
||||
```
|
||||
|
||||
### Analytics
|
||||
|
||||
```yaml
|
||||
grafana_reporting_enabled: false
|
||||
grafana_check_for_updates: false
|
||||
```
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
|
||||
```yaml
|
||||
grafana_nginx_enabled: false
|
||||
grafana_nginx_hostname: grafana.local
|
||||
```
|
||||
|
||||
## Example Playbook
|
||||
|
||||
```yaml
|
||||
---
|
||||
- hosts: monitoring
|
||||
roles:
|
||||
- role: grafana
|
||||
vars:
|
||||
grafana_domain: monitoring.example.com
|
||||
grafana_nginx_enabled: true
|
||||
grafana_nginx_hostname: grafana.example.com
|
||||
grafana_admin_password: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
...
|
||||
grafana_db_password: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
...
|
||||
grafana_secret_key: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
...
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Database Isolation
|
||||
|
||||
Grafana gets its own PostgreSQL database with a dedicated user that has minimal privileges:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE grafana;
|
||||
CREATE USER grafana_user WITH PASSWORD 'xxx';
|
||||
GRANT ALL PRIVILEGES ON DATABASE grafana TO grafana_user;
|
||||
ALTER USER grafana_user NOSUPERUSER NOCREATEDB NOCREATEROLE;
|
||||
```
|
||||
|
||||
### Service Layout
|
||||
|
||||
```
|
||||
System Services:
|
||||
├── PostgreSQL (port 5432, localhost only)
|
||||
│ └── grafana database → grafana_user
|
||||
└── Grafana (port 3000, localhost only)
|
||||
└── Nginx (port 80) → reverse proxy (optional)
|
||||
```
|
||||
|
||||
## Post-Installation
|
||||
|
||||
### Access Grafana
|
||||
|
||||
**Direct access:**
|
||||
|
||||
```
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
**With Nginx reverse proxy:**
|
||||
|
||||
```
|
||||
http://grafana.local
|
||||
```
|
||||
|
||||
### Default Credentials
|
||||
|
||||
- Username: `admin` (or value of `grafana_admin_user`)
|
||||
- Password: Value of `grafana_admin_password`
|
||||
|
||||
**Important:** Change the admin password immediately after first login!
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
systemctl status grafana-server # Debian/Ubuntu
|
||||
systemctl status grafana # Arch Linux
|
||||
|
||||
# Check database connection
|
||||
sudo -u postgres psql -c "\l" | grep grafana
|
||||
sudo -u postgres psql -c "\du" | grep grafana
|
||||
|
||||
# Check configuration
|
||||
sudo cat /etc/grafana/grafana.ini | grep -A 5 "\[database\]"
|
||||
|
||||
# Check logs
|
||||
journalctl -u grafana-server -f # Debian/Ubuntu
|
||||
journalctl -u grafana -f # Arch Linux
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Passwords:** Always use Ansible Vault for sensitive variables
|
||||
2. **Database:** Grafana user has minimal privileges (no superuser, can't create databases/roles)
|
||||
3. **Network:** Grafana binds to localhost by default, use Nginx for external access
|
||||
4. **Secret Key:** Used for signing cookies and other security features - keep it secret!
|
||||
|
||||
## Nginx Integration
|
||||
|
||||
When `grafana_nginx_enabled: true`, the role:
|
||||
|
||||
- Deploys an Nginx vhost configuration to `/etc/nginx/conf.d/grafana.conf`
|
||||
- Configures reverse proxy with WebSocket support for live updates
|
||||
- Reloads Nginx gracefully
|
||||
|
||||
When disabled, the vhost configuration is removed.
|
||||
|
||||
## OS Support
|
||||
|
||||
- **Arch Linux:** Installs from community repository
|
||||
- **Debian/Ubuntu:** Installs from official Grafana APT repository
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Grafana won't start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
journalctl -u grafana-server -n 50 # Debian/Ubuntu
|
||||
journalctl -u grafana -n 50 # Arch Linux
|
||||
|
||||
# Check configuration syntax
|
||||
sudo grafana-cli admin validate-config
|
||||
```
|
||||
|
||||
### Can't connect to PostgreSQL
|
||||
|
||||
```bash
|
||||
# Verify PostgreSQL is running
|
||||
systemctl status postgresql
|
||||
|
||||
# Test database connection
|
||||
psql -h localhost -U grafana_user -d grafana
|
||||
|
||||
# Check PostgreSQL logs
|
||||
journalctl -u postgresql -n 50
|
||||
```
|
||||
|
||||
### 502 Bad Gateway with Nginx
|
||||
|
||||
```bash
|
||||
# Verify Grafana is running
|
||||
systemctl status grafana-server
|
||||
|
||||
# Check if Grafana is listening
|
||||
ss -tlnp | grep 3000
|
||||
|
||||
# Test direct access
|
||||
curl http://localhost:3000
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Grafana Documentation](https://grafana.com/docs/grafana/latest/)
|
||||
- [Grafana Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/)
|
||||
- [PostgreSQL Data Source](https://grafana.com/docs/grafana/latest/datasources/postgres/)
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# Grafana version
|
||||
grafana_version: "stable"
|
||||
|
||||
# Network configuration
|
||||
grafana_http_port: 3200
|
||||
grafana_domain: grafana.nas.local
|
||||
grafana_root_url: "https://{{ grafana_domain }}"
|
||||
|
||||
# PostgreSQL Database configuration
|
||||
# The user and database will be created by this role.
|
||||
# REQUIRED: The password must be set in your inventory (e.g., in inventory/host_vars/your_host.yml).
|
||||
grafana_postgres_type: postgres
|
||||
grafana_postgres_host: 127.0.0.1
|
||||
grafana_postgres_port: 5432
|
||||
grafana_postgres_db_name: grafana
|
||||
grafana_postgres_user: grafana
|
||||
# grafana_postgres_password: "" # Intentionally undefined - role will fail if not set
|
||||
grafana_postgres_ssl_mode: "prefer" # 'disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full'
|
||||
|
||||
# Security
|
||||
# REQUIRED: These values must be set in your inventory.
|
||||
grafana_admin_user: admin
|
||||
# grafana_admin_password: "" # Intentionally undefined - role will fail if not set
|
||||
# grafana_secret_key: "" # Intentionally undefined - role will fail if not set
|
||||
|
||||
# Analytics
|
||||
grafana_reporting_enabled: false
|
||||
grafana_check_for_updates: false
|
||||
|
||||
# Nginx reverse proxy
|
||||
grafana_nginx_enabled: true
|
||||
grafana_nginx_hostname: "{{ grafana_domain }}"
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"title": "Node Exporter - Host Overview",
|
||||
"uid": "node-exporter-overview",
|
||||
"tags": ["node-exporter", "linux", "system"],
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "1m",
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"graphTooltip": 1,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "instance",
|
||||
"label": "Host",
|
||||
"type": "query",
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"query": { "query": "label_values(node_uname_info, instance)", "refId": "A" },
|
||||
"refresh": 2,
|
||||
"multi": false,
|
||||
"includeAll": false,
|
||||
"sort": 1,
|
||||
"current": { "selected": false, "text": "", "value": "" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "stat",
|
||||
"title": "Uptime",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "dtdurations", "color": { "mode": "thresholds" } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "none", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "node_time_seconds{instance=\"$instance\"} - node_boot_time_seconds{instance=\"$instance\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "stat",
|
||||
"title": "CPU Cores",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "thresholds" } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "none", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "count(count by (cpu) (node_cpu_seconds_total{mode=\"idle\", instance=\"$instance\"}))" } ]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "stat",
|
||||
"title": "Load 1m",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "thresholds" } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "none", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "node_load1{instance=\"$instance\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "stat",
|
||||
"title": "Memory Used",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 75 }, { "color": "red", "value": 90 } ] } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "background", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "100 * (1 - (node_memory_MemAvailable_bytes{instance=\"$instance\"} / node_memory_MemTotal_bytes{instance=\"$instance\"}))" } ]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "timeseries",
|
||||
"title": "CPU Usage",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 20, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [ { "refId": "A", "expr": "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\", instance=\"$instance\"}[$__rate_interval])) * 100)", "legendFormat": "CPU used" } ]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "timeseries",
|
||||
"title": "System Load",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 10, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [
|
||||
{ "refId": "A", "expr": "node_load1{instance=\"$instance\"}", "legendFormat": "load1" },
|
||||
{ "refId": "B", "expr": "node_load5{instance=\"$instance\"}", "legendFormat": "load5" },
|
||||
{ "refId": "C", "expr": "node_load15{instance=\"$instance\"}", "legendFormat": "load15" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "timeseries",
|
||||
"title": "Memory Usage %",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 20, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [ { "refId": "A", "expr": "100 * (1 - (node_memory_MemAvailable_bytes{instance=\"$instance\"} / node_memory_MemTotal_bytes{instance=\"$instance\"}))", "legendFormat": "Used %" } ]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "timeseries",
|
||||
"title": "Memory",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "bytes", "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 20, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [
|
||||
{ "refId": "A", "expr": "node_memory_MemTotal_bytes{instance=\"$instance\"} - node_memory_MemAvailable_bytes{instance=\"$instance\"}", "legendFormat": "Used" },
|
||||
{ "refId": "B", "expr": "node_memory_Cached_bytes{instance=\"$instance\"} + node_memory_Buffers_bytes{instance=\"$instance\"}", "legendFormat": "Cached+Buffers" },
|
||||
{ "refId": "C", "expr": "node_memory_MemAvailable_bytes{instance=\"$instance\"}", "legendFormat": "Available" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "timeseries",
|
||||
"title": "Filesystem Usage %",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 15, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [ { "refId": "A", "expr": "100 * (1 - (node_filesystem_avail_bytes{instance=\"$instance\", fstype!~\"tmpfs|overlay|squashfs|ramfs\"} / node_filesystem_size_bytes{instance=\"$instance\", fstype!~\"tmpfs|overlay|squashfs|ramfs\"}))", "legendFormat": "{{mountpoint}}" } ]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "timeseries",
|
||||
"title": "Disk I/O",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "Bps", "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 10, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [
|
||||
{ "refId": "A", "expr": "rate(node_disk_read_bytes_total{instance=\"$instance\", device!~\"ram.*|loop.*\"}[$__rate_interval])", "legendFormat": "{{device}} read" },
|
||||
{ "refId": "B", "expr": "-rate(node_disk_written_bytes_total{instance=\"$instance\", device!~\"ram.*|loop.*\"}[$__rate_interval])", "legendFormat": "{{device}} write" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "timeseries",
|
||||
"title": "Network Traffic",
|
||||
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 28 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "Bps", "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 10, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [
|
||||
{ "refId": "A", "expr": "rate(node_network_receive_bytes_total{instance=\"$instance\", device!~\"lo|veth.*|cni.*|br-.*|docker.*\"}[$__rate_interval])", "legendFormat": "{{device}} in" },
|
||||
{ "refId": "B", "expr": "-rate(node_network_transmit_bytes_total{instance=\"$instance\", device!~\"lo|veth.*|cni.*|br-.*|docker.*\"}[$__rate_interval])", "legendFormat": "{{device}} out" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"title": "UPS - NUT Status",
|
||||
"uid": "nut-status",
|
||||
"tags": ["nut", "ups", "power"],
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "30s",
|
||||
"time": { "from": "now-24h", "to": "now" },
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"graphTooltip": 1,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "instance",
|
||||
"label": "UPS",
|
||||
"type": "query",
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"query": { "query": "label_values(network_ups_tools_device_info, instance)", "refId": "A" },
|
||||
"refresh": 2,
|
||||
"multi": false,
|
||||
"includeAll": false,
|
||||
"sort": 1,
|
||||
"current": { "selected": false, "text": "", "value": "" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "stat",
|
||||
"title": "Battery Charge",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "orange", "value": 25 }, { "color": "green", "value": 50 } ] } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "background", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_battery_charge{instance=\"$instance\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "stat",
|
||||
"title": "UPS Load",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 70 }, { "color": "red", "value": 85 } ] } }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "background", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_ups_load{instance=\"$instance\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "stat",
|
||||
"title": "On Battery",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "none", "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] }, "mappings": [ { "type": "value", "options": { "0": { "text": "No", "color": "green" }, "1": { "text": "YES", "color": "red" } } } ] }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "background", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"OB\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "stat",
|
||||
"title": "Low Battery",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "none", "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] }, "mappings": [ { "type": "value", "options": { "0": { "text": "No", "color": "green" }, "1": { "text": "YES", "color": "red" } } } ] }, "overrides": [] },
|
||||
"options": { "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, "colorMode": "background", "graphMode": "none", "textMode": "auto", "justifyMode": "auto", "orientation": "auto" },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"LB\"}" } ]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "timeseries",
|
||||
"title": "Battery Charge",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 20, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_battery_charge{instance=\"$instance\"}", "legendFormat": "charge" } ]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "timeseries",
|
||||
"title": "UPS Load",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 20, "lineWidth": 1, "drawStyle": "line", "showPoints": "never" } }, "overrides": [] },
|
||||
"targets": [ { "refId": "A", "expr": "network_ups_tools_ups_load{instance=\"$instance\"}", "legendFormat": "load" } ]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "timeseries",
|
||||
"title": "Status Flags",
|
||||
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 12 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": { "defaults": { "unit": "none", "min": 0, "max": 1, "color": { "mode": "palette-classic" }, "custom": { "fillOpacity": 0, "lineWidth": 1, "drawStyle": "line", "showPoints": "never", "staircase": true } }, "overrides": [] },
|
||||
"targets": [
|
||||
{ "refId": "A", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"OL\"}", "legendFormat": "On line" },
|
||||
{ "refId": "B", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"OB\"}", "legendFormat": "On battery" },
|
||||
{ "refId": "C", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"LB\"}", "legendFormat": "Low battery" },
|
||||
{ "refId": "D", "expr": "network_ups_tools_ups_status{instance=\"$instance\", flag=\"CHRG\"}", "legendFormat": "Charging" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: prometheus
|
||||
uid: prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://127.0.0.1:9090
|
||||
isDefault: true
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
# Grafana handlers
|
||||
|
||||
- name: Restart grafana
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ grafana_service }}"
|
||||
state: restarted
|
||||
|
||||
- name: Reload nginx
|
||||
ansible.builtin.systemd:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
# No meta dependencies: shared services (postgres) are deployed once via
|
||||
# playbook role ordering (tagged 'infra'), not re-run on every deploy of
|
||||
# this role. See playbooks/*.yml.
|
||||
dependencies: []
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
# Grafana installation and configuration
|
||||
|
||||
- name: Validate required passwords are set
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- grafana_postgres_password is defined
|
||||
- grafana_postgres_password | length >= 12
|
||||
- grafana_admin_password is defined
|
||||
- grafana_admin_password | length >= 12
|
||||
- grafana_secret_key is defined
|
||||
- grafana_secret_key | length >= 16
|
||||
fail_msg: |
|
||||
grafana_postgres_password, grafana_admin_password, and grafana_secret_key are required.
|
||||
See roles/grafana/defaults/main.yml for configuration instructions.
|
||||
success_msg: "Password validation passed"
|
||||
|
||||
- name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "debian.yml"
|
||||
|
||||
# Debian/Ubuntu installation
|
||||
- name: Ensure apt keyrings directory exists (Debian/Ubuntu)
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: "0755"
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Install Grafana APT key (Debian/Ubuntu)
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ grafana_apt_key_url }}"
|
||||
dest: /etc/apt/keyrings/grafana.asc
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Add Grafana APT repository (Debian/Ubuntu)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: grafana
|
||||
types: [deb]
|
||||
uris: "{{ grafana_apt_repo_uri }}"
|
||||
suites: "{{ grafana_apt_repo_suite }}"
|
||||
components: [main]
|
||||
signed_by: /etc/apt/keyrings/grafana.asc
|
||||
state: present
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Install Grafana package (Debian/Ubuntu)
|
||||
ansible.builtin.apt:
|
||||
name: "{{ grafana_package }}={{ grafana_version }}"
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
# Arch Linux installation
|
||||
- name: Install Grafana package (Arch Linux)
|
||||
community.general.pacman:
|
||||
name: "{{ grafana_package }}"
|
||||
state: present
|
||||
when: ansible_facts['os_family'] == 'Archlinux'
|
||||
|
||||
# Database setup
|
||||
- name: Create PostgreSQL database
|
||||
community.postgresql.postgresql_db:
|
||||
name: "{{ grafana_postgres_db_name }}"
|
||||
state: present
|
||||
become: true
|
||||
become_user: "{{ postgres_admin_user }}"
|
||||
|
||||
- name: Create PostgreSQL user
|
||||
community.postgresql.postgresql_user:
|
||||
name: "{{ grafana_postgres_user }}"
|
||||
password: "{{ grafana_postgres_password }}"
|
||||
state: present
|
||||
become: true
|
||||
become_user: "{{ postgres_admin_user }}"
|
||||
|
||||
- name: Grant database privileges
|
||||
community.postgresql.postgresql_privs:
|
||||
login_db: "{{ grafana_postgres_db_name }}"
|
||||
roles: "{{ grafana_postgres_user }}"
|
||||
type: database
|
||||
privs: ALL
|
||||
state: present
|
||||
become: true
|
||||
become_user: "{{ postgres_admin_user }}"
|
||||
|
||||
- name: Grant schema privileges
|
||||
community.postgresql.postgresql_privs:
|
||||
login_db: "{{ grafana_postgres_db_name }}"
|
||||
roles: "{{ grafana_postgres_user }}"
|
||||
type: schema
|
||||
objs: public
|
||||
privs: CREATE,USAGE
|
||||
state: present
|
||||
become: true
|
||||
become_user: "{{ postgres_admin_user }}"
|
||||
|
||||
- name: Ensure minimal privileges
|
||||
community.postgresql.postgresql_user:
|
||||
name: "{{ grafana_postgres_user }}"
|
||||
role_attr_flags: NOSUPERUSER,NOCREATEDB,NOCREATEROLE
|
||||
state: present
|
||||
become: true
|
||||
become_user: "{{ postgres_admin_user }}"
|
||||
|
||||
# Grafana configuration
|
||||
- name: Configure Grafana
|
||||
community.general.ini_file:
|
||||
path: "{{ grafana_config_path }}"
|
||||
section: "{{ item.section }}"
|
||||
option: "{{ item.option }}"
|
||||
value: "{{ item.value }}"
|
||||
mode: "0640"
|
||||
owner: root
|
||||
group: "{{ grafana_group }}"
|
||||
loop:
|
||||
- section: "server"
|
||||
option: "http_port"
|
||||
value: "{{ grafana_http_port }}"
|
||||
- section: "server"
|
||||
option: "domain"
|
||||
value: "{{ grafana_domain }}"
|
||||
- section: "server"
|
||||
option: "root_url"
|
||||
value: "{{ grafana_root_url }}"
|
||||
- section: "database"
|
||||
option: "type"
|
||||
value: "{{ grafana_postgres_type }}"
|
||||
- section: "database"
|
||||
option: "host"
|
||||
value: "{{ grafana_postgres_host }}:{{ grafana_postgres_port }}"
|
||||
- section: "database"
|
||||
option: "name"
|
||||
value: "{{ grafana_postgres_db_name }}"
|
||||
- section: "database"
|
||||
option: "user"
|
||||
value: "{{ grafana_postgres_user }}"
|
||||
- section: "database"
|
||||
option: "password"
|
||||
value: "{{ grafana_postgres_password }}"
|
||||
- section: "database"
|
||||
option: "ssl_mode"
|
||||
value: "{{ grafana_postgres_ssl_mode }}"
|
||||
- section: "security"
|
||||
option: "admin_user"
|
||||
value: "{{ grafana_admin_user }}"
|
||||
- section: "security"
|
||||
option: "admin_password"
|
||||
value: "{{ grafana_admin_password }}"
|
||||
- section: "security"
|
||||
option: "secret_key"
|
||||
value: "{{ grafana_secret_key }}"
|
||||
- section: "analytics"
|
||||
option: "reporting_enabled"
|
||||
value: "{{ grafana_reporting_enabled }}"
|
||||
- section: "analytics"
|
||||
option: "check_for_updates"
|
||||
value: "{{ grafana_check_for_updates }}"
|
||||
notify: Restart grafana
|
||||
|
||||
# Provisioning (datasources + dashboards as code)
|
||||
- name: Ensure Grafana provisioning directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ grafana_user }}"
|
||||
group: "{{ grafana_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ grafana_provisioning_path }}/datasources"
|
||||
- "{{ grafana_provisioning_path }}/dashboards"
|
||||
|
||||
- name: Deploy Prometheus datasource provisioning
|
||||
ansible.builtin.copy:
|
||||
src: prometheus-datasource.yaml
|
||||
dest: "{{ grafana_provisioning_path }}/datasources/prometheus.yaml"
|
||||
owner: "{{ grafana_user }}"
|
||||
group: "{{ grafana_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart grafana
|
||||
|
||||
- name: Deploy dashboard provider provisioning
|
||||
ansible.builtin.template:
|
||||
src: dashboards-provider.yaml.j2
|
||||
dest: "{{ grafana_provisioning_path }}/dashboards/dashboards.yaml"
|
||||
owner: "{{ grafana_user }}"
|
||||
group: "{{ grafana_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart grafana
|
||||
|
||||
- name: Deploy node_exporter dashboard
|
||||
ansible.builtin.copy:
|
||||
src: node-exporter-dashboard.json
|
||||
dest: "{{ grafana_provisioning_path }}/dashboards/node-exporter.json"
|
||||
owner: "{{ grafana_user }}"
|
||||
group: "{{ grafana_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart grafana
|
||||
|
||||
- name: Deploy NUT dashboard
|
||||
ansible.builtin.copy:
|
||||
src: nut-status-dashboard.json
|
||||
dest: "{{ grafana_provisioning_path }}/dashboards/nut-status.json"
|
||||
owner: "{{ grafana_user }}"
|
||||
group: "{{ grafana_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart grafana
|
||||
|
||||
# Service management
|
||||
- name: Enable and start Grafana service
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ grafana_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
# Nginx integration
|
||||
- name: Provision TLS certificate for Grafana
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml"
|
||||
vars:
|
||||
certbot_hostname: "{{ grafana_nginx_hostname }}"
|
||||
when: grafana_nginx_enabled
|
||||
|
||||
- name: Deploy nginx vhost
|
||||
ansible.builtin.template:
|
||||
src: nginx-vhost.conf.j2
|
||||
dest: "{{ nginx_conf_dir | default('/etc/nginx/conf.d') }}/grafana.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: grafana_nginx_enabled
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Remove nginx vhost when disabled
|
||||
ansible.builtin.file:
|
||||
path: "{{ nginx_conf_dir | default('/etc/nginx/conf.d') }}/grafana.conf"
|
||||
state: absent
|
||||
when: not grafana_nginx_enabled
|
||||
notify: Reload nginx
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: default
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 30
|
||||
options:
|
||||
path: {{ grafana_provisioning_path }}/dashboards
|
||||
@@ -0,0 +1,53 @@
|
||||
# Grafana vhost with Let's Encrypt (Certbot)
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ grafana_nginx_hostname }};
|
||||
|
||||
# Certbot webroot for ACME challenges
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect to HTTPS
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name {{ grafana_nginx_hostname }};
|
||||
|
||||
# Let's Encrypt certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/{{ grafana_nginx_hostname }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ grafana_nginx_hostname }}/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }};
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
{% if nginx_log_backend | default('journald') == 'journald' %}
|
||||
access_log syslog:server=unix:/dev/log,nohostname,tag=nginx_grafana;
|
||||
error_log syslog:server=unix:/dev/log,nohostname,tag=nginx_grafana;
|
||||
{% else %}
|
||||
access_log /var/log/nginx/{{ grafana_nginx_hostname }}_access.log main;
|
||||
error_log /var/log/nginx/{{ grafana_nginx_hostname }}_error.log;
|
||||
{% endif %}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{{ grafana_http_port }};
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# WebSocket support for live updates
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Arch Linux specific variables for Grafana
|
||||
|
||||
grafana_package: grafana
|
||||
grafana_service: grafana
|
||||
grafana_user: grafana
|
||||
grafana_group: grafana
|
||||
grafana_config_path: /etc/grafana.ini
|
||||
grafana_data_path: /var/lib/grafana
|
||||
grafana_provisioning_path: /var/lib/grafana/conf/provisioning
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Debian/Ubuntu specific variables for Grafana
|
||||
|
||||
grafana_package: grafana
|
||||
grafana_service: grafana-server
|
||||
grafana_user: grafana
|
||||
grafana_group: grafana
|
||||
grafana_config_path: /etc/grafana/grafana.ini
|
||||
grafana_data_path: /var/lib/grafana
|
||||
grafana_provisioning_path: /etc/grafana/provisioning
|
||||
grafana_apt_key_url: https://apt.grafana.com/gpg.key
|
||||
grafana_apt_repo_uri: https://apt.grafana.com
|
||||
grafana_apt_repo_suite: stable
|
||||
@@ -0,0 +1,43 @@
|
||||
# node_exporter — Prometheus exporter for host metrics
|
||||
|
||||
Exposes machine metrics (CPU, memory, disk, filesystem, network) for Prometheus.
|
||||
|
||||
## Supported distributions
|
||||
|
||||
- Arch Linux (package `prometheus-node-exporter`, extra repo)
|
||||
- Debian/Ubuntu (package `prometheus-node-exporter`)
|
||||
|
||||
## Configuration
|
||||
|
||||
See [defaults/main.yml](defaults/main.yml).
|
||||
|
||||
Optional:
|
||||
|
||||
```yaml
|
||||
node_exporter_listen_address: "127.0.0.1:9100"
|
||||
```
|
||||
|
||||
Keep localhost-only — Prometheus scrapes it locally on the same host. To add
|
||||
collectors (e.g. `--collector.systemd`, `--collector.textfile.directory`), extend
|
||||
`node_exporter_listen_address` or override the args in host_vars.
|
||||
|
||||
## Pairing with Prometheus
|
||||
|
||||
```yaml
|
||||
prometheus_scrape_configs:
|
||||
- job_name: 'node'
|
||||
static_configs:
|
||||
- targets: ['localhost:9100']
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
```bash
|
||||
systemctl status prometheus-node-exporter
|
||||
curl -s http://127.0.0.1:9100/metrics | head
|
||||
journalctl -u prometheus-node-exporter -f
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
None. Standalone exporter.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# Prometheus node_exporter configuration
|
||||
|
||||
# Address the exporter listens on. Keep localhost-only — Prometheus scrapes it
|
||||
# locally on the same host; no need to expose on the network.
|
||||
node_exporter_listen_address: "127.0.0.1:9100"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart node_exporter
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ node_exporter_service }}"
|
||||
state: restarted
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
dependencies: []
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "debian.yml"
|
||||
|
||||
- name: Install node_exporter
|
||||
ansible.builtin.package:
|
||||
name: "{{ node_exporter_package }}"
|
||||
state: present
|
||||
|
||||
- name: Deploy node_exporter configuration
|
||||
ansible.builtin.template:
|
||||
src: env.conf.j2
|
||||
dest: "{{ node_exporter_args_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart node_exporter
|
||||
|
||||
- name: Enable and start node_exporter
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ node_exporter_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
@@ -0,0 +1,2 @@
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
{{ node_exporter_args_var }}="--web.listen-address={{ node_exporter_listen_address }}"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
node_exporter_package: prometheus-node-exporter
|
||||
node_exporter_service: prometheus-node-exporter
|
||||
node_exporter_args_file: /etc/conf.d/prometheus-node-exporter
|
||||
node_exporter_args_var: NODE_EXPORTER_ARGS
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
node_exporter_package: prometheus-node-exporter
|
||||
node_exporter_service: prometheus-node-exporter
|
||||
node_exporter_args_file: /etc/default/prometheus-node-exporter
|
||||
node_exporter_args_var: ARGS
|
||||
@@ -0,0 +1,89 @@
|
||||
# Prometheus Role
|
||||
|
||||
Installs and configures Prometheus monitoring system as a native system service.
|
||||
|
||||
## Supported Distributions
|
||||
|
||||
- Arch Linux
|
||||
- Debian/Ubuntu
|
||||
|
||||
## Features
|
||||
|
||||
- Native system installation (no containers)
|
||||
- Configurable scrape targets and intervals
|
||||
- Optional Nginx reverse proxy with SSL
|
||||
- Retention policy configuration
|
||||
- Alert manager integration support
|
||||
- Rule files support
|
||||
|
||||
## Configuration
|
||||
|
||||
See [defaults/main.yml](defaults/main.yml) for all available variables.
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```yaml
|
||||
prometheus_scrape_interval: 15s
|
||||
prometheus_retention_time: 15d
|
||||
prometheus_web_listen_address: "127.0.0.1:9090"
|
||||
```
|
||||
|
||||
### Scrape Targets
|
||||
|
||||
```yaml
|
||||
prometheus_scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
- job_name: 'node_exporter'
|
||||
static_configs:
|
||||
- targets: ['localhost:9100']
|
||||
```
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
|
||||
```yaml
|
||||
prometheus_nginx_enabled: true
|
||||
prometheus_nginx_server_name: prometheus.example.com
|
||||
prometheus_nginx_ssl_certificate: /path/to/cert.pem
|
||||
prometheus_nginx_ssl_certificate_key: /path/to/key.pem
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
- hosts: monitoring
|
||||
roles:
|
||||
- prometheus
|
||||
```
|
||||
|
||||
With custom configuration:
|
||||
|
||||
```yaml
|
||||
- hosts: monitoring
|
||||
vars:
|
||||
prometheus_retention_time: 30d
|
||||
prometheus_scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
- job_name: 'node_exporter'
|
||||
static_configs:
|
||||
- targets: ['localhost:9100']
|
||||
roles:
|
||||
- prometheus
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Installation**: Native package from distribution repositories
|
||||
- **Service**: System systemd service
|
||||
- **Configuration**: `/etc/prometheus/prometheus.yml`
|
||||
- **Data**: `/var/lib/prometheus`
|
||||
- **Listen**: `127.0.0.1:9090` (localhost only by default)
|
||||
|
||||
## Security
|
||||
|
||||
- Binds to localhost by default
|
||||
- Optional SSL-enabled reverse proxy via Nginx
|
||||
- Data directory restricted to prometheus user (mode 0750)
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
# Prometheus configuration
|
||||
# See: https://prometheus.io/docs/prometheus/latest/configuration/configuration/
|
||||
|
||||
# Scrape interval
|
||||
prometheus_scrape_interval: 15s
|
||||
prometheus_evaluation_interval: 15s
|
||||
|
||||
# Retention settings
|
||||
prometheus_retention_time: 15d
|
||||
prometheus_retention_size: 0
|
||||
|
||||
# Web interface settings
|
||||
prometheus_web_listen_address: "127.0.0.1:9090"
|
||||
prometheus_web_external_url: ""
|
||||
|
||||
# Storage settings (uses prometheus_data_dir from vars/)
|
||||
prometheus_storage_tsdb_path: "{{ prometheus_data_dir }}"
|
||||
|
||||
# Scrape targets configuration
|
||||
# Example:
|
||||
# prometheus_scrape_configs:
|
||||
# - job_name: 'prometheus'
|
||||
# static_configs:
|
||||
# - targets: ['localhost:9090']
|
||||
# - job_name: 'node_exporter'
|
||||
# static_configs:
|
||||
# - targets: ['localhost:9100']
|
||||
prometheus_scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
# Alertmanager configuration (optional)
|
||||
# prometheus_alertmanager_configs:
|
||||
# - static_configs:
|
||||
# - targets: ['localhost:9093']
|
||||
prometheus_alertmanager_configs: []
|
||||
|
||||
# Rule files (optional)
|
||||
# prometheus_rule_files:
|
||||
# - '/etc/prometheus/rules/*.yml'
|
||||
prometheus_rule_files: []
|
||||
|
||||
# Nginx reverse proxy (optional)
|
||||
prometheus_nginx_enabled: false
|
||||
prometheus_nginx_server_name: prometheus.example.com
|
||||
prometheus_nginx_ssl_certificate: /etc/letsencrypt/live/prometheus.example.com/fullchain.pem
|
||||
prometheus_nginx_ssl_certificate_key: /etc/letsencrypt/live/prometheus.example.com/privkey.pem
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Restart Prometheus
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ prometheus_service }}"
|
||||
state: restarted
|
||||
|
||||
- name: Reload Nginx
|
||||
ansible.builtin.systemd:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
- name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "debian.yml"
|
||||
|
||||
- name: Install Prometheus
|
||||
ansible.builtin.package:
|
||||
name: "{{ prometheus_package }}"
|
||||
state: present
|
||||
|
||||
- name: Create Prometheus configuration directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ prometheus_config_dir }}"
|
||||
state: directory
|
||||
owner: "{{ prometheus_user }}"
|
||||
group: "{{ prometheus_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Create Prometheus data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ prometheus_data_dir }}"
|
||||
state: directory
|
||||
owner: "{{ prometheus_user }}"
|
||||
group: "{{ prometheus_group }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Create Prometheus rules directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ prometheus_config_dir }}/rules"
|
||||
state: directory
|
||||
owner: "{{ prometheus_user }}"
|
||||
group: "{{ prometheus_group }}"
|
||||
mode: "0755"
|
||||
when: prometheus_rule_files | length > 0
|
||||
|
||||
- name: Deploy Prometheus configuration
|
||||
ansible.builtin.template:
|
||||
src: prometheus.yml.j2
|
||||
dest: "{{ prometheus_config_dir }}/prometheus.yml"
|
||||
owner: "{{ prometheus_user }}"
|
||||
group: "{{ prometheus_group }}"
|
||||
mode: "0644"
|
||||
validate: "promtool check config %s"
|
||||
notify: Restart Prometheus
|
||||
|
||||
- name: Deploy Prometheus environment file
|
||||
ansible.builtin.template:
|
||||
src: prometheus-env.conf.j2
|
||||
dest: "{{ prometheus_args_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart Prometheus
|
||||
|
||||
- name: Enable and start Prometheus service
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ prometheus_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Deploy Nginx vhost configuration
|
||||
ansible.builtin.template:
|
||||
src: nginx-vhost.conf.j2
|
||||
dest: "{{ nginx_conf_dir }}/prometheus.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: prometheus_nginx_enabled
|
||||
notify: Reload Nginx
|
||||
|
||||
- name: Remove Nginx vhost configuration
|
||||
ansible.builtin.file:
|
||||
path: "{{ nginx_conf_dir }}/prometheus.conf"
|
||||
state: absent
|
||||
when: not prometheus_nginx_enabled
|
||||
notify: Reload Nginx
|
||||
@@ -0,0 +1,30 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ prometheus_nginx_server_name }};
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name {{ prometheus_nginx_server_name }};
|
||||
|
||||
ssl_certificate {{ prometheus_nginx_ssl_certificate }};
|
||||
ssl_certificate_key {{ prometheus_nginx_ssl_certificate_key }};
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
access_log /var/log/nginx/prometheus_access.log;
|
||||
error_log /var/log/nginx/prometheus_error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://{{ prometheus_web_listen_address }};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
{{ prometheus_args_var }}="--web.listen-address={{ prometheus_web_listen_address }}"
|
||||
@@ -0,0 +1,60 @@
|
||||
# Prometheus configuration file
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
|
||||
global:
|
||||
scrape_interval: {{ prometheus_scrape_interval }}
|
||||
evaluation_interval: {{ prometheus_evaluation_interval }}
|
||||
{% if prometheus_web_external_url %}
|
||||
external_labels:
|
||||
monitor: '{{ prometheus_web_external_url }}'
|
||||
{% endif %}
|
||||
|
||||
{% if prometheus_alertmanager_configs | length > 0 %}
|
||||
alerting:
|
||||
alertmanagers:
|
||||
{% for alertmanager in prometheus_alertmanager_configs %}
|
||||
- {{ alertmanager | to_nice_yaml(indent=2) | indent(6) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if prometheus_rule_files | length > 0 %}
|
||||
rule_files:
|
||||
{% for rule_file in prometheus_rule_files %}
|
||||
- {{ rule_file }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
scrape_configs:
|
||||
{% for scrape_config in prometheus_scrape_configs %}
|
||||
- job_name: '{{ scrape_config.job_name }}'
|
||||
{% if scrape_config.scrape_interval is defined %}
|
||||
scrape_interval: {{ scrape_config.scrape_interval }}
|
||||
{% endif %}
|
||||
{% if scrape_config.scrape_timeout is defined %}
|
||||
scrape_timeout: {{ scrape_config.scrape_timeout }}
|
||||
{% endif %}
|
||||
{% if scrape_config.metrics_path is defined %}
|
||||
metrics_path: {{ scrape_config.metrics_path }}
|
||||
{% endif %}
|
||||
{% if scrape_config.static_configs is defined %}
|
||||
static_configs:
|
||||
{% for static_config in scrape_config.static_configs %}
|
||||
- targets:
|
||||
{% for target in static_config.targets %}
|
||||
- {{ target }}
|
||||
{% endfor %}
|
||||
{% if static_config.labels is defined %}
|
||||
labels:
|
||||
{% for label_key, label_value in static_config.labels.items() %}
|
||||
{{ label_key }}: {{ label_value }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if scrape_config.relabel_configs is defined %}
|
||||
relabel_configs:
|
||||
{% for relabel_config in scrape_config.relabel_configs %}
|
||||
- {{ relabel_config | to_nice_yaml(indent=2) | indent(8) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
prometheus_package: prometheus
|
||||
prometheus_user: prometheus
|
||||
prometheus_group: prometheus
|
||||
prometheus_service: prometheus
|
||||
prometheus_config_dir: /etc/prometheus
|
||||
prometheus_data_dir: /var/lib/prometheus
|
||||
prometheus_args_file: /etc/conf.d/prometheus
|
||||
prometheus_args_var: PROMETHEUS_ARGS
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
prometheus_package: prometheus
|
||||
prometheus_user: prometheus
|
||||
prometheus_group: prometheus
|
||||
prometheus_service: prometheus
|
||||
prometheus_config_dir: /etc/prometheus
|
||||
prometheus_data_dir: /var/lib/prometheus
|
||||
prometheus_args_file: /etc/default/prometheus
|
||||
prometheus_args_var: ARGS
|
||||
Reference in New Issue
Block a user