Gestione centralizzata dei log con Grafana Loki su un VPS
Distribuisci Grafana Loki, Promtail e Grafana tramite Docker Compose su un singolo VPS. Raccogli log di systemd, Docker e Nginx, interrogali con LogQL e configura la retention per la produzione.
Log sparsi tra /var/log e l'output di docker logs diventano ingestibili quando hai più di due servizi in esecuzione. Questo tutorial distribuisce lo stack Grafana + Loki + Promtail su un singolo VPS usando Docker Compose. Raccoglierai log da systemd, container Docker e Nginx, li interrogherai con LogQL, configurerai la retention per evitare di riempire il disco e metterai in sicurezza lo stack per un server esposto a internet.
Alla fine, l'API HTTP di Loki sarà pronta per query programmatiche. L'articolo Analisi dei log con IA tramite Ollama su un VPS: rilevare anomalie con un LLM locale si basa su questo per inviare i log a un LLM locale per il rilevamento delle anomalie.
Cosa fa lo stack Grafana + Loki + Promtail?
Grafana Loki è un sistema open-source di aggregazione log che indicizza solo le label (metadati), non il testo completo delle voci di log. Questo lo rende molto più leggero in termini di risorse rispetto a Elasticsearch. Loki archivia blocchi di log compressi sul filesystem o su object storage. Abbinato a Promtail per la raccolta e Grafana per la visualizzazione, forma uno stack completo di logging centralizzato.
I tre componenti hanno ruoli distinti:
| Componente | Ruolo | Consumo risorse |
|---|---|---|
| Loki | Riceve, archivia e indicizza i log. Serve le query. | 300-600 MB RAM a riposo, fino a 1 GB sotto query intensive |
| Promtail | Scopre le sorgenti di log, legge i file continuamente, invia le voci a Loki | 50-100 MB RAM |
| Grafana | Interfaccia web per interrogare e visualizzare i log tramite Explore | 200-300 MB RAM |
Consumo totale dello stack: 1-1,5 GB di RAM. Un VPS da 2 GB è il minimo. Un VPS da 4 GB offre margine confortevole per query LogQL su dataset più grandi.
Loki vs Elasticsearch: Elasticsearch indicizza ogni parola di ogni riga di log, il che offre ricerca full-text ma costa 10-20 volte di più in RAM e disco. L'indice basato su label di Loki significa che filtri prima per label, poi scorri i blocchi corrispondenti. Per la maggior parte dei carichi di lavoro su VPS, è il compromesso giusto. Se hai bisogno di ricerca full-text su terabyte di log, Loki non è lo strumento adatto.
Prerequisiti
- Un VPS con almeno 2 GB di RAM (4 GB consigliati). Un VPS Virtua Cloud con 4 vCPU e 8 GB di RAM gestisce questo stack senza problemi.
- Docker e Docker Compose installati. Se hai bisogno di aiuto con l'installazione, consulta [-> docker-compose-multi-service-vps].
- Un utente non-root con accesso sudo.
- Conoscenze base del terminale e della sintassi YAML.
Verifica che Docker sia in esecuzione:
docker --version
docker compose version
Dovresti vedere Docker 24+ e Compose v2+. Se uno dei comandi fallisce, Docker non è installato o manca il plugin Compose.
Come distribuisco Loki con Docker Compose su un VPS?
Crea una directory di progetto e tre file di configurazione: docker-compose.yml, loki-config.yml e promtail-config.yml. Il file Docker Compose fissa tutte le immagini a versioni specifiche, imposta limiti di risorse, configura volumi persistenti e vincola Loki solo a localhost.
Struttura del progetto
mkdir -p ~/loki-stack/{loki-data,promtail-data}
cd ~/loki-stack
La directory loki-data contiene chunk, indici e il write-ahead log. La directory promtail-data archivia il file delle posizioni di Promtail per riprendere dopo i riavvii.
Imposta la proprietà della directory dati di Loki. L'immagine Docker di Loki 3.x gira come UID 10001, non come root. Senza questo passaggio, Loki fallisce all'avvio con "permission denied" durante la creazione delle sottodirectory:
chown 10001:10001 ~/loki-stack/loki-data
docker-compose.yml
services:
loki:
image: grafana/loki:3.6.7
command: -config.file=/etc/loki/config.yml
volumes:
- ./loki-config.yml:/etc/loki/config.yml:ro
- ./loki-data:/loki
ports:
- "127.0.0.1:3100:3100"
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
networks:
- loki-net
promtail:
image: grafana/promtail:3.6.7
command: -config.file=/etc/promtail/config.yml
volumes:
- ./promtail-config.yml:/etc/promtail/config.yml:ro
- ./promtail-data:/var/lib/promtail
- /var/log:/var/log:ro
- /var/log/journal:/var/log/journal:ro
- /run/log/journal:/run/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
deploy:
resources:
limits:
memory: 256m
depends_on:
- loki
networks:
- loki-net
grafana:
image: grafana/grafana:11.5.2
volumes:
- grafana-data:/var/lib/grafana
ports:
- "127.0.0.1:3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin_pw
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_USERS_ALLOW_SIGN_UP=false
- GF_ANALYTICS_REPORTING_ENABLED=false
secrets:
- grafana_admin_pw
restart: unless-stopped
deploy:
resources:
limits:
memory: 512m
depends_on:
- loki
networks:
- loki-net
secrets:
grafana_admin_pw:
file: ./secrets/grafana_admin_pw
volumes:
grafana-data:
networks:
loki-net:
driver: bridge
Nota: Loki e Grafana sono in ascolto su 127.0.0.1, non su 0.0.0.0. Questo impedisce l'accesso esterno. Accederai a Grafana tramite un tunnel SSH o un reverse proxy. Esporre Loki o Grafana direttamente su internet è un errore comune presente nella maggior parte dei tutorial online.
La policy restart: unless-stopped garantisce che ogni servizio sopravviva ai riavvii. Se fermi manualmente un servizio con docker compose stop, resta fermo. Altrimenti si riavvia automaticamente.
I tag delle immagini fissati (3.6.7, 11.5.2) prevengono aggiornamenti inattesi. Non usare mai :latest in produzione. Quando vuoi aggiornare, cambia il tag ed esegui docker compose up -d per scaricare la nuova immagine.
Generare la password admin di Grafana
Non usare mai le credenziali predefinite. Genera una password forte e salvala in un file di segreti con permessi limitati:
mkdir -p ~/loki-stack/secrets
openssl rand -base64 32 > ~/loki-stack/secrets/grafana_admin_pw
chmod 644 ~/loki-stack/secrets/grafana_admin_pw
Verifica i permessi:
ls -la ~/loki-stack/secrets/grafana_admin_pw
Dovresti vedere -rw-r--r--. Il file deve essere leggibile da tutti perché Docker Compose (fuori dalla modalità Swarm) monta i segreti basati su file con i permessi del file sorgente. Grafana gira come UID 472 nel container e ha bisogno dell'accesso in lettura. Il file è comunque protetto dalla sua posizione in una directory dedicata ai segreti, e solo l'utente root dell'host può modificarlo. La variabile d'ambiente GF_SECURITY_ADMIN_PASSWORD__FILE indica a Grafana di leggere la password da questo file all'avvio invece di incorporarla nel file compose.
loki-config.yml
Questa configurazione usa TSDB con schema v13 (valori predefiniti di Loki 3.x), storage su filesystem per deployment a nodo singolo e il compactor per la retention:
auth_enabled: false
server:
http_listen_port: 3100
http_listen_address: 0.0.0.0
log_level: warn
http_server_read_timeout: 30s
http_server_write_timeout: 30s
common:
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /loki
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
filesystem:
directory: /loki/chunks
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
limits_config:
retention_period: 720h
max_streams_per_user: 10000
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_label_names_per_series: 15
ingester:
chunk_encoding: snappy
wal:
dir: /loki/wal
enabled: true
chunk_store_config:
chunk_cache_config:
embedded_cache:
enabled: true
max_size_mb: 100
Scelte chiave in questa configurazione:
auth_enabled: falseè sicuro qui perché Loki ascolta solo su localhost (rete interna Docker + binding su 127.0.0.1). Le configurazioni multi-tenant richiedonoauth_enabled: truecon un headerX-Scope-OrgIDsu ogni richiesta.retention_period: 720hconserva i log per 30 giorni. Loki 3.x usa0s(conserva per sempre) come predefinito se non imposti questo valore. Il disco si riempirà.schema: v13constore: tsdbè richiesto per le funzionalità di Loki 3.x. Le vecchie configurazioniboltdb-shipperdei tutorial Loki 2.x falliranno all'avvio o produrranno avvisi di deprecazione.chunk_encoding: snappycomprime i chunk con Snappy. Più veloce di gzip, file leggermente più grandi. Buon valore predefinito per nodo singolo dove la CPU è più limitata del disco.- WAL abilitato: il write-ahead log protegge dalla perdita di dati se Loki si blocca durante la scrittura. Al riavvio, Loki riproduce il WAL per recuperare le voci non confermate. Vedrai messaggi "WAL replay" nei log all'avvio. È normale.
max_label_names_per_series: 15corrisponde al valore predefinito di Loki 3.x. Mantieni bassa la cardinalità delle label. Label comeuser_idorequest_idcreano troppi stream e degradano le prestazioni.
Come configuro Promtail per raccogliere i log del journal systemd?
Promtail raccoglie log da più sorgenti e li invia a Loki. La configurazione seguente raccoglie da tre sorgenti in job scrape_configs separati: journal systemd, container Docker e file di log Nginx.
Avviso di fine vita di Promtail: Promtail ha raggiunto la fine del ciclo di vita il 2 marzo 2026. Grafana Alloy è il successore ufficiale. Questo tutorial usa Promtail perché i concetti di configurazione si trasferiscono direttamente ad Alloy, e milioni di deployment lo usano ancora. Consulta la sezione "Promtail è deprecato?" qui sotto per i passaggi di migrazione.
promtail-config.yml
server:
http_listen_port: 9080
log_level: warn
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# --- Systemd journal ---
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: unit
- source_labels: ['__journal_priority_keyword']
target_label: severity
- source_labels: ['__journal__hostname']
target_label: hostname
# --- Docker containers ---
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.+)'
target_label: container
- source_labels: ['__meta_docker_container_log_stream']
target_label: stream
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: compose_service
# --- Nginx access and error logs ---
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
type: access
__path__: /var/log/nginx/access.log
- targets:
- localhost
labels:
job: nginx
type: error
__path__: /var/log/nginx/error.log
Il file positions.yaml tiene traccia di quanto Promtail ha letto in ogni sorgente. Se Promtail si riavvia, riprende da dove si era fermato invece di reinviare vecchi log o perderne di nuovi.
Come funziona il job journal
Il blocco journal legge direttamente dal journal systemd tramite l'API journal. L'impostazione max_age: 12h indica a Promtail di acquisire solo le voci delle ultime 12 ore al primo avvio. Senza questo, Promtail tenterebbe di acquisire l'intera cronologia del journal, che può essere di gigabyte su server in esecuzione da tempo.
I relabel_configs estraggono metadati dalle voci del journal in label Loki. __journal__systemd_unit diventa la label unit (es. sshd.service, nginx.service). __journal_priority_keyword diventa severity (es. warning, err, info). Queste label permettono di filtrare i log in modo efficiente in LogQL senza scansionare ogni riga.
Perché lo scraping del journal funzioni, il container Promtail necessita di due mount di volume: /run/log/journal (o /var/log/journal se il sistema persiste i log) e /etc/machine-id. L'ID macchina identifica quale journal leggere.
Limitazione dell'immagine Docker: L'immagine Docker standard
grafana/promtailnon è compilata con il supporto per il journal systemd. Se vedisupport for reading the systemd journal is not compiled into this build of promtailnei log, lo scraping del journal non funzionerà dal container Docker. Hai due opzioni:
- Installare Promtail come binario sull'host invece di usare l'immagine Docker. Scaricalo dalla pagina delle release di Loki ed eseguilo direttamente sull'host, dove ha accesso nativo all'API journal.
- Usare Grafana Alloy (vedi la sezione sulla migrazione qui sotto), che supporta lo scraping del journal nella sua immagine Docker.
I job di scraping Docker e file Nginx funzionano senza problemi nell'immagine Docker. Solo lo scraping del journal richiede il binario host o Alloy.
Come raccolgo i log dei container Docker con Promtail?
Il job docker usa Docker service discovery per scoprire automaticamente tutti i container in esecuzione sull'host. Promtail si connette al daemon Docker tramite /var/run/docker.sock e interroga i nuovi container ogni 5 secondi. Quando un container parte o si ferma, Promtail inizia o smette automaticamente di seguirne i log.
I relabel_configs estraggono metadati utili:
__meta_docker_container_namediventa la labelcontainer. La regex'/(.+)'rimuove il/iniziale che Docker aggiunge ai nomi dei container.__meta_docker_container_log_streamdiventa la labelstream(stdoutostderr).__meta_docker_container_label_com_docker_compose_serviceestrae il nome del servizio Compose (es.loki,grafana). Questa label esiste solo per i container gestiti da Docker Compose.
Non devi configurare i singoli container. Ogni container con un driver di log che scrive sul filesystem (il driver json-file predefinito) verrà scoperto. Se esegui un container database, un'app web e una cache, tutti e tre appaiono automaticamente in Loki con i loro nomi di container.
Per escludere container specifici dalla raccolta log, aggiungi una label Docker e filtra su di essa:
relabel_configs:
# ... existing relabel rules ...
- source_labels: ['__meta_docker_container_label_logging']
regex: 'disabled'
action: drop
Poi sul container da escludere:
noisy-service:
image: some/image
labels:
logging: "disabled"
Come raccolgo i log di accesso e di errore di Nginx con Promtail?
Il job nginx usa static_configs con la label __path__ per seguire file di log specifici. A differenza del Docker service discovery, richiede di conoscere i percorsi dei file in anticipo. Nginx scrive di default in /var/log/nginx/access.log e /var/log/nginx/error.log.
La label type distingue tra log di accesso e di errore. Questo permette di interrogarli separatamente in LogQL:
{job="nginx", type="access"} # solo log di accesso
{job="nginx", type="error"} # solo log di errore
{job="nginx"} # entrambi
Se Nginx non è installato sull'host, Promtail registra un avviso per i file mancanti ma continua a raccogliere dalle altre sorgenti. È innocuo. Rimuovi il job nginx dalla configurazione se non usi Nginx.
Per Nginx in esecuzione in un container Docker, hai due opzioni. Puoi usare il Docker service discovery (stdout/stderr del container verranno catturati automaticamente). Oppure puoi montare la directory dei log Nginx come volume condiviso e usare lo scraper di file statici. L'approccio Docker è più semplice. L'approccio per file ti dà label separate type: access e type: error.
Avviare lo stack
cd ~/loki-stack
docker compose up -d
Verifica che tutti e tre i container siano in esecuzione:
docker compose ps
Dovresti vedere loki, promtail e grafana con stato Up. Se un servizio mostra Restarting, controlla i suoi log:
docker compose logs <service-name> --tail=30
Verifica che Loki sia pronto:
curl -s http://127.0.0.1:3100/ready
Output atteso: ready. Se ottieni Ingester not ready: waiting for 15s after being ready, attendi 15 secondi e riprova. Loki ha bisogno di tempo per inizializzare l'ingester ring.
Verifica i target di Promtail:
docker compose logs promtail --tail=20
Cerca righe che mostrino i target scoperti. Dovresti vedere voci per il journal, il socket Docker e i percorsi dei log Nginx. Non dovrebbero apparire righe con level=error.
Come verifico che i log appaiano in Grafana Explore?
Apri un tunnel SSH per accedere a Grafana dalla tua macchina locale. Usiamo un tunnel perché Grafana è in ascolto su localhost sul VPS e non è esposto a internet.
ssh -L 3000:127.0.0.1:3000 user@your-vps-ip
Apri http://localhost:3000 nel browser. Accedi con nome utente admin e la password da ~/loki-stack/secrets/grafana_admin_pw. Leggila con:
cat ~/loki-stack/secrets/grafana_admin_pw
Aggiungere Loki come data source
- Vai su Connections > Data Sources > Add data source
- Seleziona Loki
- Imposta l'URL su
http://loki:3100(questo è l'hostname interno Docker, non localhost) - Clicca su Save & test
Dovresti vedere "Data source successfully connected." Se fallisce, verifica che entrambi i container siano sulla stessa rete Docker (loki-net).
Eseguire la prima query
- Vai su Explore (icona bussola nella barra laterale)
- Seleziona il data source Loki
- Passa alla modalità Code (non Builder) e inserisci questa query LogQL:
{job="systemd-journal"} |= "ssh"
Questo mostra tutte le voci del journal systemd contenenti "ssh". Se vedi righe di log, l'intera pipeline funziona: journal -> Promtail -> Loki -> Grafana.
Prova una query su container Docker:
{compose_service="loki"}
Questo restituisce i log di Loki stesso, raccolti da Promtail tramite Docker service discovery.
E una query Nginx (se Nginx è installato e genera log):
{job="nginx", type="error"}
Se Grafana mostra "No data", attendi 2-3 minuti. Loki ha bisogno di tempo per acquisire e indicizzare il primo batch di log.
Quali sono le query LogQL più utili per i log del server?
LogQL ha due tipi di query: le query di log restituiscono righe di log, le query di metriche restituiscono valori numerici. Entrambe iniziano con un selettore di stream ({label="value"}) che sceglie quali stream di log scansionare, poi aggiungono filtri e parser per affinare i risultati.
Selettori di stream e filtri di riga
# Tutti i log del daemon SSH
{unit="sshd.service"}
# Righe contenenti "Failed password"
{unit="sshd.service"} |= "Failed password"
# Righe che NON contengono "Accepted"
{unit="sshd.service"} != "Accepted"
# Corrispondenza regex: indirizzi IP
{unit="sshd.service"} |~ "\\d+\\.\\d+\\.\\d+\\.\\d+"
# Corrispondenza case-insensitive
{job="nginx", type="error"} |~ "(?i)timeout"
I filtri di riga (|=, !=, |~, !~) vengono eseguiti dopo il selettore di stream. Scansionano il contenuto delle righe di log. Più filtri si concatenano e devono tutti corrispondere:
{unit="sshd.service"} |= "Failed" |= "root"
Questo trova righe contenenti sia "Failed" che "root".
Parser
I parser estraggono campi strutturati da righe di log non strutturate. Una volta effettuato il parsing, puoi filtrare sui campi estratti come status >= 500 invece di usare regex. Scegli il parser giusto per il tuo formato di log:
| Parser | Sintassi | Adatto per | Note sulle prestazioni |
|---|---|---|---|
logfmt |
| logfmt |
Log chiave=valore (systemd, app Go) | Il più veloce. Zero regex. |
json |
| json |
Log strutturati in JSON | Veloce. Parsing JSON nativo. |
pattern |
| pattern "<pattern>" |
Log a formato fisso (Nginx combined, Apache) | Veloce. Estrazione posizionale. |
regexp |
| regexp "<regex>" |
Formati irregolari, strutture miste | Il più lento. Usare come ultima risorsa. |
Usa logfmt o json quando i tuoi log hanno già una struttura. Usa pattern per formati noti come il log combined di Nginx. Usa regexp solo quando nient'altro funziona, perché il parsing con regex è significativamente più lento sugli stream ad alto volume.
Log di accesso Nginx con parser pattern:
{job="nginx", type="access"}
| pattern "<ip> - - [<timestamp>] \"<method> <uri> <_>\" <status> <bytes>"
| status >= 500
Questo effettua il parsing del formato combined di Nginx in campi denominati e filtra gli errori 5xx. Il placeholder <_> scarta i campi non necessari (la versione HTTP in questo caso).
Parser di log JSON:
{compose_service="myapp"}
| json
| level="error"
| line_format "{{.timestamp}} {{.message}}"
Lo stage line_format riformatta l'output. Utile quando i log JSON sono rumorosi e vuoi un output più pulito in Grafana.
Metriche dai log
Le query di metriche trasformano le righe di log in numeri. Alimentano le dashboard di Grafana e le regole di alerting:
# Tasso di login SSH falliti al minuto nell'ultima ora
rate({unit="sshd.service"} |= "Failed password" [5m])
# Totale errori Nginx 5xx in finestre di 5 minuti
count_over_time(
{job="nginx", type="access"}
| pattern "<_> - - [<_>] \"<_> <_> <_>\" <status> <_>"
| status >= 500
[5m]
)
# P95 del tempo di risposta dai log JSON di un'app
# unwrap estrae un campo numerico per l'aggregazione
quantile_over_time(0.95,
{compose_service="myapp"}
| json
| unwrap response_time_ms
| __error__=""
[5m]
) by (endpoint)
# Byte serviti al secondo da Nginx
sum(rate(
{job="nginx", type="access"}
| pattern "<_> - - [<_>] \"<_> <_> <_>\" <_> <bytes>"
| unwrap bytes
| __error__=""
[5m]
))
Il filtro | __error__="" dopo unwrap scarta le righe dove l'estrazione numerica è fallita (valori non numerici, campi mancanti). Senza di esso, quelle righe producono silenziosamente valori zero che alterano i risultati. Includi sempre questo filtro dopo unwrap.
Il range [5m] definisce la dimensione della finestra. Range più brevi (1m) danno dati più granulari ma più rumorosi. Range più lunghi (15m, 1h) smussano i picchi. Per le dashboard, 5m è un buon punto di partenza.
Come configuro la retention dei log in Loki 3.x?
In Loki 3.x, la retention è gestita dal compactor. Imposta retention_period sotto limits_config e abilita il compactor con retention_enabled: true. La retention predefinita in Loki 3.0+ è 0s (conserva per sempre), quindi devi configurarla esplicitamente o il disco si riempirà.
Il loki-config.yml qui sopra include già la retention. Ecco come interagiscono le impostazioni:
compactor:
retention_enabled: true # Deve essere true, altrimenti il compactor compatta soltanto (nessuna eliminazione)
retention_delete_delay: 2h # Attendere 2h dopo aver marcato i chunk prima di eliminarli
compaction_interval: 10m # Frequenza di esecuzione del compactor
limits_config:
retention_period: 720h # 30 giorni come predefinito globale
Il compactor gira come parte del processo Loki in modalità nodo singolo. Scansiona l'indice TSDB, identifica i chunk più vecchi del periodo di retention, li marca per l'eliminazione, poi li rimuove dopo il retention_delete_delay. Il ritardo ti dà una finestra di recupero in caso di configurazione errata.
Retention per stream
Puoi impostare periodi di retention diversi per stream di log diversi. I log ad alto volume e basso valore (come l'output di debug) possono scadere più velocemente:
limits_config:
retention_period: 720h
retention_stream:
- selector: '{job="nginx", type="access"}'
priority: 1
period: 336h # 14 giorni per i log di accesso
- selector: '{severity="debug"}'
priority: 2
period: 72h # 3 giorni per i log di debug
I valori di priorità più alti vincono quando più selettori corrispondono allo stesso stream. Un log di accesso Nginx a livello debug corrisponde a entrambe le regole. La priorità 2 vince, quindi ottiene 3 giorni di retention.
Dimensionamento della retention
Stima l'utilizzo disco prima di impegnarti su un periodo di retention. Loki comprime bene i log (rapporto 5-10x con Snappy), ma i numeri si accumulano su server attivi:
| Volume grezzo/giorno | Compresso (stima) | Retention 7 giorni | Retention 30 giorni | Retention 90 giorni |
|---|---|---|---|---|
| 100 MB | ~15 MB | ~105 MB | ~450 MB | ~1,35 GB |
| 500 MB | ~75 MB | ~525 MB | ~2,25 GB | ~6,75 GB |
| 1 GB | ~150 MB | ~1,05 GB | ~4,5 GB | ~13,5 GB |
| 5 GB | ~750 MB | ~5,25 GB | ~22,5 GB | ~67,5 GB |
Queste sono stime. La compressione effettiva dipende dal contenuto dei log. I log ripetitivi (log di accesso con percorsi simili) si comprimono meglio dell'output di debug casuale. Monitora l'utilizzo reale con:
du -sh ~/loki-stack/loki-data/
Esegui questo comando settimanalmente per individuare crescite inattese prima di esaurire lo spazio disco.
Come ottimizzo Loki per la produzione su un singolo VPS?
Una configurazione Loki predefinita funziona per i test ma richiede ottimizzazioni per un VPS di produzione. Le modifiche seguenti riducono i picchi di memoria, proteggono contro stream di log fuori controllo e rafforzano lo stack per un server esposto a internet.
Vincolare Loki a localhost
Già fatto nel docker-compose.yml qui sopra (127.0.0.1:3100:3100). Verifica dopo il deployment:
ss -tlnp | grep 3100
Dovresti vedere 127.0.0.1:3100, non 0.0.0.0:3100. Fai lo stesso per Grafana sulla porta 3000.
Regole del firewall
Se usi ufw, blocca l'accesso esterno alle porte di logging:
sudo ufw deny 3100/tcp comment "Loki - localhost only"
sudo ufw deny 3000/tcp comment "Grafana - localhost only"
sudo ufw status numbered
Dato che le porte sono già vincolate a localhost, il firewall è una difesa in profondità. Se qualcuno modifica accidentalmente il file compose per vincolare a 0.0.0.0, il firewall blocca comunque l'accesso esterno.
Nascondere le informazioni di versione
La divulgazione della versione aiuta gli attaccanti a prendere di mira vulnerabilità note. Il GF_ANALYTICS_REPORTING_ENABLED=false nel file compose disabilita già la telemetria di Grafana. L'endpoint /loki/api/v1/status/buildinfo di Loki espone i dettagli della versione, ma dato che Loki è vincolato a localhost, solo i processi locali possono accedervi.
Se metti Grafana dietro un reverse proxy (Nginx, Caddy), aggiungi queste impostazioni Nginx:
server_tokens off;
proxy_hide_header X-Powered-By;
Cambiamenti importanti di Loki 3.x
Se stai migrando da una configurazione Loki 2.x o segui un tutorial più vecchio, fai attenzione a questi cambiamenti:
| Cambiamento | Default Loki 2.x | Default Loki 3.x | Azione richiesta |
|---|---|---|---|
| Schema | v11/v12 | v13 | Usa schema: v13 con store: tsdb |
| Index store | boltdb-shipper |
tsdb |
Migra a TSDB (BoltDB deprecato) |
| Retention | 0s (conserva per sempre) | 0s (conserva per sempre) | Imposta retention_period esplicitamente |
| Metadati strutturati | Disabilitato | Abilitato | Richiede schema v13 |
| Max label per serie | 30 | 15 | Riduci la cardinalità delle label o aumenta il limite |
| Immagine Docker | Include shell BusyBox | Nessuna shell | Non è possibile docker exec nel container |
L'immagine Docker senza shell di Loki 3.6+ significa che non puoi eseguire docker exec -it loki sh per il debug. Controlla invece i log con docker compose logs loki e la disponibilità con curl http://127.0.0.1:3100/ready.
Filesystem vs object storage
Per un singolo VPS, lo storage su filesystem è la scelta corretta. L'object storage (S3, GCS, MinIO) aggiunge complessità e latenza che si giustifica solo quando hai bisogno di:
- Più istanze Loki che condividono gli stessi dati
- Storage illimitato oltre il disco del VPS
- Replica cross-region
Resta con lo storage su filesystem finché non superi le capacità di un singolo nodo.
Monitorare lo stack
Controlla l'utilizzo risorse dei container in esecuzione:
docker stats --no-stream
Controlla i log Loki per avvisi ed errori:
docker compose logs loki --tail=50 | grep -E "level=(error|warn)"
Problemi comuni e soluzioni:
stream limit exceeded: aumentamax_streams_per_userinlimits_config. Di solito causato da label ad alta cardinalità.ingestion rate limit reached: aumentaingestion_rate_mb. Succede durante i picchi di log (deployment, tempeste di errori).WAL replay: normale all'avvio. Loki sta recuperando le scritture non confermate dal write-ahead log.- Alto utilizzo di memoria: riduci
max_size_mbinchunk_cache_configo abbassaingestion_burst_size_mb.
Promtail è deprecato? Dovrei usare Grafana Alloy al suo posto?
Sì. Promtail è entrato nel supporto a lungo termine il 13 febbraio 2025 e ha raggiunto la fine del ciclo di vita il 2 marzo 2026. Non verranno più rilasciati aggiornamenti, correzioni di bug o patch di sicurezza. Grafana Alloy è il sostituto ufficiale. È la distribuzione di Grafana Labs dell'OpenTelemetry Collector e gestisce log, metriche, trace e dati di profiling in un singolo agente.
Perché questo tutorial usa ancora Promtail
I concetti di configurazione di Promtail si trasferiscono direttamente ad Alloy. Gli scrape config, le regole di relabel e gli stage della pipeline funzionano allo stesso modo. Imparare Promtail è ancora utile perché:
- Milioni di deployment esistenti lo usano
- Lo strumento di migrazione Alloy converte automaticamente le configurazioni Promtail
- Capire Promtail facilita il debug delle configurazioni Alloy
- Il volume di ricerca per tutorial Promtail resta alto e i concetti sono trasferibili
Migrare ad Alloy
Converti la tua configurazione Promtail nel formato Alloy con un comando:
alloy convert --source-format=promtail --output=alloy-config.alloy promtail-config.yml
Questo genera un file di configurazione in formato Alloy. Controlla l'output prima di distribuire. Il convertitore gestisce la maggior parte dei casi ma potrebbe richiedere aggiustamenti manuali per stage di pipeline personalizzati.
Poi sostituisci il servizio Promtail in docker-compose.yml:
alloy:
image: grafana/alloy:v1.14.1
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12345
volumes:
- ./alloy-config.alloy:/etc/alloy/config.alloy:ro
- /var/log:/var/log:ro
- /var/log/journal:/var/log/journal:ro
- /run/log/journal:/run/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
depends_on:
- loki
networks:
- loki-net
Rimuovi il blocco del servizio promtail ed esegui docker compose up -d. Alloy inizierà a raccogliere le stesse sorgenti di log usando la configurazione convertita.
Per nuovi deployment da zero, usa Alloy fin dall'inizio. Per installazioni Promtail esistenti, pianifica la migrazione senza urgenza immediata. Il binario Promtail continua a funzionare. Fissa il tag dell'immagine (grafana/promtail:3.6.7) per controllare cosa gira.
Come interrogo i log Loki in modo programmatico tramite l'API HTTP?
Loki espone un'API REST per query programmatiche dei log. È il modo in cui integri Loki con script, pipeline di alerting o il livello di analisi log con IA trattato in Analisi dei log con IA tramite Ollama su un VPS: rilevare anomalie con un LLM locale.
L'API accetta le stesse query LogQL che usi in Grafana. L'endpoint principale è /loki/api/v1/query_range per le query con intervallo temporale.
Interrogare log recenti
END=$(date +%s)000000000
START=$(( $(date +%s) - 3600 ))000000000
curl -s "http://127.0.0.1:3100/loki/api/v1/query_range" \
--data-urlencode "query={unit=\"sshd.service\"} |= \"Failed password\"" \
--data-urlencode "start=$START" \
--data-urlencode "end=$END" \
--data-urlencode "limit=50" | jq .
I parametri start e end usano timestamp Unix in nanosecondi. Il calcolo shell sopra determina "adesso meno 1 ora" e aggiunge nove zeri per la precisione in nanosecondi.
La risposta è in JSON:
{
"status": "success",
"data": {
"resultType": "streams",
"result": [
{
"stream": {"unit": "sshd.service", "severity": "info"},
"values": [
["1710850000000000000", "Mar 19 14:00:00 vps sshd[1234]: Failed password for root from 203.0.113.5 port 22"]
]
}
]
}
}
Ogni valore è una coppia [timestamp_nanosecondi, riga_di_log]. È esattamente il formato che parserai quando invii i log a un LLM locale per l'analisi.
Interrogare label e stream
Elencare tutti i nomi delle label:
curl -s "http://127.0.0.1:3100/loki/api/v1/labels" | jq .
Elencare i valori di una label specifica:
curl -s "http://127.0.0.1:3100/loki/api/v1/label/unit/values" | jq .
Questi endpoint sono utili per costruire query dinamiche negli script di automazione. Puoi enumerare tutte le unit, poi interrogare ciascuna per errori.
Query istantanee
Per query "in questo momento" senza intervallo temporale, usa /loki/api/v1/query:
curl -s "http://127.0.0.1:3100/loki/api/v1/query" \
--data-urlencode 'query=count_over_time({job="systemd-journal"} |= "error" [1h])' | jq .
Questo restituisce un singolo punto dati: il conteggio degli errori del journal nell'ultima ora. Utile per controlli di salute e script di monitoraggio.
Risoluzione dei problemi
Promtail mostra "permission denied" per il socket Docker:
Il container Promtail ha bisogno di accesso in lettura a /var/run/docker.sock. Controlla i permessi del socket sull'host:
ls -la /var/run/docker.sock
Il socket appartiene tipicamente a root:docker. L'immagine Promtail gira come root di default, quindi di solito funziona. Se esegui Promtail con un utente personalizzato, quell'utente deve essere nel gruppo docker.
Nessun log del journal appare:
Prima di tutto, controlla nei log di Promtail il messaggio support for reading the systemd journal is not compiled into this build of promtail. Se lo vedi, l'immagine Docker non supporta lo scraping del journal. Installa Promtail come binario sull'host o passa a Grafana Alloy (vedi le sezioni sopra).
Se Promtail gira come binario host con supporto journal, verifica che la directory del journal esista:
ls -la /var/log/journal/
Se non esiste, systemd sta usando storage journal volatile (solo in memoria). Abilita lo storage persistente:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Poi aggiorna il mount di volume Promtail in docker-compose.yml da /run/log/journal a /var/log/journal e riavvia:
docker compose up -d promtail
Loki segnala "too many outstanding requests":
Il carico di query supera la capacità di Loki. Riduci l'intervallo temporale delle query o aggiungi limiti:
limits_config:
max_query_parallelism: 16
max_query_series: 500
Grafana mostra "Data source connected, no labels found":
Loki ha bisogno di qualche minuto per acquisire e indicizzare i primi log. Attendi 2-3 minuti, poi riprova la query. Verifica che Loki sia pronto:
curl -s http://127.0.0.1:3100/ready
Controllare i log di tutti i servizi contemporaneamente:
docker compose logs -f --tail=50
Questo segue tutti e tre i servizi. Filtra i problemi:
docker compose logs --tail=100 | grep -i error
Prossimi passi
La tua pipeline Loki sta raccogliendo log da systemd, Docker e Nginx. Puoi interrogarli con LogQL in Grafana o tramite l'API HTTP.
Da qui:
- Analisi dei log con IA tramite Ollama su un VPS: rilevare anomalie con un LLM locale Invia questi log a un LLM locale per il rilevamento delle anomalie e l'alerting automatizzato
- AIOps su un VPS: gestione dei server con IA e strumenti open source Panoramica dello stack AIOps self-hosted
- [-> self-host-signoz-openobserve-vps] Se preferisci un APM all-in-one allo stack Grafana
Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.
Pronto a provare?
Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.
Vedi piani VPS