Grafana 13.3+Prometheus 3.15+Loki 3.7+Alloy 1.21 用一份 docker-compose 就能架完,五個容器實測記憶體約 645MB,能同時看指標、查日誌、發告警。
為什麼已經有 Uptime Kuma 還需要這一套
如果你照著站上Uptime Kuma 完整教學架了監控,你已經知道「服務掛了會叫」。但你一定有過這種時刻:手機跳出「網站 503」,你打開終端機、翻 docker logs、猜是哪個容器、猜是不是記憶體爆掉。
Uptime Kuma 回答的是「活著嗎」;Grafana 這套回答的是「為什麼」。兩者不是替代關係:
| 你有的工具 | 它回答的問題 | 對應元件 |
|---|---|---|
| Uptime Kuma | 服務活著嗎?憑證過期沒? | 黑箱探測(HTTP/TCP/Ping) |
| Prometheus | CPU、記憶體、磁碟、連線數現在多少?趨勢往哪走? | 白箱指標(metrics) |
| Loki | 出事那三分鐘,log 裡到底寫了什麼? | 日誌聚合(logs) |
| Grafana | 把上面三個放在同一張圖上,並且在超標時叫人 | 儀表板+告警 |
換句話說,Uptime Kuma 是門口的警衛,Grafana 堆疊是整棟樓的監視器加電表。這篇就是把後者架起來,而且不用按任何一次滑鼠——所有資料來源都用檔案宣告,重建環境不用重來一遍。
一表看懂:四個角色各做什麼
| 元件 | 版本(2026-10-29 查證) | 授權 | 星數 | 角色 | 實測記憶體 |
|---|---|---|---|---|---|
| Grafana | 13.3.0 | AGPL-3.0 | 約 77.6K | 儀表板、告警、資料來源代理 | 約 494 MB |
| Prometheus | 3.15.1 | Apache-2.0 | 約 66.9K | 拉取並儲存時序指標 | 約 37 MB |
| Loki | 3.7.9 | AGPL-3.0 | 約 29.1K | 日誌聚合(「日誌版的 Prometheus」) | 約 56 MB |
| Alloy | 1.21.3 | Apache-2.0 | 約 3.7K | 採集代理,把日誌送進 Loki | 約 51 MB |
| node-exporter | 1.9.1 | Apache-2.0 | — | 把主機 CPU/記憶體/磁碟變成指標 | 約 7 MB |
加起來大約 645 MB 記憶體,這是在一台 7.7GB RAM 的 VPS 上實測的數字。硬碟則看你保留多久的日誌與指標:Prometheus 預設保留 30 天、Loki 這份設定保留 31 天(744 小時)。
資料是怎麼流的
不用畫圖也看得懂,就三條線:
| 方向 | 起點 | 經過 | 終點 |
|---|---|---|---|
| 指標線 | node-exporter(主機) | Prometheus 每 15 秒來拉一次 | Prometheus 時序資料庫 |
| 日誌線 | 各容器的 stdout | Alloy 讀 Docker socket | Loki |
| 呈現線 | Prometheus+Loki | Grafana 當資料來源查詢 | 你的瀏覽器 |
關鍵差別是「推」與「拉」:Prometheus 是主動去拉(pull),所以你的服務只要有 /metrics 端點就會被動被監控;Loki 則是被動接收(push),所以需要 Alloy 這種代理去把日誌推過去。理解這點,後面所有設定就都不難了。
安裝:目錄與三份設定檔
先在要放監控的機器上開一個目錄:
mkdir -p ~/observability/grafana/provisioning/datasources
cd ~/observability
總共會用四個檔案:docker-compose.yaml、prometheus.yml、loki-config.yaml、config.alloy,外加一個 Grafana 的資料來源宣告檔。
1. docker-compose.yaml
services:
grafana:
image: grafana/grafana:13.3.0
container_name: obs-grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=changeme
- GF_USERS_ALLOW_SIGN_UP=false
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
depends_on:
- prometheus
- loki
prometheus:
image: prom/prometheus:v3.15.1
container_name: obs-prometheus
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
loki:
image: grafana/loki:3.7.9
container_name: obs-loki
restart: unless-stopped
command: -config.file=/etc/loki/config.yaml
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml:ro
- loki-data:/loki
alloy:
image: grafana/alloy:v1.21.3
container_name: obs-alloy
restart: unless-stopped
command:
- run
- /etc/alloy/config.alloy
- --storage.path=/var/lib/alloy/data
- --server.http.listen-addr=0.0.0.0:12345
ports:
- "12345:12345"
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- alloy-data:/var/lib/alloy/data
depends_on:
- loki
node-exporter:
image: prom/node-exporter:v1.9.1
container_name: obs-node-exporter
restart: unless-stopped
command:
- --path.rootfs=/host
pid: host
volumes:
- /:/host:ro,rslave
ports:
- "9100:9100"
volumes:
grafana-data:
prometheus-data:
loki-data:
alloy-data:
三個地方特別容易寫錯,先標出來:
pid: host不能省。node-exporter 要讀得到主機的/proc才能算出真實的 CPU 使用率;少了它,你會在圖表上看到容器自己的視角,數字全部失真。- Docker socket 一定要掛
:ro。Alloy 只需要「讀」日誌,給寫入權限等於把整台主機的 root 交給一個容器。 - 映像檔版本我全部釘死(不是
:latest)。監控系統最怕的就是「重開機之後 Grafana 升級、面板全跑版」。
2. prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
monitor: homelab
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: node
static_configs:
- targets: ["node-exporter:9100"]
srape_interval: 15s 是很實用的折衷:再短(例如 5s)會讓時序資料量暴增三倍,再長則告警會慢半拍。如果你的機器很吃緊(1GB RAM 以下),改成 60s 也完全夠用。
設定檔寫完,可以先用官方工具驗語法,不要等到容器起不來才猜:
docker run --rm \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml:ro \
--entrypoint promtool prom/prometheus:v3.15.1 \
check config /etc/prometheus/prometheus.yml
實測輸出:
Checking /etc/prometheus/prometheus.yml
SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax
3. loki-config.yaml
這是整篇最容易踩雷的檔案。官方 getting-started 範例是給「分散式部署」用的(讀寫分離+MinIO+nginx 閘道,共七個容器),照抄到小主機上會很痛苦。單機版用下面這份:
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 744h
allow_structured_metadata: true
reject_old_samples: false
compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem
ruler:
storage:
type: local
local:
directory: /loki/rules
四個關鍵字說明:
retention_period: 744h是 31 天。光寫這行沒有用,還要加上compactor區塊的retention_enabled: true與delete_request_store: filesystem,否則日誌只會一直長大、不會被刪。reject_old_samples: false是本文實測出來最重要的一行,原因見下面「五個坑」的第一條。allow_structured_metadata: true讓 Loki 自動辨識日誌等級(打開後查詢結果會多一個detected_level標籤,可以直接篩error)。auth_enabled: false是單租戶模式。只有自己用沒問題,但不要把 3100 埠直接曝露到公網,Loki 在這模式下沒有驗證。
4. config.alloy
Alloy 是 Grafana 近年用來取代 Promtail/Grafana Agent 的新代理,設定語言是 HCL 風格的 .alloy:
discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
refresh_interval = "5s"
}
discovery.relabel "containers" {
targets = []
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container"
}
rule {
source_labels = ["__meta_docker_container_log_stream"]
target_label = "stream"
}
rule {
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
target_label = "service"
}
}
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.containers.targets
forward_to = [loki.write.local.receiver]
relabel_rules = discovery.relabel.containers.rules
refresh_interval = "5s"
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
那段 regex = "/(.*)" 不是裝飾:Docker 回報的容器名稱長得像 /obs-grafana,前面有一個斜線,不去掉的話你的標籤會變成 container="/obs-grafana",之後查詢怎麼寫都對不上。
5. Grafana 資料來源自動掛載
grafana/provisioning/datasources/ds.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
uid: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
jsonData:
timeInterval: 15s
- name: Loki
type: loki
uid: loki
access: proxy
url: http://loki:3100
jsonData:
maxLines: 1000
這段是「可重現」的關鍵。手動點 UI 加資料來源也可以,但下次換機器你就得再點一次、還可能點錯;用檔案宣告,git clone 完 docker compose up -d 就是一台設定好的監控主機。
啟動與驗證:每一項都要看到 200
docker compose config --quiet && echo "設定檔語法 OK"
docker compose up -d
等大約 30 秒再驗證。實測輸出如下:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/api/health # 200
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9090/-/ready # 200
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3100/ready # ready
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9100/metrics # 200
再確認 Prometheus 真的有抓到兩個目標(1 代表健康):
curl -s 'http://127.0.0.1:9090/api/v1/query?query=up'
實測回傳:
{"status":"success","data":{"resultType":"vector","result":[
{"metric":{"__name__":"up","instance":"localhost:9090","job":"prometheus"},"value":[1793304221.051,"1"]},
{"metric":{"__name__":"up","instance":"node-exporter:9100","job":"node"},"value":[1793304221.051,"1"]}]}}
Grafana 的資料來源也可以直接用 API 驗(不需要開瀏覽器):
curl -s -u admin:changeme \
'http://127.0.0.1:3000/api/datasources/proxy/uid/prometheus/api/v1/query?query=up'
回傳跟上面一樣的 JSON,就代表「Grafana → Prometheus」這一段通了。這個指令很好用,出問題時可以快速二分法:是資料源壞了,還是面板壞了。
實測踩到的五個坑
這五條都是我實際把這套跑起來時遇到的,不是抄文件:
坑 1:Loki 一直回 400,日誌全部被丟掉
Alloy 第一次啟動時會把 Docker 裡既有的歷史日誌整批讀進來,其中很多是幾個月前的。Loki 的 reject_old_samples 預設會拒絕太舊的樣本,於是 Alloy 不斷重試、不斷收到 400:
level=error msg="final error sending batch, no retries left, dropping data"
status=400 error="... entry has timestamp too old: 2026-03-05T17:26:28Z,
oldest acceptable timestamp is: 2026-10-22T20:02:51Z"
解法就是上面那份 loki-config.yaml 裡的 reject_old_samples: false。改完重建容器,實測 timestamp too old 錯誤數從滿畫面變成 0,Alloy 的 dropping data 也是 0。
如果你不想全關掉這個保護,替代做法是把 reject_old_samples_max_age 調長(例如 2160h)。但對「剛接上既有主機」的情境,先關掉再讓資料自然過期,實務上最省事。
坑 2:官方 getting-started 的 compose 檔不適合小主機
我照官方文件抓了 examples/getting-started/docker-compose.yaml(Loki v3.7.0),打開發現它是分散式架構:read、write、backend 三個 Loki 角色+minio 物件儲存+nginx 當閘道+flog 產生假日誌,一共七個容器。這在單機上完全是小題大作,而且 MinIO 社群版早已停止維護(站上自架 S3 那篇有詳細說明替代方案)。
結論:官方 Docker 安裝頁面同時提供「單容器 docker run」與「Compose」兩種,小主機請走單機設定,不要抄 getting-started 的 compose。
坑 3:想改埠號時,compose 的 override 是「相加」不是「取代」
我為了避開已被佔用的 3100 埠,寫了一個 docker-compose.test.yaml 想覆蓋成 3101:
services:
loki:
ports:
- "3101:3100"
結果啟動直接失敗:
failed to bind host port 0.0.0.0:3100/tcp: address already in use
因為 Compose 合併 ports 是聯集,3100 與 3101 都留著。正確寫法要用 !override 標籤:
services:
loki:
ports: !override
- "3101:3100"
(需要 Docker Compose v2.24 以上;本篇環境是 v5.1.0。)這個坑值得記住,因為它也會發生在你幫其他服務寫 dev/prod 兩份 compose 的時候。
坑 4:Grafana 13 的記憶體比你想的高
實測閒置狀態:Grafana 約 494 MB,比 Loki(56 MB)+Prometheus(37 MB)+Alloy(51 MB)加起來還多。這是 Grafana 13 拉了更多前端資源與外掛的結果。在 1GB RAM 的小機器上,這一顆就會把記憶體吃光——如果你的機器很迷你,考慮改用 grafana/grafana:13.3.0-slim(不含預載外掛),或把 Grafana 放在比較大的機器上、其餘元件留在小主機。
坑 5:Loki 剛啟動時 /ready 會回 503,不要以為壞了
Ingester not ready: waiting for 15s after being ready
這是 Loki 的正常行為(ingester 暖機),大約 15~30 秒後就變 ready。如果你用腳本自動部署,健康檢查的 start_period 要設 30 秒以上,不然會被誤判成失敗而重啟,然後永遠起不來。
PromQL 與 LogQL 起手式
裝好之後最常被問的是「那我現在要看什麼」。這幾條貼進 Grafana 的 Explore 就能用:
| 用途 | 語法 | 備註 |
|---|---|---|
| 主機 CPU 使用率(%) | 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100) | 實測回傳約 18%,與 top 相符 |
| 可用記憶體(%) | 100 * node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes | 比 MemFree 準,因為含快取 |
| 根目錄磁碟使用率 | 100 - 100 * node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} | 小主機最常爆的一項 |
| 目標是否活著 | up == 0 | 搭配告警用,最實用的一條 |
| 找某容器最近的錯誤 | {container="obs-grafana"} |= "error" | LogQL,|= 是「包含」 |
| 跨容器找錯誤 | {service=~".+"} |~ "(?i)error|fail" | 正則比對,注意 Loki 預設只回最近 1,000 行 |
LogQL 這一段有個實測發現:開啟 allow_structured_metadata 後,查詢結果會自動多一個 detected_level 標籤,於是你可以直接寫 {detected_level="error"},不用再去猜每支程式把錯誤寫成 ERROR、error 還是 ERR。這是 Loki 3.x 相對舊版最省事的功能之一。
告警:不要只看儀表板,要會叫人
Grafana 內建的統一告警(Alerting)可以直接對 Prometheus 下條件,不用另外架 Alertmanager。最小可用的一條規則長這樣(在 UI 建,或寫成 provisioning 檔):
- 查詢:
up == 0 - 條件:
IS BELOW 1 - 持續時間:
For 2m(避免瞬斷誤報) - 通知:Telegram/Email/Slack contact point
建議一開始只設三條:up == 0(服務掛)、磁碟 > 90%、記憶體可用 < 10%。這三條能涵蓋自架環境八成的意外。站上Portainer 完整教學裡提到的容器重啟循環,用 up 這條也能抓到。
備份與資料保留
監控資料通常「掉了不痛」,但設定檔掉了很痛。備份策略分兩層:
| 對象 | 要不要備份 | 做法 |
|---|---|---|
docker-compose.yaml、prometheus.yml、loki-config.yaml、config.alloy、provisioning | 一定要 | 丟進 git,這是整套系統真正的資產 |
Prometheus 資料(prometheus-data) | 通常不用 | 30 天後自然輪替,除非要做長期趨勢分析 |
Grafana 資料庫(grafana-data) | 建議要 | 面板 JSON 用 API 匯出更乾淨:GET /api/dashboards/uid/<uid> |
Loki 日誌(loki-data) | 看用途 | 合規需求才需要;一般除錯靠 31 天足夠 |
如果你已經有 restic 或 Kopia 在跑(自架備份三選一那篇有完整設定),把上面那幾個 YAML 目錄加進備份清單就好,不需要備份整個 volume。
已知限制:這套不是萬靈丹
照本站慣例,講清楚沒做的部分:
- 本文只實測單機 Docker 部署,沒有測 Kubernetes、沒有測多節點 Loki 叢集。站上另有一篇 Prometheus 與 Kubernetes 監控談 K8s 情境,兩者不要混用同一份設定。
- 沒有實作 tracing。這套是 metrics+logs,缺了 traces 那一塊(要 Loki 3.x 的衍生欄位或 Tempo 才能串起來)。對大部分自架服務,metrics+logs 已經夠用。
- 沒有測高流量情境。實測的是五個容器、每天幾十 MB 日誌的家庭/小型團隊規模。日誌量到每天 GB 級時,Loki 的儲存與查詢就會需要調 chunk 大小與 compactor 參數,這篇沒有涵蓋。
- 版本會動。文中版本號是 2026-10-29 以 GitHub Releases API 查證的當下最新版;Grafana 生態改版很快(例如 Alloy 取代 Promtail 就是近兩年的事),實際安裝前建議再確認一次映像檔標籤存在。
reject_old_samples: false不是無腦推薦。它會讓 Loki 接受很舊的日誌,如果你有「日誌時間戳必須可信」的稽核需求,請改用調長max_age的做法。
誰適合、誰不需要
| 情境 | 建議 |
|---|---|
| 只有一台 VPS、兩個網站 | 先用 Uptime Kuma就好,這套對你可能過重 |
| 自架 5 個以上服務、常需要查「為什麼變慢」 | 這套值得,投入約半天 |
| 有合規要求、日誌要留 30 天以上可查 | 這套是標配 |
| 團隊多人,需要共用儀表板 | 這套很適合,Grafana 權限與資料夾管理成熟 |
| 只想看有沒有掛掉 | 不需要,Uptime Kuma 更輕 |
站內延伸閱讀
- Uptime Kuma 完整教學 2026:91K 星自架監控神器 — 黑箱探測,與本文互補,先架這套再架本文這套最順。
- 深入探索 Prometheus 與 Kubernetes 監控系統 — 如果你跑的是 K8s,從這篇接續。
- Portainer 完整教學 2026:Docker 圖形化管理 — 容器管理介面,跟本文的 Grafana 面板搭配使用。
- Traefik 完整教學 2026:Docker Labels 自動路由 — 用反向代理把 Grafana 接到網域與 HTTPS。
原始來源
- Grafana Docker 安裝:grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
- Grafana Docker 環境變數與映像變體:Configure a Grafana Docker image
- Prometheus 安裝:prometheus.io/docs/prometheus/latest/installation/
- Prometheus 設定參考:configuration/configuration
- node-exporter 指南:prometheus.io/docs/guides/node-exporter/
- Loki Docker 安裝:grafana.com/docs/loki/latest/setup/install/docker/
- Loki HTTP API(驗證查詢用):loki-http-api
- Alloy Docker 安裝:grafana.com/docs/alloy/latest/set-up/install/docker/
- 資料來源設定:Prometheus/Loki
- Grafana 告警:grafana.com/docs/grafana/latest/alerting/
利益揭露:本文無聯盟連結。文中版本號、星數與記憶體數字為 2026-10-29 在本站 VPS(7.7GB RAM、Docker 29.2.1、Compose v5.1.0)實測結果,你的環境可能不同。
