资讯详情

资讯详情

K8S日志系统实战:Fluentd+Elasticsearch+Kibana搭建与避坑指南

1. 为什么我要自己搭一套日志系统容器化环境里最让人头疼的事情之一就是日志。业务跑在几十个 Pod 里每个 Pod 挂了之后日志跟着消失出了问题只能靠kubectl logs一条条翻翻完还得拼时间线。这种日子我过了大半年直到有一次线上故障排查花了整整一个下午我才下决心把日志系统彻底搭起来。这套方案的核心组合是Fluentd Elasticsearch Kibana也就是常说的 EFK 栈。Fluentd 负责从各个节点采集容器日志Elasticsearch 负责存储和索引Kibana 负责查询和可视化。听起来简单但我在实际搭建过程中踩了非常多的坑镜像拉取失败、RBAC 权限不足、索引写不进去、Kibana 连不上 ES、日志字段乱码、时间戳对不上……网上很多文章要么版本太老要么关键步骤一笔带过要么直接复制粘贴根本跑不通。这篇文章我会把整套流程从头到尾讲清楚包括每个组件为什么这样配、参数怎么算、遇到问题怎么排查。适合正在搭建 K8S 日志系统的运维和开发也适合刚接触 EFK 想快速上手的朋友。我不会只给你一堆 YAML 让你复制而是把背后的逻辑讲透这样你遇到变体场景也能自己调整。2. 整体架构设计与选型思路2.1 为什么是 Fluentd 而不是 Filebeat 或 Logstash日志采集器的选择其实挺多的常见的有 Filebeat、Logstash、Fluentd、Fluent Bit。我最终选 Fluentd主要基于几个考虑。Filebeat 确实轻量资源占用小但它的插件生态相对弱一些尤其是做字段解析和路由的时候配置起来没有 Fluentd 灵活。Logstash 功能强大但它是 JVM 系的一个进程动辄几百 MB 内存在每个节点上都跑一个 Logstash 成本太高。Fluent Bit 更轻但它的过滤插件不如 Fluentd 丰富复杂场景下会受限。Fluentd 的优势在于它是 CNCF 毕业项目和 K8S 集成度高插件体系成熟fluent-plugin-elasticsearch这个插件几乎能覆盖所有 ES 写入场景配置用 Ruby 风格的 DSL虽然第一次看有点陌生但写顺了之后非常灵活。资源占用方面一个 Fluentd DaemonSet 的 Pod 大概占 100-200MB 内存可以接受。提示如果你的集群规模特别大比如超过 200 个节点可以考虑 Fluent Bit 做采集、Fluentd 做聚合的两层架构。但中小规模集群直接用 Fluentd DaemonSet 就够了别过度设计。2.2 采集端部署模式DaemonSet 还是 Sidecar这是很多人纠结的点。DaemonSet 是在每个节点上跑一个采集 PodSidecar 是在每个业务 Pod 里塞一个采集容器。DaemonSet 的优点是资源利用率高一个节点一个采集器维护简单。缺点是它只能采集到节点上的容器日志文件也就是/var/log/containers/下面的那些软链接。Sidecar 的优点是能采集到标准输出之外的日志比如应用写到容器内文件里的日志但缺点是每个 Pod 都多一个容器资源开销大而且业务 Pod 重启时 Sidecar 也跟着重启。我的建议是默认用 DaemonSet。因为 K8S 的最佳实践就是应用把日志打到标准输出让容器运行时统一管理。只有那些历史遗留系统、日志必须写文件的场景才考虑 Sidecar。这套方案我采用的是 DaemonSet 模式。2.3 存储层Elasticsearch 集群怎么规划Elasticsearch 的规划直接决定了日志系统的稳定性和查询性能。我见过太多人上来就单节点 ES结果索引一多就 OOM。节点角色上ES 分为 master、data、ingest、coordinating 几种角色。小规模集群日志量每天 10GB 以内可以三个节点混部 master data。中等规模建议 master 和 data 分离至少 3 个 master、2 个以上 data。大规模集群还要考虑 coordinating 节点做查询分发。资源规划上有个经验公式ES 的堆内存设置为物理内存的一半但不超过 31GB。为什么是 31GB因为 JVM 在堆内存超过 32GB 时会关闭指针压缩反而浪费内存。所以哪怕你有 128GB 内存的机器堆也只设 31GB剩下的留给文件系统缓存。存储容量方面日志数据一般保留 7-30 天。按每天 10GB 原始日志算加上 1 个副本30 天就是 600GB。再算上索引开销建议预留 800GB 以上。磁盘用 SSD 最好机械盘在写入量大时容易成为瓶颈。2.4 索引策略按天滚动还是按大小滚动日志索引如果不做生命周期管理ES 迟早会被撑爆。常见的策略有两种按时间滚动每天一个索引和按大小滚动达到一定体积就 rollover。按时间滚动最简单索引名类似k8s-logs-2024.01.15配合 ILMIndex Lifecycle Management可以自动删除过期索引。按大小滚动更灵活适合日志量波动大的场景但配置复杂一些。我采用的是按天滚动 ILM 自动清理。这样索引数量可控查询时也方便按时间范围过滤。ILM 策略里设置 hot 阶段 7 天、delete 阶段 30 天超过 30 天的索引自动删除。3. 核心组件部署与关键配置3.1 Elasticsearch 部署要点ES 的部署方式有几种用 Helm Chart、用 ECK Operator、或者手写 StatefulSet。我推荐用 Helm Chart因为社区维护得好参数化配置清晰。但要注意 Chart 版本和 ES 版本的对应关系版本不匹配会出现各种诡异问题。部署前先创建命名空间和 StorageClass。如果集群没有默认 StorageClass需要手动指定。ES 的 PVC 模板里要写清楚 storageClassName否则 Pod 会一直 Pending。volumeClaimTemplate: accessModes: [ReadWriteOnce] storageClassName: your-storage-class resources: requests: storage: 100GiES 的配置文件里几个关键参数必须调cluster.name集群名称所有节点必须一致node.roles节点角色按规划设置discovery.seed_hosts种子节点列表cluster.initial_master_nodes首次启动时的 master 候选节点xpack.security.enabled是否开启安全认证注意如果开启了 Xpack 安全认证Fluentd 和 Kibana 连接 ES 时都要带用户名密码。生产环境强烈建议开启测试环境可以先用false简化调试。ES 启动后用curl检查集群健康状态curl -X GET localhost:9200/_cluster/health?pretty返回的status字段如果是green就没问题yellow说明副本没分配完单节点集群正常red说明有主分片未分配需要排查。3.2 Fluentd DaemonSet 配置详解Fluentd 的配置分两部分ConfigMap 里的fluent.conf和 DaemonSet 的资源定义。fluent.conf的核心结构是 source、filter、match 三段。source 定义日志来源filter 做字段处理match 定义输出目标。source 部分要采集容器日志配置如下source type tail id in_tail_container_logs path /var/log/containers/*.log pos_file /var/log/fluentd-containers.log.pos tag kubernetes.* read_from_head true parse type json time_key time time_format %Y-%m-%dT%H:%M:%S.%N%z /parse /source这里有几个坑。path必须是/var/log/containers/*.log这是 K8S 自动创建的软链接指向实际的容器日志文件。pos_file记录读取位置Fluentd 重启后不会重复采集。read_from_head true表示从文件头开始读第一次部署时能采集到历史日志但文件很大时会拖慢启动可以改成false。filter 部分要解析 K8S 元数据把 Pod 名称、命名空间、容器名等字段提取出来filter kubernetes.** type kubernetes_metadata id filter_kube_metadata kubernetes_url #{ENV[KUBERNETES_SERVICE_HOST]}:#{ENV[KUBERNETES_SERVICE_PORT]} verify_ssl #{ENV[KUBERNETES_VERIFY_SSL]} ca_file #{ENV[KUBERNETES_CA_FILE]} skip_labels false skip_container_metadata false skip_master_url false skip_namespace_metadata false /filterkubernetes_metadata插件会调用 K8S API 获取 Pod 信息所以 Fluentd 的 ServiceAccount 必须有get、list、watchPod 和 Namespace 的权限。RBAC 配置漏了的话日志里会一直报403 Forbidden。match 部分输出到 ESmatch kubernetes.** type elasticsearch id out_es host #{ENV[FLUENT_ELASTICSEARCH_HOST]} port #{ENV[FLUENT_ELASTICSEARCH_PORT]} user #{ENV[FLUENT_ELASTICSEARCH_USER]} password #{ENV[FLUENT_ELASTICSEARCH_PASSWORD]} index_name fluentd-k8s type_name _doc include_timestamp true logstash_format true logstash_prefix k8s-logs logstash_dateformat %Y.%m.%d buffer type file path /var/log/fluentd-buffers/kubernetes.system.buffer flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever true retry_max_interval 30 chunk_limit_size 2M queue_limit_length 8 overflow_action block /buffer /matchlogstash_format true配合logstash_prefix和logstash_dateformat会自动生成按天滚动的索引名比如k8s-logs-2024.01.15。buffer部分的配置很关键flush_interval 5s表示每 5 秒刷一次retry_forever true表示 ES 挂了也不丢数据overflow_action block表示缓冲区满了就阻塞采集防止数据丢失。提示chunk_limit_size和queue_limit_length决定了缓冲区大小。2M × 8 16M 的缓冲一般够用。如果日志量特别大可以适当调大但要注意节点磁盘空间。3.3 Kibana 部署与连接配置Kibana 的部署相对简单用 Helm Chart 或者 Deployment 都行。关键是配置好和 ES 的连接。环境变量里要设置env: - name: ELASTICSEARCH_HOSTS value: http://elasticsearch:9200 - name: ELASTICSEARCH_USERNAME value: elastic - name: ELASTICSEARCH_PASSWORD value: your-password如果 ES 开了 HTTPSELASTICSEARCH_HOSTS要写成https://并且配置ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES指向 CA 证书。Kibana 启动后访问 5601 端口第一次进去要创建 Index Pattern。索引名填k8s-logs-*时间字段选timestamp。创建完成后在 Discover 页面就能看到日志了。如果 Kibana 报Unable to connect to Elasticsearch先检查网络连通性再检查认证信息。常见问题是 Kibana 的 Service 和 ES 的 Service 不在同一个命名空间DNS 解析不到。4. 实操过程中的坑与排查记录4.1 镜像拉取失败与版本兼容问题第一个坑就是镜像拉取。国内环境拉取elastic.co的镜像经常超时需要配置镜像加速或者用私有仓库。另外Fluentd 的镜像版本要和 ES 版本匹配比如 ES 7.x 配fluentd-elasticsearch:7.xES 8.x 配fluentd-elasticsearch:8.x。版本不匹配会出现index_not_found_exception或者字段类型冲突。我遇到过 Fluentd 用 6.x 镜像往 ES 7.x 写数据结果timestamp字段类型对不上索引创建失败。后来统一版本就好了。4.2 RBAC 权限不足导致元数据采集失败Fluentd 的kubernetes_metadata插件需要访问 K8S API。如果 ServiceAccount 权限不够日志里会刷403 Forbidden而且采集到的日志缺少 Pod 名称、命名空间等关键字段查询时根本没法定位。正确的 ClusterRole 配置apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd rules: - apiGroups: [] resources: [pods, namespaces] verbs: [get, list, watch]然后通过 ClusterRoleBinding 绑定到 Fluentd 的 ServiceAccount。配置完用kubectl auth can-i list pods --assystem:serviceaccount:logging:fluentd验证。4.3 索引写入失败与字段映射冲突ES 默认会动态映射字段类型。如果同名字段在不同日志里类型不一致比如一个日志里status是数字另一个是字符串ES 会报mapper_parsing_exception导致整条日志写入失败。解决办法有两种一是用 Index Template 预先定义字段映射把容易冲突的字段显式声明类型二是在 Fluentd 的 filter 里做类型转换统一字段格式。我推荐用 Index Template在 ES 里提前创建curl -X PUT localhost:9200/_template/k8s-logs -H Content-Type: application/json -d { index_patterns: [k8s-logs-*], settings: { number_of_shards: 1, number_of_replicas: 1 }, mappings: { properties: { timestamp: {type: date}, kubernetes: { properties: { namespace_name: {type: keyword}, pod_name: {type: keyword}, container_name: {type: keyword} } }, message: {type: text} } } }4.4 时间戳对不上与日志乱序日志时间戳对不上是高频问题。K8S 容器日志的格式是时间戳 流 消息Fluentd 解析时如果time_format写错timestamp就会变成采集时间而不是日志产生时间。正确的time_format是%Y-%m-%dT%H:%M:%S.%N%z对应2024-01-15T10:30:45.123456789Z这种格式。如果日志是其他格式需要调整。另外Fluentd 的 buffer 是异步刷写的日志到达 ES 的顺序可能和产生顺序不一致。Kibana 里按timestamp排序就能还原真实顺序不用太担心。4.5 常见问题速查表问题现象可能原因排查方法解决方案Fluentd Pod 一直 CrashLoopBackOff配置文件语法错误kubectl logs看报错检查 fluent.conf 缩进和标签闭合日志里没有 Pod 名称RBAC 权限不足看 Fluentd 日志有无 403补全 ClusterRole 权限ES 索引创建失败字段类型冲突看 ES 日志 mapper_parsing_exception用 Index Template 预定义映射Kibana 连不上 ES网络或认证问题curl测试 ES 连通性检查 Service DNS 和密码日志延迟很大buffer 配置不合理看 Fluentd 监控指标调小 flush_interval增大 buffer磁盘写满索引未清理看 ES 磁盘使用率配置 ILM 自动删除过期索引5. 性能调优与日常维护经验5.1 Fluentd 资源限制与调优Fluentd 默认资源限制往往不够。日志量大时CPU 和内存都会飙升。我的经验是每个节点给 Fluentd 分配requests: cpu 100m, memory 200Milimits: cpu 1000m, memory 500Mi。如果节点日志量特别大可以适当调高。buffer 的flush_thread_count可以调到 4 或 8提高并发写入能力。但要注意线程太多会增加 ES 的写入压力需要平衡。5.2 ES 索引生命周期管理ILM 是 ES 自带的索引管理功能可以自动做 rollover、shrink、delete。配置一个策略{ policy: { phases: { hot: { actions: { rollover: { max_size: 10gb, max_age: 1d } } }, delete: { min_age: 30d, actions: { delete: {} } } } } }然后在 Index Template 里引用这个策略。这样索引超过 10GB 或 1 天就滚动30 天后自动删除。5.3 监控告警配置日志系统本身也需要监控。关键指标包括Fluentd 的 buffer 队列长度、ES 的集群健康状态、磁盘使用率、Kibana 的响应时间。可以用 Prometheus 采集这些指标配置告警规则。比如 ES 磁盘使用率超过 80% 就告警Fluentd buffer 队列持续增长也告警。5.4 日常维护 checklist每天检查 ES 集群健康状态确保是 green 或 yellow每周检查磁盘使用率提前扩容或清理每月 review 一次 ILM 策略根据日志量调整保留时间定期备份 Kibana 的 Dashboard 和 Saved Search 配置关注 Fluentd 和 ES 的版本更新及时修复安全漏洞6. 几个容易被忽略的细节6.1 多行日志的处理Java 应用的异常堆栈是多行的默认按行采集会把一个异常拆成多条日志。需要在 Fluentd 里配置 multiline 解析parse type multiline format_firstline /^\d{4}-\d{2}-\d{2}/ format1 /^(?time\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?level\w) (?message.*)/ /parseformat_firstline定义新日志的起始模式匹配不到的行会拼接到上一条。这样异常堆栈就能完整保留。6.2 敏感信息过滤日志里可能包含密码、Token 等敏感信息。可以在 Fluentd 的 filter 里做脱敏filter kubernetes.** type record_transformer enable_ruby true record message ${record[message].gsub(/password\w/, password***)} /record /filter这样写入 ES 之前就把敏感字段替换掉了。6.3 跨命名空间日志隔离如果多个团队共用一套日志系统需要做隔离。可以在 Fluentd 的 match 里根据 namespace 路由到不同的索引match kubernetes.** type elasticsearch index_name ${record[kubernetes][namespace_name]}-logs ... /match这样每个命名空间的日志写到独立索引查询和权限控制都方便。7. 写在最后的一些个人体会这套 EFK 方案我从零搭到稳定运行前后花了大概三天时间其中大部分时间是在排查各种配置问题。回头看最关键的几个点其实是RBAC 权限要配全、Index Template 要提前建、buffer 配置要合理、ILM 策略要设置。网上很多文章只给 YAML 不给解释遇到变体场景就抓瞎。我的建议是先把每个组件的作用和配置逻辑搞清楚再动手部署。遇到报错不要慌先看日志Fluentd 和 ES 的日志信息都很详细大部分问题都能从日志里找到线索。另外测试环境先跑通再上生产。生产环境的日志量、网络环境、权限体系都和测试环境不一样直接上生产容易出问题。可以先在一个节点上部署 Fluentd观察一天日志采集情况确认没问题再扩展到全集群。如果你也在搭日志系统希望这篇内容能帮你少走一些弯路。有问题欢迎交流我踩过的坑基本都写在这里了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →