feat(vaultwarden): add SMTP configuration options and enhance signup settings
- Introduced SMTP settings for Vaultwarden including host, port, security, and authentication details. - Added variables for signup verification, 2FA settings, password hints, and logging options. - Updated Vaultwarden deployment to utilize new SMTP configurations. - Enhanced Grafana module to support dynamic dashboard and datasource provisioning. - Added LLM proxy configuration for Open Web UI with necessary environment variables.
This commit is contained in:
@@ -46,18 +46,6 @@ amd_exporter_python_version: "python3"
|
||||
amd_exporter_sysfs_gpu_pattern: "/sys/class/drm/card?/device/"
|
||||
|
||||
# Llama Exporter configuration
|
||||
llama_exporter_enabled: true
|
||||
llama_exporter_port: 9550
|
||||
llama_exporter_user: llama_exporter
|
||||
llama_exporter_user_home: "/home/llama_exporter"
|
||||
llama_exporter_install_dir: "/opt/llama_exporter"
|
||||
llama_exporter_venv_path: "/opt/llama_exporter/.venv"
|
||||
llama_exporter_scrape_interval: 15
|
||||
llama_exporter_scrape_timeout: 5
|
||||
|
||||
# Llama.cpp scrape targets
|
||||
llama_exporter_targets: []
|
||||
|
||||
# Firewall configuration
|
||||
prometheus_fw_port: "{{ prometheus_port }}"
|
||||
prometheus_fw_proto: "tcp"
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""LLM inference metrics exporter for Prometheus.
|
||||
|
||||
Scrape targets:
|
||||
- llama.cpp HTTP API servers (standard /metrics or custom health)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
POLL_INTERVAL = int(os.environ.get("LLAMA_EXPORTER_INTERVAL", "15"))
|
||||
SCRAPE_TIMEOUT = int(os.environ.get("LLAMA_EXPORTER_TIMEOUT", "5"))
|
||||
|
||||
LLAMA_CPP_DEFAULTS = [
|
||||
{"name": "llama-cpp-8012", "url": "http://localhost:8012"},
|
||||
]
|
||||
|
||||
|
||||
def _fetch_json(url, timeout=SCRAPE_TIMEOUT):
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
logger.debug("Failed to fetch %s: %s", url, e)
|
||||
return None
|
||||
|
||||
|
||||
def _collect_llama_cpp(target):
|
||||
metrics = {}
|
||||
|
||||
def _set_metric(metric_name, labels, value):
|
||||
label_key = tuple(sorted(labels.items()))
|
||||
metrics[(metric_name, label_key)] = value
|
||||
|
||||
base_url = target["url"].rstrip("/")
|
||||
|
||||
# Check health endpoint
|
||||
health_url = f"{base_url}/health"
|
||||
health = _fetch_json(health_url)
|
||||
if health and isinstance(health, dict):
|
||||
status = health.get("status", "unknown")
|
||||
_set_metric("llama_server_health", {"server": target["name"], "model": health.get("model", "unknown")}, 1.0 if status == "ok" else 0.0)
|
||||
else:
|
||||
_set_metric("llama_server_health", {"server": target["name"], "model": "unknown"}, 0.0)
|
||||
|
||||
# Check /metrics endpoint for llama.cpp built-in metrics
|
||||
metrics_url = f"{base_url}/metrics"
|
||||
try:
|
||||
req = urllib.request.Request(metrics_url, headers={"Accept": "text/plain"})
|
||||
with urllib.request.urlopen(req, timeout=SCRAPE_TIMEOUT) as resp:
|
||||
body = resp.read().decode("utf-8")
|
||||
for line in body.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# Parse simple key value pairs
|
||||
if "{" in line:
|
||||
key_part = line.split("{")[0]
|
||||
labels_part = line.split("{")[1].rstrip("}").split("}")[0] if "}" in line else ""
|
||||
value = line.split()[-1] if line.split() else "1"
|
||||
label_str = ""
|
||||
if labels_part:
|
||||
label_str = ",".join(f'{{{k}="{v}"}}' for k, v in (
|
||||
pair.split("=") for pair in labels_part.split(",") if "=" in pair
|
||||
))
|
||||
_set_metric(key_part.strip(), {"server": target["name"], "raw": labels_part}, float(value) if value.replace(".", "").replace("-", "").isdigit() else value)
|
||||
else:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
float(parts[1])
|
||||
_set_metric(parts[0], {"server": target["name"]}, parts[1])
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug("Failed to scrape %s/metrics: %s", metrics_url, e)
|
||||
|
||||
# Check /model endpoint for model info
|
||||
model_url = f"{base_url}/models"
|
||||
models = _fetch_json(model_url)
|
||||
if models and isinstance(models, dict):
|
||||
model_list = models.get("data", [models]) if "data" in models else [models]
|
||||
for m in model_list:
|
||||
if not isinstance(m, dict):
|
||||
continue
|
||||
model_id = m.get("id", "unknown")
|
||||
_set_metric("llama_models_loaded", {"server": target["name"], "model": model_id}, 1.0)
|
||||
ctx_max = m.get("context_size", m.get("n_ctx", 0))
|
||||
if ctx_max:
|
||||
_set_metric("llama_model_context_max", {"server": target["name"], "model": model_id}, int(ctx_max))
|
||||
trained = m.get("train_tokens", m.get("n_train", 0))
|
||||
if trained:
|
||||
_set_metric("llama_model_tokens_trained", {"server": target["name"], "model": model_id}, int(trained))
|
||||
|
||||
return metrics
|
||||
|
||||
|
||||
|
||||
class MetricsHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path != "/metrics":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
all_metrics = {}
|
||||
|
||||
# Scrape llama.cpp targets
|
||||
targets = []
|
||||
env_targets = os.environ.get("LLAMA_TARGETS", "")
|
||||
if env_targets:
|
||||
try:
|
||||
targets = json.loads(env_targets)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("Invalid LLAMA_TARGETS JSON, using defaults")
|
||||
targets = []
|
||||
|
||||
if not targets:
|
||||
targets = LLAMA_CPP_DEFAULTS
|
||||
|
||||
for target in targets:
|
||||
if "url" not in target or "name" not in target:
|
||||
target["name"] = target.get("name", "default")
|
||||
target["url"] = target.get("url", "http://localhost:8012")
|
||||
try:
|
||||
all_metrics.update(_collect_llama_cpp(target))
|
||||
except Exception as e:
|
||||
logger.error("Error scraping llama.cpp target %s: %s", target.get("name", "unknown"), e)
|
||||
|
||||
lines = []
|
||||
for (metric_type, label_pairs), value in sorted(all_metrics.items()):
|
||||
labels = dict(label_pairs)
|
||||
if not labels:
|
||||
line = f"{metric_type} {value}"
|
||||
else:
|
||||
label_str = ",".join(f'{k}="{v}"' for k, v in sorted(labels.items()))
|
||||
line = f"{metric_type}{{{label_str}}} {value}"
|
||||
lines.append(line)
|
||||
|
||||
body = "\n".join(lines) + "\n" if lines else "# no metrics available\n"
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(body.encode("utf-8"))
|
||||
|
||||
def log_message(self, format, *args):
|
||||
logger.debug("%s - - %s", self.address_string(), format % args)
|
||||
|
||||
|
||||
def main():
|
||||
port = int(os.environ.get("LLAMA_EXPORTER_PORT", "9550"))
|
||||
host = os.environ.get("LLAMA_EXPORTER_BIND", "0.0.0.0")
|
||||
|
||||
server = HTTPServer((host, port), MetricsHandler)
|
||||
logger.info("Starting Llama Exporter on %s:%d", host, port)
|
||||
logger.info("Polling every %d seconds, timeout %ds", POLL_INTERVAL, SCRAPE_TIMEOUT)
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down")
|
||||
server.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -18,10 +18,3 @@
|
||||
name: "{{ amd_exporter_service_name }}"
|
||||
state: restarted
|
||||
listen: Restart amd_exporter
|
||||
|
||||
- name: Restart llama_exporter
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ llama_exporter_service_name }}"
|
||||
state: restarted
|
||||
listen: Restart llama_exporter
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
name: "gpu_monitoring"
|
||||
vars:
|
||||
amd_exporter_enabled: false
|
||||
llama_exporter_enabled: false
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
- name: Create llama_exporter group
|
||||
become: true
|
||||
ansible.builtin.group:
|
||||
name: "{{ llama_exporter_user }}"
|
||||
state: present
|
||||
system: true
|
||||
|
||||
- name: Create llama_exporter user
|
||||
become: true
|
||||
ansible.builtin.user:
|
||||
name: "{{ llama_exporter_user }}"
|
||||
group: "{{ llama_exporter_user }}"
|
||||
home: "{{ llama_exporter_user_home }}"
|
||||
create_home: true
|
||||
shell: /usr/sbin/nologin
|
||||
system: true
|
||||
|
||||
- name: Create llama_exporter install directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ llama_exporter_user }}"
|
||||
group: "{{ llama_exporter_user }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ llama_exporter_install_dir }}"
|
||||
- "{{ llama_exporter_install_dir }}/logs"
|
||||
|
||||
- name: Copy llama_exporter script
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
src: llama_exporter.py
|
||||
dest: "{{ llama_exporter_script_path }}"
|
||||
owner: "{{ llama_exporter_user }}"
|
||||
group: "{{ llama_exporter_user }}"
|
||||
mode: "0755"
|
||||
notify:
|
||||
- Restart llama_exporter
|
||||
|
||||
- name: Create Python virtual environment
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ ansible_facts['python']['executable'] }} -m venv {{ llama_exporter_venv_path }}"
|
||||
creates: "{{ llama_exporter_venv_path }}/bin/activate"
|
||||
|
||||
- name: Install Python dependencies in llama_exporter venv
|
||||
become: true
|
||||
ansible.builtin.pip:
|
||||
name:
|
||||
- prometheus-client
|
||||
- requests
|
||||
virtualenv: "{{ llama_exporter_venv_path }}"
|
||||
state: present
|
||||
|
||||
- name: Fix ownership of llama_exporter directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ llama_exporter_install_dir }}"
|
||||
owner: "{{ llama_exporter_user }}"
|
||||
group: "{{ llama_exporter_user }}"
|
||||
recurse: true
|
||||
|
||||
- name: Generate llama_exporter systemd service
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: llama_exporter.service.j2
|
||||
dest: "/etc/systemd/system/{{ llama_exporter_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify:
|
||||
- Reload systemd daemon
|
||||
- Restart llama_exporter
|
||||
|
||||
- name: Enable llama_exporter service
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ llama_exporter_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -11,12 +11,6 @@
|
||||
tags:
|
||||
- roles::gpu_monitoring::amd_exporter
|
||||
|
||||
- name: Include Llama Exporter setup
|
||||
when: llama_exporter_enabled | default(true) | bool
|
||||
ansible.builtin.include_tasks: llama_exporter.yml
|
||||
tags:
|
||||
- roles::gpu_monitoring::llama_exporter
|
||||
|
||||
- name: Include Prometheus setup
|
||||
ansible.builtin.include_tasks: prometheus.yml
|
||||
tags:
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
[Unit]
|
||||
Description=LLM Inference Metrics Exporter (llama.cpp + Ollama)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ llama_exporter_user }}
|
||||
Group={{ llama_exporter_user }}
|
||||
WorkingDirectory={{ llama_exporter_install_dir }}
|
||||
Environment="LLAMA_EXPORTER_PORT={{ llama_exporter_port }}"
|
||||
Environment="LLAMA_EXPORTER_BIND=0.0.0.0"
|
||||
Environment="LLAMA_EXPORTER_INTERVAL={{ llama_exporter_scrape_interval }}"
|
||||
Environment="LLAMA_EXPORTER_TIMEOUT={{ llama_exporter_scrape_timeout }}"
|
||||
{% if llama_exporter_targets and llama_exporter_targets != "" %}
|
||||
Environment="LLAMA_TARGETS={{ llama_exporter_targets | to_json }}"
|
||||
{% endif %}
|
||||
Environment="PATH={{ llama_exporter_venv_bin }}:/usr/bin:/bin"
|
||||
ExecStart={{ llama_exporter_venv_bin }}/python3 {{ llama_exporter_script_path }}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
TimeoutStopSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=llama_exporter
|
||||
|
||||
# Security hardening
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths={{ llama_exporter_install_dir }} /var/log
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -21,10 +21,3 @@ scrape_configs:
|
||||
- targets: ["{{ prometheus_host_gateway }}:{{ amd_exporter_port }}"]
|
||||
labels:
|
||||
datacenter: "{{ inventory_hostname }}"
|
||||
|
||||
- job_name: llama_exporter
|
||||
metrics_path: /metrics
|
||||
static_configs:
|
||||
- targets: ["{{ prometheus_host_gateway }}:{{ llama_exporter_port }}"]
|
||||
labels:
|
||||
datacenter: "{{ inventory_hostname }}"
|
||||
|
||||
@@ -12,12 +12,3 @@ nvidia_exporter_service_name: "nvidia_exporter"
|
||||
amd_exporter_script_path: "{{ amd_exporter_install_dir }}/amd_gpu_exporter.py"
|
||||
amd_exporter_venv_bin: "{{ amd_exporter_venv_path }}/bin"
|
||||
amd_exporter_service_name: "amdgpu_exporter"
|
||||
|
||||
llama_exporter_script_path: "{{ llama_exporter_install_dir }}/llama_exporter.py"
|
||||
llama_exporter_venv_bin: "{{ llama_exporter_venv_path }}/bin"
|
||||
llama_exporter_service_name: "llama_exporter"
|
||||
|
||||
# Default llama.cpp targets if none provided
|
||||
llama_exporter_targets_default:
|
||||
- name: "llama-cpp-8012"
|
||||
url: "http://localhost:8012"
|
||||
|
||||
Reference in New Issue
Block a user