一句話結論:單機叢集想集中看 Pod 的 log,可以用單一二進位模式的 Loki,加一個 Alloy 透過 Kubernetes API 收 log。兩份 values 就能跑起來,合計大約只吃 130 MiB 記憶體。
遇到的問題
Pod 出事的時候,最常做的事就是 kubectl logs。
但 Pod 重啟之後,上一次的 log 就看不到了。想跨 Pod、跨時間搜尋,更是做不到。
Loki 是這類需求常見的選擇。麻煩的是,官方 chart 的預設值是為正式環境設計的,直接 helm install 在單機叢集上,不是跑不起來,就是太重。
這篇把 Loki 和收集器 Alloy 需要調整的地方整理出來,所有 values 都在一個全新的單節點 k3s 叢集上實際跑過。
手把手步驟
前提與測試版本
手邊要有一個 Kubernetes 叢集和 Helm。下面的指令與 values,是在這些版本上實測的:
| 元件 | Chart 版本 | 應用程式版本 |
|---|---|---|
| grafana/loki | 7.3.0 | Loki 3.6.12 |
| grafana/alloy | 1.13.1 | Alloy v1.20.1 |
叢集是 k3s v1.31.5(單節點)。版本不同的話,values 的欄位可能不一樣,請對一下 chart 的 values.yaml。
步驟一:加 Helm repo
helm repo add grafana https://grafana.github.io/helm-charts helm repo update 兩個元件都放在 monitoring namespace,下面的指令會自動建立。
步驟二:裝 Loki(單一二進位模式)
Loki 的 chart 預設是 SimpleScalable 模式,需要物件儲存(例如 S3),還會拆成 backend、read、write 好幾組。
單機小規模用不到,改成單一二進位加本機檔案系統。新建 loki-values.yaml:
deploymentMode: SingleBinary loki: auth_enabled: false commonConfig: replication_factor: 1 schemaConfig: configs: - from: "2024-04-01" store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h storage: type: filesystem limits_config: retention_period: 24h compactor: retention_enabled: true delete_request_store: filesystem compaction_interval: 1h singleBinary: replicas: 1 resources: requests: {cpu: 100m, memory: 256Mi} limits: {cpu: 500m, memory: 1Gi} persistence: enabled: true size: 10Gi # 小規模單機用不到的元件,全部關掉 backend: {replicas: 0} read: {replicas: 0} write: {replicas: 0} gateway: {enabled: false} chunksCache: {enabled: false} resultsCache: {enabled: false} lokiCanary: {enabled: false} test: {enabled: false} sidecar: rules: {enabled: false} monitoring: selfMonitoring: {enabled: false} serviceMonitor: {enabled: false} rules: {enabled: false} 幾個重點。
單一二進位
deploymentMode: SingleBinary 配上 backend、read、write 的 replicas: 0,才會只跑一個 Pod。
保留 24 小時
retention_period: 24h 只是設定了保留時間,真正去刪舊資料的是 compactor,所以 compactor.retention_enabled: true 要一起開,而且要指定 delete_request_store。
少了這兩項,log 不會被清掉,磁碟會慢慢長大。
關掉一堆東西
gateway、兩個 cache、canary、測試 Pod、規則 sidecar 在正式環境有用,單機用不到,關掉可以少跑好幾個 Pod。
安裝:
helm upgrade --install loki grafana/loki --version 7.3.0 \ -n monitoring --create-namespace \ -f loki-values.yaml --wait --timeout 6m 確認:
kubectl get pods -n monitoring loki-0 應該是 1/1 Running。
步驟三:裝 Alloy 收 log
Alloy 是 Grafana 現在主推的收集器,用 Kubernetes API 把 Pod 的 log 轉送到 Loki。新建 alloy-values.yaml:
controller: type: deployment replicas: 1 rbac: create: true serviceAccount: create: true alloy: resources: requests: {memory: 256Mi} limits: {memory: 512Mi} configMap: create: true content: | discovery.kubernetes "pods" { role = "pod" } discovery.relabel "pods" { targets = discovery.kubernetes.pods.targets rule { source_labels = ["__meta_kubernetes_namespace"] target_label = "namespace" } rule { source_labels = ["__meta_kubernetes_pod_name"] target_label = "pod" } rule { source_labels = ["__meta_kubernetes_pod_container_name"] target_label = "container" } rule { source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"] target_label = "app" } } loki.source.kubernetes "pods" { targets = discovery.relabel.pods.output forward_to = [loki.write.default.receiver] } loki.write "default" { endpoint { url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push" } } 設定檔是一條管線,從上往下:
-
discovery.kubernetes找出所有 Pod。 -
discovery.relabel把 namespace、Pod 名稱、容器名稱、app 標籤變成 Loki 的 label。 -
loki.source.kubernetes讀取這些 Pod 的 log。 -
loki.write推送到 Loki。
最後的 url 是上一步 Loki 的 Service 名稱,release 名稱不是 loki 或 namespace 不同的話,這裡要跟著改。
因為是用 Kubernetes API 讀 log,這個元件不需要特權容器,也不用掛載主機的目錄,所以用一個 Deployment 就夠,不需要每個節點一個 DaemonSet。
安裝:
helm upgrade --install alloy grafana/alloy --version 1.13.1 \ -n monitoring -f alloy-values.yaml --wait --timeout 5m 步驟四:驗證
Loki 有沒有收到 log
Loki 沒有網頁介面,用 Kubernetes 的 service proxy 直接問它:
kubectl get --raw \ "/api/v1/namespaces/monitoring/services/loki:3100/proxy/loki/api/v1/labels" 收到 log 之後會看到這些 label:
{"status":"success","data":["app","container","instance","job","namespace","pod","service_name"]} 再實際查幾行 log。下面的查詢是 {namespace="monitoring"}(已經 URL 編碼),換成自己有 Pod 的 namespace 也可以:
kubectl get --raw \ "/api/v1/namespaces/monitoring/services/loki:3100/proxy/loki/api/v1/query_range?query=%7Bnamespace%3D%22monitoring%22%7D&limit=3" 回傳的 JSON 裡,data.result[].stream 是 label,values 是 [時間戳, log 內容] 的陣列。
接上 Grafana(選用)
如果叢集裡已經有 Grafana,新增一個 Loki 資料來源就能用 Explore 查 log:
- Type:Loki
- URL:
http://loki.monitoring.svc.cluster.local:3100
按 Save & test,看到 Data source successfully connected. 就接通了。接著在 Explore 查 {namespace="monitoring"},查詢語言是 LogQL。
資源用量
裝完幾分鐘後,用 kubectl top pods 量到的數字:
| 元件 | 記憶體 |
|---|---|
| Loki | 約 73 MiB |
| Alloy | 約 55 MiB |
合計約 130 MiB。這是剛裝好、流量很小的數字,log 量變大之後 Loki 的用量會增加,上線之後請再觀察一陣子。
踩到的坑
1. Loki 的 chart 用預設值根本裝不起來
我直接 helm install grafana/loki,不加任何設定,會出現:
Error: execution error at (loki/templates/write/statefulset-write.yaml:50:28): Please define loki.storage.bucketNames.chunks 補上檔案系統的儲存設定、但仍用預設的部署模式,則是:
Cannot run scalable targets (backend, read, write) or distributed targets without an object storage backend. 原因就是預設的 SimpleScalable 模式需要物件儲存。單機請明確設定 deploymentMode: SingleBinary,並且把 backend、read、write 的 replicas 設成 0。
2. Loki 啟動時的 memberlist 警告可以忽略
單一二進位模式第一次啟動時,log 裡會出現類似這樣的警告:
Failed to resolve loki-memberlist.monitoring.svc.cluster.local ... 這是 Loki 在嘗試加入叢集,單副本的情況下找不到其他成員,過幾秒就會繼續正常啟動。只要 loki-0 最後是 1/1 Running 就沒事。
3. 要有 compactor,保留時間才會生效
只設 retention_period 不會刪資料。要同時開 compactor.retention_enabled: true 並指定 delete_request_store,這點在 Loki 官方的 Log retention 文件裡有說明。
4. 看不到節點本身的 log
loki.source.kubernetes 只能收 Pod 的 log。官方文件特別註明,它收不到 Kubernetes 節點本身的 log(例如 systemd 的 log)。
如果也需要節點層級的 log,要另外加其他來源。
為什麼這樣選
Loki 單一二進位,而不是 SimpleScalable
單機沒有高可用的需求,檔案系統加單一副本最簡單。
代價是:這個模式沒有水平擴展,也沒有備援,資料就放在一顆 PVC 上。log 量大到單機撐不住的時候,才需要換成物件儲存加分散式部署。
Alloy,而不是 Promtail
Grafana 的 Promtail 文件上寫得很清楚:Promtail 已經在 2026 年 3 月 2 日結束生命週期(EOL),不再有任何支援與更新,之後的功能開發都在 Alloy。所以新的部署直接用 Alloy。
用 loki.source.kubernetes 的好處,官方文件也寫了:它透過 Kubernetes API 讀 log,不需要特權容器,也不需要 root。
保留只有 24 小時
這是刻意的。單機磁碟有限,log 主要用來排查剛發生的問題,要長期保存的話,需要調大 retention_period 和 PVC。
延伸閱讀
- Grafana Loki:Install the monolithic Helm chart:單一二進位模式的官方安裝說明。
- Grafana Loki:Log retention:
compactor.retention_enabled與delete_request_store的設定。 - Grafana Alloy:loki.source.kubernetes:用 Kubernetes API 收 log 的元件,以及它收不到節點 log 的說明。
- Grafana Alloy:Deploy on Kubernetes:用 Helm 安裝 Alloy。
- Grafana Loki:Promtail agent:Promtail 的 EOL 公告與遷移到 Alloy 的說明。
這篇的 values 在上面列的版本實測過。換版本的話,記得先看 chart 的 values.yaml 欄位有沒有改名。
