Fluentd-ElasticSearch配置两三(糗)事

上周末在家闲来无事,于是乎动手帮项目组搭建日志收集的EFK环境,最终目标的部署是这个样子的:

Fluentd-ElasticSearch配置两三(糗)事_第1张图片

在每个应用机器上部一个Fluentd做为代理端,以tail方式读取指定的应用日志文件,然后Forward到做为汇聚端的Fluentd,汇聚端对日志内容加工、分解成结构化内容,再存储到ElasticSearch。日志内容的展现由Kinana实现。

Fluentd是什么? Fluentd是一个完全免费的、完全开源的日志收集器,可以与125多种系统相对接,实现“日志一切”架构。

由于前面已经在Kubernetes容器平台上部署好了ElasticSearch与Kibana,周末只是忙乎Fluentd的安装配置,以前没有使用过,现学再卖,结果折腾过程中出现了几个问题,在这里念叨记录一下。

Output插件的请求超时时间

在我们的部署中,只使用了Fluentd两种Output插件,一个是代理端用于向后传递日志内容的Ouput-Forward,一个是用于向ES中存储数据的Output-ElasticSearch。

  • 代理端Output-Forward插件配置(最初)
  
    @type             forward
    require_ack_response ture
    
      host             10.xxx.xx.xx
      port             24224
    
    <buffer tag>
      @type            file
      path             /xxx/xxx/buffer
      flush_interval   10s
      total_limit_size 1G
    buffer>
  

  • 汇聚端Output-ElasticSearch插件配置(最初)
  <match xxxxx.xxxxx.**>
    @type              elasticsearch
    host               10.xxx.xx.xxx
    port               30172
    logstash_format    true
    logstash_prefix    log.xxxxx.xxxxx.xxxxx-
    
      @type            file
      path             "/xxx/xxx/fluentd/aggregator/xxxx/buffer"
      total_limit_size 20G
      flush_interval   10s
    
  match>

配置完以后,简单测试跑通后,满心欢喜地拿应用某台机器上1-3月产生的日志灌数。从Kibana界面上代表日志数量的绿柱向上增长,几千…几万..百万…两百万,当时还发图片给项目组的显摆了一番。后面想着不可能有这么多日志记录吧!? 登录到应用服务器上去日志目录里wc了一把,所有文件行数总和才150万! 晕死!

跑进Kibana界面细观瞧,才发现入库的日志记录有重复内容,比如:某条记录就重复了233次。 当时也不知道具体原因,猜测着可能是Output-ElasticSearch插件因接收响应超时,叕重发了内容到ElasticSearch,所以试着查到相应参数:request_timeout加入到汇聚端配置中,参数默认值是:5s


  @type              elasticsearch
  ...
  request_timeout    30s
  
    ...
  

发信号HUP给汇聚端Fluentd进程,让它重新载入修改后的配置,再试,没再出现重复日志记录。但,超时时间只是表象,真正的原因是buffer配置操成,请见下一条内容

Request Entity Too Large

在说明这个问题前,先看看Fluentd的buffer机制,buffer做为Output插件的缓冲区,当输出端(比如:ElasticSearch)服务不可用时,Fluentd暂时缓存需要输出的内容到文件或者内存,然后再重试向输出端发送。

What? 那输出端服务总是不恢复,光进不出,企不是要将Fluend缓冲区撑爆后丢失日志数据?

当缓冲区Full后,默认产生BufferOverflowError异常,输入插件自行如何处理此异常。我们使用的in_tail插件会停止读取日志文件内容,in_forward插件会向上一级ouput_forward插件返回错误。除产生异常外,还可通过Output插件的配置参数overflow_action(>=1.0)或者buffer_queue_full_action(<1.0)控制缓冲区满后的行为:

  • throw_exception
  • block
  • drop_oldest

具体说明请见:官网Output Plugin Overview (>=1.0) 或者Buffer Plugin Overview

为什么扯到buffer?因为遇到的这个问题与它有关。在修改完request_timeout参数后,又经常看到错误信息:

2018-03-04 15:10:12 +0800 [warn]: #0 fluent/log.rb:336:warn: Could not push logs to Elasticsearch, resetting connection and trying again. Connection reset by peer (Errno::ECONNRESET)

显示被ElasticSearch端强制断掉连接,什么情况? 在命令行启动fluentd时强制trace日志级别,满屏的输出中找不见错误原因,除了ECCONNRESET。 ElasticSearch端日志也未见异常信息。 只好求助于另一杀器:tcpdump,抓取与ES之间的通讯包:

sudo tcpdump -A -nn -s 0 'tcp  port 9200 ' -i eth1  #9200是ES接受请求的端口

得到,请求包头:

User-Agent: Faraday v0.13.1
Content-Type: application/json
Host: 10.210.39.136:30172
Content-Length: 129371575

响应包头:

HTTP/1.1 413 Request Entity Too Large
content-length: 0

Request Entity Too Large! 查了下ElasticSearch文档,ES默认能接受的数据包大小是100MB,由`http.max_content_length参数控制。而我们上送的数据包长度明显大于这一限制Content-Length: 129371575

是什么造成了这种结果? 从上面关于Fluentd Buffer的示意图中,可以看出Output插件是以Chunk为单位向输出端发送数据包,Chunk的大小由参数chunk_limit_size设置(说明文档here),默认值:内存缓冲8MB/文件缓冲256MB。 在我们初始配置中并没有设定,采用的是默认值,所以出现Request Entity Too Large! 错误也就不稀奇。 分别在代理端与汇聚端的buffer相关配置里增加chunk_limit_size参数设置:


  ...
  chunk_limit_size        10M
  ...

  ...
  chunk_limit_size        15M
  ...

想想前面一节说明到与ES间请求超时,与采用默认chunk大小设置是有关的,文件型缓冲区默认256M的chunk大小,送给ES端就是大小不超过其限制的100MB,也会让ES处理较长的时间,所以会有请求超时出现。

在配置过程中,看错了老版本的文档,使用参数buffer_chunk_limit设置,没起作用,迷惑一小会儿!

另外,是否可以将汇聚端的chunk大小设置得比代理端小? 是可以的,但汇聚端会有警告日志产生。大致看了下代码,说说我自己的理解(不见得对哟):Flunetd后一级收到前一级发送来的chunk数据时,先不做大小上的处理,在向再后一级发送时,根据后一级类型进行chunk的编码,完成后再与设置的chunk大小做比对,如果大于设置再重新分拆成多个后再进行编码发送,期间会产生警告日志 。

增加ElasticSearch日志数据防重配置

为防止再出现日志记录重复的现象,除上述配置上的处理外,最好在发给ElasticSearch前,给每条日志生成一个主键值,这样ES在收到重复记录后,如果发现主键对应的记录已存在则Update,否则才Insert。


  @type            elasticsearch_genid
  hash_id_key      _hash    # storing generated hash id key (default is _hash)



  @type              elasticsearch
  ...
  id_key             _hash # specify same key name which is specified in hash_id_key
  remove_keys        _hash # Elasticsearch doesn't like keys that start with _
  ...

Timestamp是Unix Time格式!

因为上送ElasticSearc的日志记录中,标明日志时间的域值是拼接出来的再转换成日期类型的:

 ${ DateTime.parse(record["systemDate"] + " " + record["systemTime"] + " +0800").to_time.to_i }

注意,一定要在最后使用to_i方法,将其转换成Unix Time格式。否则,会在编码送往ES数据时报错,大到错误信息是“未发现Time类型上的msgPack方法”。

另:Output-ElasticSearch插件的说明文档,官网内容不全面,完全说明在这里。

附上全部配置文件内容:

<system>
  worker            2
  root_dir          /xxxx/xxxxx/fluentd/agent
  log_level         info
system>

<source>
  @type             tail
  tag               xxxxx.xxxxx.agent.log
  path_key          path
  path              /xxxx/xxxxx/agent/logs/xxxxx/*.batch*.log
  pos_file          /xxxx/xxxxx/fluentd/agent/pos/batch_agent.db
  read_from_head    true

  <parse>
    @type             multiline
    format_firstline  /^(\d+-\d+-\d+\s+)?\d+:\d+:\d+\|/
    format1           /^(?<log>(\d+-\d+-\d+\s+)?\d+:\d+:\d+\|.*)$/
  parse>
  #multiline_flush_interval 5s
source>

<filter foo.bar>
  @type             record_transformer
  <record>
    hostname        "#{Socket.gethostname}"
  record>
filter>

<match **>
  @type             forward
  require_ack_response ture
  <server>
    host             xx.xxx.xx.xx
    port             24224
  server>
  <buffer tag>
    @type            file
    path             /xxxx/xxxxx/fluentd/agent/buffer
    flush_interval   10s
    total_limit_size 1G
    chunk_limit_size 10M
  buffer>
match>

  @type               forward
  bind                0.0.0.0
  source_address_key  hostIP



   @type           parser
   reserve_data    true
   key_name        log
   
      @type        regexp
      expression   /(?mx)^(?\d+-\d+-\d+)?\s?(?\d+:\d+:\d+)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)\|(?.*)$/
   



   @type           record_transformer
   enable_ruby     true
   
     systemDate    ${ if !(o=record["systemDate"]).nil? and o.length!=0 then o else if (d=record["path"].scan(/\d{4}-\d{2}-\d{2}/).last).nil? then Time.new.strftime("%Y-%m-%d") else d end  end }
   



   @type          record_transformer
   enable_ruby    true
   
     logtime      ${ DateTime.parse(record["systemDate"] + " " + record["systemTime"] + " +0800").to_time.to_i }
   
   renew_time_key logtime
   remove_keys    logtime,systemDate,systemTime



  @type            elasticsearch_genid
  hash_id_key      _hash    # storing generated hash id key (default is _hash)


<match aplus.batch.**>
  @type              elasticsearch
  host               xx.xxx.xx.xxxx
  port               9200
  id_key             _hash # specify same key name which is specified in hash_id_key
  remove_keys        _hash # Elasticsearch doesn't like keys that start with _
  logstash_format    true
  logstash_prefix    log.xxxx.xxxxx.agent-
  request_timeout    30s
# reload_connections false
  slow_flush_log_threshold 30s
  
    @type            file
    path             "/xxxx/xxxxx/fluentd/aggregator/#{ENV['HOSTNAME']}/buffer"
    total_limit_size 20G
    chunk_limit_size 15M
    flush_interval   10s
    retry_wait       10.0
  
match>

你可能感兴趣的:(DevOps)