在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 Easysearch

本文基于 k3s + Easysearch 2.0.3 实测验证,从零开始搭建一套完整的日志收集方案。

什么是 Fluent Bit

Fluent Bit 是一个轻量级的日志收集和转发工具,用 C 语言写的,内存占用极低(通常只需要几十 MB)。它的工作很简单:从某个地方读日志(INPUT),可选地处理一下(FILTER),然后发到某个地方(OUTPUT)。

1
2
INPUT → FILTER → OUTPUT
读日志 处理 发送

常见用法:

  • 从文件读日志(tail 插件,类似 tail -f
  • 从容器 stdout 读日志
  • 发送到 Elasticsearch / Easysearch / Kafka / S3 等

和 Fluentd 的区别:Fluent Bit 更轻量(C 语言 vs Ruby),适合作为 Agent 部署在每个节点或 Pod 里。Fluentd 功能更丰富,适合做日志聚合层。在 Kubernetes 场景下,Fluent Bit 是更常见的选择。

什么是 Easysearch

INFINI Easysearch 是兼容 Elasticsearch API 的搜索引擎。Fluent Bit 的 es(Elasticsearch)输出插件可以直接对接,不需要改配置。简单理解:Easysearch 是 Elasticsearch 的国产替代品。

为什么用 Sidecar 模式

本文用 Sidecar 模式部署 Fluent Bit:把它和 Nginx 放在同一个 Pod 里,共享日志目录。

另一种常见方式是 DaemonSet 模式:在每个节点上跑一个 Fluent Bit,收集该节点上所有 Pod 的 stdout 日志。DaemonSet 适合收集所有 Pod 的日志,Sidecar 适合收集特定应用的日志文件。

1
2
3
4
5
Nginx Pod
├── nginx 容器 → 产生访问日志 → /var/log/nginx/access.log
└── fluent-bit 容器 → tail 日志文件 → 写入 Easysearch

curl 查询 nginx-logs-* 索引

环境准备

  • k3s 单节点(Ubuntu 24.04,30G 内存)
  • Helm 3
  • cert-manager(Easysearch 依赖)

安装 k3s

1
2
3
4
5
6
7
8
curl -sfL https://get.k3s.io | sh -

# 配置 kubectl
mkdir -p ~/.kube
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
kubectl get nodes

安装 Helm

1
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

设置内核参数

Easysearch(基于 Lucene)需要较高的 mmap 限制:

1
2
sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

第一步:部署 Easysearch

1.1 准备工具

更新helm仓库并且初始化cert-manager。

1
2
3
4
5
6
7
helm repo add infinilabs https://helm.infinilabs.com
helm repo update
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml


# 等所有 Pod Ready(约 30-60 秒)
kubectl get pods -n cert-manager -w

1.2 创建命名空间和证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kubectl create namespace es

# 创建自签名 CA
cat << EOF | kubectl -n es apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: easysearch-ca-issuer
spec:
selfSigned: {}

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: easysearch-ca-certificate
spec:
commonName: easysearch-ca-certificate
duration: 87600h0m0s
isCA: true
issuerRef:
kind: Issuer
name: easysearch-ca-issuer
privateKey:
algorithm: ECDSA
size: 256
renewBefore: 2160h0m0s
secretName: easysearch-ca-secret
EOF

1.3 创建密码 Secret

⚠️ 密码必须包含至少 2 类字符(大写/小写/数字/特殊字符),否则初始化会失败,Pod 直接 Exit Code 1 崩溃。

1
2
kubectl create secret generic easysearch-secrets \
-n es --from-literal=ezs_password='Admin123'

1.4 Helm 安装 Easysearch

使用厂家提供了helm Chart安装Easysearch,并且通过变量设置image的版本,截止目前,最新版本是2.0.3-2534。

1
2
3
4
5
6
helm repo add infinilabs https://helm.infinilabs.com --force-update
helm repo update


helm install easysearch infinilabs/easysearch -n es \
--set image.tag=2.0.3-2534

然后就是等 Pod Running:

1
2
3
kubectl get pods -n es -w
# NAME READY STATUS RESTARTS AGE
# easysearch-0 1/1 Running 0 60s

1.5 验证 Easysearch

1
2
3
kubectl port-forward -n es pod/easysearch-0 9200:9200 &
sleep 3
curl -s http://localhost:9200 -u admin:Admin123

返回 JSON 集群信息即成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "easysearch-0",
"cluster_name" : "infinilabs",
"cluster_uuid" : "_eqbd5PbQ5WNpNVJFccNDA",
"version" : {
"distribution" : "easysearch",
"number" : "2.0.3",
"distributor" : "INFINI Labs",
"build_hash" : "e6180819aedb3d4759cdbcd2c1b856a9635d4aff",
"build_date" : "2026-01-16T09:44:14.477332385Z",
"build_snapshot" : false,
"lucene_version" : "9.12.2",
"minimum_wire_lucene_version" : "8.7.0",
"minimum_lucene_index_compatibility_version" : "8.7.0"
},
"tagline" : "You Know, For Easy Search!"
}

如果你的机器内存比较小(< 4G)或者使用其他 storageClassName 需要修改配置:

1
2
3
4
5
helm install easysearch infinilabs/easysearch -n es \
--set storageClassName=gp2 \
--set resources.requests.memory=512Mi \
--set resources.limits.memory=1536Mi \
--set "javaOpts=-Xms256m -Xmx256m"

第二步:部署 Nginx + Fluent Bit Sidecar

这个 YAML 包含三个 Kubernetes 资源(用 --- 分隔):

  1. ConfigMap:存放 Fluent Bit 的配置文件 fluent-bit.conf
  2. Deployment:定义一个 Pod,里面跑两个容器(nginx + fluent-bit sidecar)
  3. Service:让集群内其他 Pod 可以通过 http://nginx:80 访问 Nginx

创建 nginx-fluentbit.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
cat << 'EOF' > nginx-fluentbit.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: default
data:
fluent-bit.conf: |
[SERVICE]
Flush 3
Log_Level info
Daemon off
[INPUT]
Name tail
Tag nginx.access
Path /var/log/nginx/access.log
DB /var/log/flb_nginx.db
Mem_Buf_Limit 5MB
Refresh_Interval 5
[OUTPUT]
Name es
Match *
Host easysearch.es.svc.cluster.local
Port 9200
HTTP_User admin
HTTP_Passwd Admin123
tls Off
Logstash_Format On
Logstash_Prefix nginx-logs
Retry_Limit False
Suppress_Type_Name On
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: nginx-logs
mountPath: /var/log/nginx
- name: fluent-bit
image: fluent/fluent-bit:2.2
volumeMounts:
- name: nginx-logs
mountPath: /var/log/nginx
readOnly: true
- name: config
mountPath: /fluent-bit/etc/
volumes:
- name: nginx-logs
emptyDir: {}
- name: config
configMap:
name: fluent-bit-config
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
EOF

部署和删除

1
2
3
4
5
# 部署
kubectl apply -f nginx-fluentbit.yaml

# 删除
kubectl delete -f nginx-fluentbit.yaml

确认 Pod 里两个容器都 Running(READY 2/2):

1
2
3
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-699c4dd7ff-ntzfg 2/2 Running 0 30s

YAML 逐字段解释

ConfigMap 部分(Fluent Bit 配置):

字段 说明
[SERVICE] Flush 3 每 3 秒把缓冲区的日志刷到 OUTPUT
Log_Level info Fluent Bit 自身的日志级别
Daemon off 前台运行,容器里必须前台否则容器会退出
[INPUT] Name tail 类似 tail -f,实时跟踪文件新增内容
Tag nginx.access 给输入打标签,OUTPUT 用 Match 匹配
Path /var/log/nginx/access.log 要读的日志文件路径
DB /var/log/flb_nginx.db SQLite 文件,记录读到哪一行了,重启不重复读
Mem_Buf_Limit 5MB 内存缓冲区上限,防止日志太多撑爆内存
Refresh_Interval 5 每 5 秒检查文件是否有新内容
[OUTPUT] Name es Elasticsearch 兼容输出插件,Easysearch 直接可用
Match * 匹配所有 Tag 的日志
Host easysearch.es.svc.cluster.local Easysearch 的 Service DNS(集群内访问)
HTTP_User/Passwd admin/Admin123 Easysearch 认证信息
tls Off 不用 HTTPS(本版本默认 HTTP)
Logstash_Format On 按日期创建索引,格式:nginx-logs-2026.03.11
Logstash_Prefix nginx-logs 索引名前缀
Retry_Limit False 发送失败无限重试
Suppress_Type_Name On 兼容 ES 7.x+,不发送 _type

⚠️ Fluent Bit 的 ini 格式不支持行内注释!Flush 3 # 注释 会把 3 # 注释 整个当成值,导致解析失败。

Deployment 部分(两个容器 + 共享卷):

字段 说明
containers[0]: nginx Nginx 容器,处理 HTTP 请求,日志写到 /var/log/nginx/access.log
containers[1]: fluent-bit Sidecar 容器,读取 nginx 的日志文件发送到 Easysearch
volumeMounts: nginx-logs 两个容器都挂载这个卷,共享 /var/log/nginx 目录
readOnly: true Fluent Bit 只读挂载,最小权限原则
volumeMounts: config 把 ConfigMap 挂载为 Fluent Bit 的配置文件目录
volumes: nginx-logs (emptyDir) 临时卷,Pod 内容器共享,Pod 删除后数据丢失
volumes: config (configMap) 引用上面的 ConfigMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
                    同一个 Pod
┌─────────────────────────────────────────────────┐
│ │
│ nginx 容器 fluent-bit 容器 │
│ ┌──────────┐ ┌──────────────┐ │
│ │ 处理请求 │ 写入 │ tail 插件 │ │
│ │ │ ────────→ │ 读取日志文件 │ │
│ │ access │ emptyDir │ │ │
│ │ .log │ 共享卷 │ es 插件 │ │
│ └──────────┘ │ 发送到 │ │
│ │ Easysearch │ │
│ └──────┬───────┘ │
│ │ │
└───────────────────────────────────┼─────────────┘


┌──────────────────┐
│ Easysearch │
│ nginx-logs-* │
│ 索引 │
└──────────────────┘

关键概念

为什么用 emptyDir

emptyDir 是一个临时卷,在 Pod 创建时自动创建,Pod 删除时自动清理。它的作用是让同一个 Pod 里的多个容器共享文件。nginx 写日志到这个目录,fluent-bit 从这个目录读日志。

为什么 fluent-bit 挂载时加 readOnly: true

Fluent Bit 只需要读日志文件,不需要写。加 readOnly 是最小权限原则,防止 Fluent Bit 意外修改日志文件。

Logstash_Format On 是什么意思?

开启后 Fluent Bit 会按日期自动创建索引,格式为 {Logstash_Prefix}-{日期},比如 nginx-logs-2026.03.11。这样每天的日志在不同索引里,方便按时间范围查询和清理旧数据。

DB /var/log/flb_nginx.db 是什么?

Fluent Bit 用这个 SQLite 数据库文件记录”读到文件的哪一行了”。如果 Fluent Bit 重启,它会从上次的位置继续读,不会重复发送已经处理过的日志。

第三步:产生日志并查询

3.1 产生访问日志

1
2
kubectl run curl-test --rm -it --restart=Never --image=curlimages/curl -- \
sh -c 'for i in $(seq 1 20); do curl -s http://nginx > /dev/null; sleep 0.5; done'

3.2 查询 Easysearch

1
2
3
4
5
kubectl port-forward -n es pod/easysearch-0 9200:9200 &
sleep 2

curl -s 'http://localhost:9200/nginx-logs-*/_search?size=3' \
-u admin:Admin123 | python3 -m json.tool

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"hits": {
"total": { "value": 31 },
"hits": [
{
"_index": "nginx-logs-2026.03.11",
"_source": {
"@timestamp": "2026-03-11T07:16:55.881Z",
"log": "10.42.0.22 - - [11/Mar/2026:07:16:55 +0000] \"GET / HTTP/1.1\" 200 896 \"-\" \"curl/8.18.0\" \"-\""
}
}
]
}
}

3.3 从本地电脑访问(SSH 隧道)

Easysearch 自带了一个UI,但是跑在远程POD里,部署的Service也是Headless,所以没办法直接访问,我们的办法是先用kubectl port-forward把pod端口映射到服务器宿主机,然后再用 SSH 隧道把 9200 端口映射到本地:

1
2
3
4
5
6
7
8
# 远程服务器上先跑 port-forward
kubectl port-forward -n es pod/easysearch-0 9200:9200 &

# 本地电脑 SSH 隧道
ssh -L 9200:localhost:9200 ubuntu@<server-ip> -i key.pem

# 然后本地直接访问
curl -s http://localhost:9200/nginx-logs-*/_search?size=3 -u admin:Admin123

这样转发之后,我们也可以使用本机浏览器直接访问Easysearch自带的UI了。

踩坑记录

1. 密码复杂度(最常见的坑)

Easysearch 最新版本要求密码至少包含 2 类字符,不满足时 Pod 直接崩溃(Exit Code 1),日志来不及写,非常难排查。

1
2
3
4
5
# ❌ 纯小写
ezs_password='easysearchpaswd'

# ✅ 大写 + 小写 + 数字
ezs_password='Admin123'

2. vm.max_map_count

不设置的话 Easysearch 启动失败:

1
sudo sysctl -w vm.max_map_count=262144

3. 小内存环境 OOMKilled

默认 JVM 堆 1G + 堆外内存,总共需要 2G+。小集群用:

1
2
3
--set "javaOpts=-Xms256m -Xmx256m"
--set resources.requests.memory=512Mi
--set resources.limits.memory=1536Mi

4. StorageClass 不匹配(AWS 环境)

我的自建集群没有默认 StorageClass 是 gp2,需要指定:

1
--set storageClassName=gp2

如果环境自带 local-path-provisioner,不需要指定。

5. Fluent Bit 配置不支持行内注释

Fluent Bit 的 ini 格式不支持行内注释,# 后面的内容会被当成值的一部分:

1
2
3
4
5
6
# ❌ 错误:行内注释会被当成值
Logstash_Format On # 按日期创建索引

# ✅ 正确:注释单独一行
# 按日期创建索引
Logstash_Format On

排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Pod 状态
kubectl get pods -n es -w
kubectl describe pod easysearch-0 -n es

# 容器日志
kubectl logs easysearch-0 -n es
kubectl logs easysearch-0 -n es --previous
kubectl logs easysearch-0 -n es -c init-config

# Fluent Bit sidecar 日志
kubectl logs <nginx-pod-name> -c fluent-bit

# 退出原因
kubectl describe pod easysearch-0 -n es | grep -A5 "Last State"
# Exit Code 1 = 应用错误(密码/配置)
# Exit Code 137 = OOMKilled(内存不足)

# 资源使用
kubectl top nodes
kubectl top pods -n es
free -h

# 确认 TLS 模式
kubectl logs easysearch-0 -n es | grep "TLS HTTP Provider"
# null = HTTP,JDK = HTTPS

# 确认密码
kubectl get secret easysearch-secrets -n es \
-o jsonpath='{.data.ezs_password}' | base64 -d

# 确认镜像版本
kubectl get pod easysearch-0 -n es -o jsonpath='{.spec.containers[0].image}'

清理

1
2
3
4
5
6
7
8
# 删除 Nginx + Fluent Bit
kubectl delete -f nginx-fluentbit.yaml

# 删除 Easysearch
helm uninstall easysearch -n es
kubectl delete pvc --all -n es
kubectl delete secret easysearch-secrets -n es
kubectl delete namespace es

在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 Easysearch

https://cloudsmithy.github.io/posts/8fee9a49/

作者

Xu

发布于

2026-03-11

更新于

2026-03-11

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×