在之前的文章中,我们已经完成了 Kubernetes 指标监控系统的搭建:

  • Prometheus 采集 Kubernetes Metrics

  • Grafana 展示集群运行状态

通过 Metrics,我们可以发现系统是否出现异常,例如:

  • Node CPU 是否过高

  • Pod 是否频繁重启

  • Memory 是否不足

  • 某个服务的错误率是否突然升高

但是,Metrics 通常只能告诉我们:

系统出现了什么现象。

它不一定能告诉我们:

为什么会出现这个问题。

例如,某个服务突然返回大量 HTTP 500。

Prometheus 可以发现:

HTTP Error Rate ↑

但是它无法直接告诉我们:

  • 数据库连接失败?

  • 配置文件错误?

  • 权限不足?

  • 还是程序内部异常?

这些答案通常隐藏在应用日志中。

因此,一个相对完整的 Kubernetes 可观测体系通常包含三个部分:

Metrics    指标    Prometheus
Logs       日志    Loki
Tracing    链路    Tempo

上一篇,我们通过 Prometheus 和 Grafana 回答了:

集群现在是否健康?

这一篇,我们继续引入 Loki 和 Fluent Bit,尝试回答:

集群为什么出现问题?

本篇将在单节点 Kubernetes 实验环境中部署:

  • Loki:集中存储和查询日志

  • Fluent Bit:采集 Kubernetes 容器日志

  • Grafana:查询和展示日志

最终形成下面这条日志链路:

Application Pod
      │
      │ stdout / stderr
      ▼
Node 容器日志文件
      │
      ▼
Fluent Bit DaemonSet
      │
      ▼
Loki Gateway
      │
      ▼
Loki SingleBinary
      │
      ▼
Grafana Explore

一、为什么 Kubernetes 需要日志系统?

很多 Kubernetes 初学者最开始查看日志的方法是:

kubectl logs <pod-name>

例如:

kubectl logs nginx-xxx

在学习阶段,这种方式完全没有问题。

但是进入稍微复杂一些的环境后,就会遇到几个问题。

1. Pod 数量越来越多

例如一个系统中可能有:

frontend    10 个 Pod
backend     20 个 Pod
database     3 个 Pod

当业务出现问题时:

到底应该查看哪个 Pod?

如果需要逐个执行 kubectl logs,排查效率会非常低。

2. Pod 生命周期比较短

Kubernetes 中的 Pod 并不是永久存在的。

例如:

Pod A
  │
发生故障
  │
  ▼
Pod 被删除
  │
  ▼
创建 Pod B

如果日志只保存在原来的 Pod 或 Node 上,那么 Pod A 被删除后,历史日志也可能随之消失。

3. 多节点环境更加复杂

在生产 Kubernetes 集群中,Pod 可能分布在多个 Node 上:

Node 1
  └── Pod

Node 2
  └── Pod

Node 3
  └── Pod

如果每次排查问题都需要先确定 Pod 所在节点,再登录节点查找日志,显然不可持续。

因此,Kubernetes 通常需要一套集中日志系统:

日志采集
   +
集中存储
   +
统一查询

本实验室采用:

Fluent Bit + Loki + Grafana

二、Loki + Fluent Bit 架构

整体日志链路如下:

Kubernetes Cluster

Application Pod
      │
      │ stdout / stderr
      ▼
/var/log/containers/*.log
      │
      ▼
Fluent Bit
      │
      ▼
Loki Gateway
      │
      ▼
Loki
      │
      ▼
Grafana

Fluent Bit

Fluent Bit 是一个轻量级日志采集器。

在 Kubernetes 中,它通常以 DaemonSet 形式运行。

DaemonSet 可以保证每个 Node 上都运行一个 Fluent Bit Pod,从而读取本节点上的容器日志。

Fluent Bit 主要负责:

  • 读取节点上的容器日志文件

  • 识别对应的 Pod 和 Container

  • 添加 Namespace、Pod 等 Kubernetes Metadata

  • 将日志转发到 Loki

应用写入 stdout 和 stderr 的内容,会由容器运行时保存在 Node 上。

Kubernetes 节点通常通过下面的目录提供统一的容器日志入口:

/var/log/containers/*.log

Fluent Bit 会从这个目录持续读取新增日志。

例如,应用输出:

ERROR database connection timeout

经过 Fluent Bit 的 Kubernetes Filter 处理后,日志会附带类似信息:

namespace=testing
pod=api-service
container=api-service
message=ERROR database connection timeout

后续在 Grafana 中就可以根据 Namespace、Pod 等条件查询日志。

Loki

Loki 是 Grafana 生态中的日志系统。

与 Elasticsearch 等系统不同,Loki 通常不会为所有日志正文建立全文索引。

它主要索引 Label,例如:

namespace="testing"
pod="api-service"

真正的日志内容:

ERROR database connection timeout

则在查询时读取。

这种设计非常适合 Kubernetes 场景:

  • 资源占用相对较低

  • 与 Grafana 集成方便

  • 适合使用 Namespace、Pod、Container 等 Label 查询

  • 对单节点实验环境比较友好


三、创建 logging Namespace

首先创建专门用于日志系统的 Namespace:

kubectl create namespace logging

检查:

kubectl get namespace

应该可以看到:

logging

后续 Loki 和 Fluent Bit 都安装在这个 Namespace 中。


四、准备 Loki values.yaml

Loki 官方 Helm Chart 默认面向相对完整的生产部署场景。

其中可能包含:

  • Distributor

  • Ingester

  • Querier

  • Backend

  • Cache

  • Gateway

对于单节点实验环境来说,没有必要部署完整的分布式架构。

我们的目标是:

单节点
+
低资源占用
+
便于学习

因此采用:

  • SingleBinary 模式

  • 单副本

  • 本地文件系统

  • 不创建 PVC

  • 关闭缓存组件

首先添加 Grafana Helm Repository:

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

查看 Loki Chart:

helm search repo grafana/loki

创建配置文件:

vim loki-values.yaml

内容如下:

deploymentMode: SingleBinary

loki:
  auth_enabled: false

  commonConfig:
    replication_factor: 1

  useTestSchema: true

  storage:
    type: filesystem

singleBinary:
  replicas: 1

  # 单节点实验环境不创建 PVC
  persistence:
    enabled: false

  # 为 Loki 提供可写的临时目录
  extraVolumes:
    - name: loki-data
      emptyDir: {}

  extraVolumeMounts:
    - name: loki-data
      mountPath: /var/loki

backend:
  replicas: 0

read:
  replicas: 0

write:
  replicas: 0

# 单节点实验环境关闭缓存组件
chunksCache:
  enabled: false

resultsCache:
  enabled: false

最终结构大致如下:

Fluent Bit
      │
      ▼
Loki Gateway
      │
      ▼
Loki SingleBinary
      │
      ▼
Grafana

关于临时存储

这里配置了:

persistence:
  enabled: false

并使用:

emptyDir: {}

这意味着 Loki 日志数据只适合临时实验。

如果 Loki Pod 被删除或重新创建,已经采集的历史日志可能丢失。

因此,这套配置适用于:

  • 学习 Loki

  • 验证日志采集链路

  • 单节点实验环境

不适合生产环境。

生产环境通常应该使用:

  • PVC

  • 对象存储

  • 或其他持久化存储方案


五、安装 Loki

执行安装:

helm install loki grafana/loki \
  -n logging \
  -f loki-values.yaml

查看 Helm Release:

helm list -n logging

查看 Pod:

kubectl get pods -n logging

正常情况下可以看到类似结果:

loki-0                         2/2     Running
loki-gateway-xxxxxxxxxx-xxxxx  1/1     Running

不同版本的 Loki Helm Chart,Pod 名称和容器数量可能略有差异。

查看 Service:

kubectl get svc -n logging

应该可以看到:

loki-gateway

后续 Fluent Bit 和 Grafana 都通过这个 Service 访问 Loki。

完整的 Kubernetes Service DNS 是:

loki-gateway.logging.svc.cluster.local

六、准备 Fluent Bit values.yaml

添加 Fluent Bit Helm Repository:

helm repo add fluent https://fluent.github.io/helm-charts

helm repo update

查看 Chart:

helm search repo fluent/fluent-bit

创建配置文件:

vim fluent-bit-values.yaml

内容如下:

config:
  service: |
    [SERVICE]
        Flush        1
        Log_Level    info
        HTTP_Server  On
        HTTP_Listen  0.0.0.0
        HTTP_Port    2020

  inputs: |
    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Tag               kube.*

  filters: |
    [FILTER]
        Name                kubernetes
        Match               kube.*
        Merge_Log           On

  outputs: |
    [OUTPUT]
        Name loki
        Match kube.*
        Host loki-gateway.logging.svc.cluster.local
        Port 80
        Labels job=fluent-bit,namespace=$kubernetes['namespace_name'],pod=$kubernetes['pod_name']

下面解释几个关键配置。

1. 读取容器日志

Path /var/log/containers/*.log

应用写入 stdout 和 stderr 的内容,会由容器运行时保存到 Node 上。

Fluent Bit 从下面的统一入口读取日志:

/var/log/containers

2. 添加 Kubernetes Metadata

Name kubernetes

Kubernetes Filter 会根据日志文件和 Kubernetes API,为日志增加相关信息,例如:

  • Namespace

  • Pod

  • Container

  • Kubernetes Labels

后面在 Grafana 中,就可以根据 Namespace 或 Pod 查询日志。

3. 输出到 Loki

Host loki-gateway.logging.svc.cluster.local
Port 80

Fluent Bit 通过 Kubernetes 内部 DNS 找到 Loki Gateway。

4. Loki Labels

Labels job=fluent-bit,namespace=$kubernetes['namespace_name'],pod=$kubernetes['pod_name']

这里将下面的信息作为 Loki Label:

  • job

  • namespace

  • pod

因此,后续可以使用 LogQL 查询:

{namespace="testing"}

或者:

{pod=~"api-service.*"}

本实验已经配置:

auth_enabled: false

因此不需要配置:

X-Scope-OrgID

七、安装 Fluent Bit

执行:

helm install fluent-bit fluent/fluent-bit \
  -n logging \
  -f fluent-bit-values.yaml

检查 Pod:

kubectl get pods -n logging

正常情况下可以看到:

fluent-bit-xxxxx    1/1    Running

由于 Fluent Bit 以 DaemonSet 运行,因此单节点集群中通常只有一个 Fluent Bit Pod。

查看 DaemonSet:

kubectl get daemonset -n logging

查看 Fluent Bit 日志:

kubectl logs -n logging \
  -l app.kubernetes.io/name=fluent-bit \
  --tail=50

如果没有明显的连接错误,说明 Fluent Bit 已经开始读取日志并发送到 Loki。

此时整个日志链路已经搭建完成:

Pod stdout / stderr
        │
        ▼
Node 日志文件
        │
        ▼
Fluent Bit
        │
        ▼
Loki Gateway
        │
        ▼
Loki

接下来需要创建一些测试应用,主动产生业务日志。


八、创建多 Namespace 测试应用

为了模拟真实 Kubernetes 环境,我们创建两个业务 Namespace:

production
testing

其中:

  • production 模拟生产环境

  • testing 模拟测试环境和故障场景

Case 1:生产环境 Nginx

创建 Namespace:

kubectl create namespace production

部署 Nginx:

kubectl create deployment nginx \
  -n production \
  --image=nginx:1.27

创建 Service:

kubectl expose deployment nginx \
  -n production \
  --port=80

检查:

kubectl get pods -n production
kubectl get svc -n production

等待 Nginx Pod 进入:

Running

然后创建一个临时 curl Pod,访问 Nginx:

kubectl run curl \
  -n production \
  --image=curlimages/curl \
  --restart=Never \
  --rm -it \
  -- curl http://nginx

这条命令会:

  1. 创建临时 curl Pod

  2. 通过 Service 名称 nginx 访问 Nginx

  3. 输出网页内容

  4. 命令结束后自动删除 curl Pod

Nginx 会产生类似访问网页日志:

GET / HTTP/1.1
200

这些日志会被 Fluent Bit 自动采集并发送到 Loki。

Case 2:模拟业务服务日志

创建测试 Namespace:

kubectl create namespace testing

创建一个持续输出日志的 BusyBox Pod:

kubectl run api-service \
  -n testing \
  --image=busybox \
  --restart=Never \
  --command -- \
  sh -c '
while true;
do
  echo "$(date -Iseconds) INFO request received";
  sleep 3;
  echo "$(date -Iseconds) ERROR database connection timeout";
  sleep 10;
done'

检查 Pod:

kubectl get pods -n testing

查看日志:

kubectl logs api-service -n testing

可以看到类似输出:

2026-07-30T10:00:00+00:00 INFO request received
2026-07-30T10:00:03+00:00 ERROR database connection timeout

这个 Pod 会持续产生:

  • INFO 日志

  • ERROR 日志

方便后续在 Grafana 中进行过滤查询。

Case 3:模拟应用启动失败

创建一个启动后立即退出的 Pod:

kubectl run crash-app \
  -n testing \
  --image=busybox \
  --restart=Never \
  --command -- \
  sh -c '
echo "Starting application";
echo "ERROR configuration file missing";
exit 1'

查看 Pod:

kubectl get pods -n testing

结果类似:

crash-app    0/1    Error

这里需要注意:

--restart=Never

表示容器退出后不会自动重新启动。

因此 Pod 状态通常是:

Error

而不是:

CrashLoopBackOff

查看日志:

kubectl logs crash-app -n testing

输出:

Starting application
ERROR configuration file missing

虽然 Pod 已经退出,但是它退出前写出的日志仍然会被 Fluent Bit 采集到 Loki。


九、在 Grafana 中配置 Loki

进入 Grafana。

依次打开:

Connections
    ↓
Add new connection
    ↓
Loki
    ↓
Add new data source

填写 URL:

http://loki-gateway.logging.svc.cluster.local

本实验中的 Grafana 本身也运行在 Kubernetes 集群内部,因此可以访问 Kubernetes Service DNS。

由于本篇 Loki 配置中已经关闭认证:

auth_enabled: false

所以不需要配置:

X-Scope-OrgID

点击:

Save & Test

如果看到类似提示:

Data source successfully connected

说明 Grafana 已经成功连接 Loki。


十、在 Grafana 中查询日志

进入:

Explore

选择数据源:

Loki

1. 查询 production Namespace

输入:

{namespace="production"}

可以看到 Nginx 的访问日志。

例如:

GET / HTTP/1.1

2. 查询 testing Namespace

输入:

{namespace="testing"}

可以看到:

INFO request received
ERROR database connection timeout
Starting application
ERROR configuration file missing

3. 只查询错误日志

输入:

{namespace="testing"} |= "ERROR"

可以直接过滤出:

ERROR database connection timeout
ERROR configuration file missing

4. 查询 api-service 日志

输入:

{pod=~"api-service.*"}

可以看到持续产生的业务日志。

5. 查询 crash-app 日志

输入:

{pod=~"crash-app.*"}

可以看到:

Starting application
ERROR configuration file missing


十一、故障排查 Case

假设业务反馈:

测试环境接口调用失败。

传统排查方式可能是:

kubectl get pods -n testing
kubectl logs api-service -n testing
kubectl logs crash-app -n testing

如果 Pod 数量较多,就需要不断切换 Pod 查看日志。

现在可以直接进入 Grafana:

Explore
    ↓
Loki
    ↓
namespace="testing"
    ↓
过滤 ERROR

查询:

{namespace="testing"} |= "ERROR"

可以快速找到:

ERROR database connection timeout

如果是应用启动失败,则可以查询:

{pod=~"crash-app.*"}

看到:

ERROR configuration file missing

这就是集中日志系统的价值:

不需要逐个进入 Pod
不需要先确定 Pod 位于哪个 Node
可以跨 Pod 查询
可以按 Namespace 过滤
可以统一搜索错误日志

十二、Kubernetes Service DNS 小知识

前面 Grafana 和 Fluent Bit 都使用了这个地址:

loki-gateway.logging.svc.cluster.local

很多刚接触 Kubernetes 的朋友可能会疑惑:

为什么我的电脑解析不了这个地址,但是 Grafana 和 Fluent Bit 可以?

原因是:

Grafana 和 Fluent Bit 本身也是 Kubernetes Pod。

Pod 内部的 DNS 解析由 CoreDNS 提供。

Kubernetes Service 的完整 DNS 格式通常是:

<Service>.<Namespace>.svc.cluster.local

例如:

loki-gateway.logging.svc.cluster.local

表示:

Service:    loki-gateway
Namespace:  logging

这个地址只在 Kubernetes Cluster 内部有效。

因此:

集群外部浏览器

通常无法直接解析:

loki-gateway.logging.svc.cluster.local

但是运行在 Kubernetes 中的 Grafana Pod 可以正常访问。

实际上,因为 Grafana 和 Loki 位于不同 Namespace,所以也可以使用相对简写:

loki-gateway.logging

不过在配置文件中使用完整地址:

loki-gateway.logging.svc.cluster.local

含义更加明确,也更方便初学者理解。


十三、清理测试资源

本篇创建的 Nginx、api-servicecrash-app,只是为了制造测试日志。

确认已经能够在 Grafana 中查询日志后,可以删除这些临时测试资源。

删除两个测试 Namespace:

kubectl delete namespace production
kubectl delete namespace testing

检查:

kubectl get namespace
kubectl get pods -A

这里删除的只是测试业务,不会影响日志系统本身。

以下组件继续保留:

Fluent Bit
Loki
Loki Gateway
Grafana

可以通过下面的命令确认:

kubectl get pods -n logging

之所以删除测试应用,是因为下一篇部署 Tempo 和 OpenTelemetry 时,我们会重新创建一个专门支持链路追踪的 Demo 应用。

Nginx 和 BusyBox 虽然可以制造日志,但并不能直接产生完整的分布式 Trace。

因此,本系列采用:

基础设施组件:继续保留
临时测试业务:验证完成后删除

这样可以避免实验资源不断堆积,也能让每一篇文章的实验边界更加清楚。


十四、小结:Metrics、Logs 与 Tracing

到这里,单节点 Kubernetes 可观测实验室已经完成了两个核心能力。

Metrics

Kubernetes Metrics
        │
        ▼
Prometheus
        │
        ▼
Grafana

Metrics 主要回答:

系统发生了什么?

例如:

  • CPU 是否过高

  • Memory 是否不足

  • Pod 是否频繁重启

  • 服务错误率是否上升

Logs

Pod stdout / stderr
        │
        ▼
Fluent Bit
        │
        ▼
Loki
        │
        ▼
Grafana

Logs 主要回答:

系统为什么出现问题?

例如:

  • 数据库连接失败

  • 配置文件缺失

  • 权限错误

  • 程序异常退出

Tracing

下一篇,我们继续完成第三块能力:

Tracing

最终完成 Kubernetes 可观测三件套:

Metrics
   +
Logs
   +
Tracing

让这个单节点 Kubernetes 实验室逐渐接近真实生产环境。

更多推荐