iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0

昨天的告警能在出問題時通知我們,但通知只說了「dev 的 API 沒有可用的 Pod」,要知道原因需要看 log。今天用 Loki 把所有 Pod 的 log 集中起來,在 Grafana 裡查詢。


為什麼不用 kubectl logs 就好

kubectl logs 在開發時很好用,但真的出問題時會遇到幾個限制:

  • Pod 不見,log 就跟著不見:Pod 被刪除或重建後,舊的 log 就查不到了。--previous 也只能看到上一次重啟前的。
  • 一次只能看一個 Pod:staging 的 API 有 2~5 個 Pod,要一個一個查,很難看出整體狀況。
  • 沒辦法搜尋過去:想找「昨晚 3 點所有 Pod 的錯誤訊息」,幾乎做不到。

昨天的告警可能在半夜觸發,等你早上起來看,出事的 Pod 可能早就被重建了。所以 log 要在 Pod 消失之前就先收集起來。


Loki 怎麼運作

各個 Pod 的 log
      │
      │  Alloy 收集(透過 K8s API 讀取 log)
      ▼
Loki(儲存 log)
      │
      │  Grafana 用 LogQL 查詢
      ▼
Grafana Explore
  • Alloy:負責收集 log,並幫每筆 log 貼上 namespace、pod、container 等 label,再送到 Loki。
  • Loki:負責儲存 log。它的設計跟 Prometheus 很像,只替 label 建索引,不替 log 內容建索引,所以儲存成本比傳統的全文檢索方案低很多。
  • LogQL:Loki 的查詢語言,寫法跟 PromQL 很像,Day 27 學的 label 篩選可以直接沿用。

以前常見的組合是 Loki + Promtail,但 Promtail 已經停止維護,官方改推 Alloy 來收集 log。


實際操作

今天目標:安裝 Loki 和 Alloy,在 Grafana 加上 Loki 資料來源,查詢 Todo App 的 log,並模擬一次故障,用 log 找出原因。

Step1: 安裝 Loki

  1. 新增 Grafana 官方的 Chart 儲存庫:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
  1. Loki 的 Chart 預設是給大型叢集用的,會開很多元件。在 monitoring/ 建立 loki-values.yaml,改成適合練習的單機模式:
deploymentMode: SingleBinary

loki:
  auth_enabled: false
  commonConfig:
    replication_factor: 1
  storage:
    type: filesystem
  schemaConfig:
    configs:
      - from: "2024-04-01"
        store: tsdb
        object_store: filesystem
        schema: v13
        index:
          prefix: loki_index_
          period: 24h

singleBinary:
  replicas: 1

# 關掉其他部署模式的元件
backend:
  replicas: 0
read:
  replicas: 0
write:
  replicas: 0

# 關掉練習用不到、又很吃記憶體的元件
chunksCache:
  enabled: false
resultsCache:
  enabled: false
lokiCanary:
  enabled: false
test:
  enabled: false
  • deploymentMode: SingleBinary:所有功能跑在一個 Pod 裡,適合本機練習。
  • auth_enabled: false:關掉多租戶,不然每次查詢都要帶租戶 ID。
  • storage.type: filesystem:log 存在 Pod 掛載的磁碟裡。正式環境通常會存到 S3 這類物件儲存。
  • chunksCache:預設會跟叢集要好幾 GB 的記憶體,minikube 撐不住,一定要關掉。
  1. 安裝到 monitoring,跟 Prometheus、Grafana 放在一起:
helm install loki grafana/loki -n monitoring -f loki-values.yaml
kubectl get pods -n monitoring -l app.kubernetes.io/name=loki -w

等 loki-0 和 loki-gateway-... 都 Running 後按 Ctrl + C。
https://ithelp.ithome.com.tw/upload/images/20261007/20183863SIbJBGR7XF.png

Pod 卡在 Pending 的話,用 kubectl describe pod 看 Events。Insufficient memory 代表記憶體不夠,可以先把 dev 縮到 0 個副本騰出空間。

Step2: 安裝 Alloy

  1. 在 monitoring/ 建立 alloy-values.yaml:
controller:
  type: deployment
  replicas: 1

alloy:
  configMap:
    content: |
      // 1. 找出 dev 和 staging 的所有 Pod
      discovery.kubernetes "pods" {
        role = "pod"
        namespaces {
          names = ["dev", "staging"]
        }
      }

      // 2. 把 K8s 的資訊轉成 log 的 label
      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"
        }
      }

      // 3. 透過 K8s API 讀取這些 Pod 的 log
      loki.source.kubernetes "pods" {
        targets    = discovery.relabel.pods.output
        forward_to = [loki.write.default.receiver]
      }

      // 4. 送到 Loki
      loki.write "default" {
        endpoint {
          url = "http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push"
        }
      }

設定由四個區塊串成一條流水線,每個區塊的輸出接到下一個區塊:找出 Pod → 貼上 label → 讀取 log → 送到 Loki。

  • namespaces:只收集 dev 和 staging,減少 minikube 的負擔。
  • controller.type: deployment:Alloy 的 Chart 預設是 DaemonSet,每個 Node 跑一個。但 loki.source.kubernetes 是透過 K8s API 讀取整個叢集的 log,跑多個會重複收集,所以改成只跑 1 個的 Deployment。
  1. 安裝:
helm install alloy grafana/alloy -n monitoring -f alloy-values.yaml
kubectl get pods -n monitoring -l app.kubernetes.io/name=alloy
  1. Running 之後,看 log 確認沒有錯誤:
kubectl logs -n monitoring deploy/alloy --tail=20

Step3: 在 Grafana 加上 Loki 資料來源

資料來源可以在 Grafana UI 手動新增,但跟 Day 28 的 Dashboard 一樣,Grafana 重啟後就會消失。所以改用 kube-prometheus-stack 的 values 設定。

  1. 在 monitoring/ 建立 grafana-loki-values.yaml:
grafana:
  additionalDataSources:
    - name: Loki
      type: loki
      url: http://loki-gateway.monitoring.svc.cluster.local
      access: proxy

跟昨天一樣,先用 helm list -n monitoring 查出 chart 版本,再升級:

helm upgrade monitoring prometheus-community/kube-prometheus-stack -n monitoring --version 91.8.2 --reuse-values -f grafana-loki-values.yaml

--reuse-values 會保留昨天設定的 Alertmanager,不用再傳一次 alertmanager-values.yaml。

升級後 Grafana 的 Pod 可能會重建,原本的 port-forward 會斷線,重新執行一次:

kubectl port-forward svc/monitoring-grafana -n monitoring 3001:80

打開 Grafana,Connections → Data sources 會多一個 Loki。點進去拉到最下面按 Test,顯示成功就完成了。

Step4: 用 LogQL 查詢 log

左側選單 Explore,上方的資料來源選 Loki,切到 Code 模式。

1. 用 label 選出要看的 log

{namespace="staging", container="api"}

大括號的寫法跟 PromQL 一樣,這會列出 staging 所有 API Pod 的 log,不用再一個一個 Pod 查。每筆 log 點開可以看到它的 label,知道是哪個 Pod 印出來的。

2. 用關鍵字過濾內容

{namespace="staging", container="api"} |= "GET"

|= 是「包含這個字串」,常用的還有這幾個:

寫法 意思
|= "error" 包含 error
!= "health" 不包含 health,常用來濾掉健康檢查的 log
|~ "(?i)error|exception" 符合正規表示式,(?i) 代表不分大小寫

大括號裡的 label 篩選會用到索引,速度很快;|= 是把選出來的 log 逐行掃過。所以要先用 label 把範圍縮小,再用關鍵字過濾。

3. 把 log 算成數字

sum by (pod) (count_over_time({namespace="staging", container="api"} [5m]))

count_over_time 計算 5 分鐘內有幾筆 log,結果變成時間序列,可以像 Prometheus 的指標一樣畫成圖。換成 |~ "(?i)error" 過濾後再計算,就是每個 Pod 的錯誤數量。

Step5: 模擬故障,用 log 找原因

把 dev 的 MySQL 停掉,模擬資料庫掛掉:

kubectl scale statefulset todo-dev-mysql -n dev --replicas=0

打開 dev 的 Todo App,新增或重新整理幾次,讓 API 去連資料庫。接著到 Explore 查詢:

{namespace="dev", container="api"} |~ "(?i)error|exception"

會看到 API 連不上 MySQL 的錯誤訊息,log 會明確寫出是哪個連線失敗。dev 的 log level 是 debug,訊息會比 staging 詳細。

查不到結果的話,先拿掉 |~ 那段,看 API 實際印出什麼,再依照 log 的內容調整關鍵字。

這就是實務上排查問題的流程:

收到告警(Day 29)→ 看 Dashboard 找出哪裡異常(Day 28)→ 查 log 找出原因(今天)

確認完把 MySQL 恢復:

kubectl scale statefulset todo-dev-mysql -n dev --replicas=1

Step6: 把設定存進 Git

今天的三個 values 檔都沒有密碼,可以直接 commit:

git pull
git add monitoring/loki-values.yaml monitoring/alloy-values.yaml monitoring/grafana-loki-values.yaml
git commit -m "Add Loki and Alloy for log collection"
git push

練習結束後,到 port-forward 的終端機按 Ctrl + C 停掉。


延伸筆記

1. label 不是越多越好

Loki 是靠 label 建索引,每一組不同的 label 組合會存成一條獨立的資料流(stream)。如果把使用者 ID、請求 ID 這類值很多的欄位設成 label,資料流數量會暴增,Loki 反而變慢、變吃資源。

所以 label 只放 namespace、pod、container 這類值的種類有限、而且常用來縮小範圍的欄位。使用者 ID 這種細節留在 log 內容裡,需要時再用 |= 搜尋。

2. 延伸筆記:可觀測性的三大支柱

業界常說的可觀測性(Observability),通常包含三種資料:

  • Metrics(指標):告訴你「哪裡有問題」,例如記憶體使用率偏高。Day 27~29 的 Prometheus。
  • Logs(日誌):告訴你「為什麼有問題」,例如連不上資料庫的錯誤訊息。今天的 Loki。
  • Traces(追蹤):告訴你「問題卡在哪一段」,一個請求經過前端、API、資料庫,每一段各花多少時間。Grafana 生態系的對應工具是 Tempo,搭配 OpenTelemetry 使用。

Todo App 的架構還不複雜,Metrics 和 Logs 已經足夠排查大部分問題。等服務拆成更多元件,請求要經過好幾個服務時,Traces 的價值才會真正顯現。


小結

  • kubectl logs 只能看單一 Pod,Pod 消失 log 就沒了,要靠集中式的 log 系統
  • Alloy 收集 log 並貼上 label,Loki 負責儲存,只替 label 建索引
  • LogQL 用 {label} 選範圍、|= 過濾內容,count_over_time 能把 log 算成數字
  • 資料來源用 values 設定,Grafana 重啟也不會消失
  • 排查流程:告警通知 → Dashboard 找異常 → Loki 查原因

系列回顧 & 心得

這 30 天從一個 Todo App 開始,部署到 K8s、用 Helm 管理多環境、CI 自動 build、ArgoCD 自動部署,最後用 Prometheus、Grafana、Alertmanager 和 Loki 建立監控。從寫完程式到上線、再到上線後知道它跑得好不好,整條流程都串起來了。

當初開始寫這個系列,是希望透過每天先學一點基礎知識,再搭配實際操作,加深對 K8s 核心概念和周邊工具的印象。為了讓大家每天讀文章不要太有負擔,每篇都盡量寫得精簡,這樣的取捨讓內容沒辦法講得很深入,但我相信這 30 天下來,還是有打下一些基礎!

非常感謝一路看到今天的你!K8s 還有很多主題值得深入學習,系列結束後,也期待大家都能繼續往下探索。之後如果遇到覺得不錯、值得分享的內容,我會再繼續寫下去~


上一篇
Day 29|Alerting
系列文
從零學 K8s|30 天核心概念 × 實作,新手也能真正掌握 Kubernetes 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言