从零搭建一个单节点 K8S 可观测实验室(五):安装 Loki + Fluent Bit,构建日志采集链路
在之前的文章中,我们已经完成了 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
这条命令会:
-
创建临时 curl Pod
-
通过 Service 名称
nginx访问 Nginx -
输出网页内容
-
命令结束后自动删除 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-service 和 crash-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 实验室逐渐接近真实生产环境。
更多推荐




所有评论(0)