本文基于 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 -
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
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
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
|
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 资源(用 --- 分隔):
- ConfigMap:存放 Fluent Bit 的配置文件
fluent-bit.conf
- Deployment:定义一个 Pod,里面跑两个容器(nginx + fluent-bit sidecar)
- 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):
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
| kubectl port-forward -n es pod/easysearch-0 9200:9200 &
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
| 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
kubectl logs <nginx-pod-name> -c fluent-bit
kubectl describe pod easysearch-0 -n es | grep -A5 "Last State"
kubectl top nodes kubectl top pods -n es free -h
kubectl logs easysearch-0 -n es | grep "TLS HTTP Provider"
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
| kubectl delete -f nginx-fluentbit.yaml
helm uninstall easysearch -n es kubectl delete pvc --all -n es kubectl delete secret easysearch-secrets -n es kubectl delete namespace es
|