Filebeat系统资源使用优化

Filebeat在生产部署后,必定会对服务CPU、内存、网络有影响,如果将这些因素都在可控范围内,那是完全可以接受的。但是可能由于我们的配置不合理,或者非预期的情况导致CPU、内存占用过大,势必会影响到同在一起的业务应用稳定性。

问题场景

    将filebeat部署到生产环境,或者某个参数配置错误,都可能会出现意想不到的问题,轻则影响服务的整体性能,重则可能造成应用被OOM-killer导致业务中断。我们在刚开始使用filebeat时,觉得这个组件已经如此成熟,应该问题不大。所以使用了最简单最基本的配置将其部署,为后来的问题埋下了祸根!

我们在实际使用中的确遇到了如下问题:

  • Filebeat内存爆增至数GB、导致业务服务器触发OOM-killer,将业务进程kill掉。
  • 平常不会很频繁,但一旦压测或者有大量异常时就会出现以上问题

来看几个问题样本:
在这里插入图片描述

这里是filebeat导致Linux OOM 的一个场景,可使用 dmesg -T 查看,这里可以看到,在OOM时,filebeat大概占用523561*4k ~= 2G(rss是内存页数,乘上内存页大小4k就是实际物理内存)(多的时候有过5G,比如有的日志文件中打印了很多二进制内容)

初版filebeat.yml配置

这是初版的配置文件,相对简单,基本没有做什么特殊的配置(这是一个错误的样本配置,请不要使用)

filebeat.inputs:
- type: log
  enabled: true
  paths:                                # 日志文件路径
    - /data/logs/*/*.log
  exclude_files: [.*file3.*|.*file4.*]  # 忽略的文件列表,正则匹配
  fields:                               # 在事件json中添加字段
    appName: ${serviceName}
    agentHost: ${hostIp}
  fields_under_root: true               # 将添加的字段加在JSON的最外层
  tail_files: false                     # 不建议一直开启,从日志文件的最后开始读取新内容
  multiline:                            # 多行匹配日志
    pattern: '\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' # 匹配一个以 [YYYY-MM-DD HH:mm:ss 开头的行
    negate: true                        # 将 pattern 取否(即不匹配pattern的情况)
    match: after                        # 将其追加到上一行之后 pattern + negate + match 组合成一条语意为: 如果不匹配 [YYYY-MM-DD HH:mm:ss 开头的行,则将其合并到当前行的上一行
    timeout: 30s                        

output.kafka:
  enabled: true
  hosts: ['ip1:9092','ip2:9092']
  topic: 'my_topic'
  partition.round_robin:
    reachable_only: true
  worker: 4
  required_acks: 1
  compression: gzip
  max_message_bytes: 1000000            # 10MB

初期的问题和调整

    我们接收业务反馈CPU占据到了300%上下(四核),并且偶现开头出现的OOM现象。

    经过文档的查阅,发现几个明显的配置问题,并针对其做过一次初步的优化

  • max_bytes:单行日志的大小,默认为10M
  • queue.mem.events:内存队列的大小默认为4096,有这个一个公式 max_bytes * queue.mem.events等于约占的内存量,这里默认10M * 4096=40G,再考虑到队列存储已经编码为json的数据,则原始数据应该也会存储在内存中,那满打满算就得有80G内存占用,即使不存储原始日志,日常的服务器也难以接受这40G的内
  • max_procs:没有限制CPU内核数使用,在日志较频繁时可能导致CPU满载
  • ignore_older:由于服务器上有历史日志,可以使用此选项忽略较旧的文件
初步调整:
max_procs: 1   # *限制一个CPU核心,避免过多抢占业务资源
filebeat.inputs:
    - type: log
      ignore_older: 48h   # 忽略这个时间之前的文件(根据文件改变时间)
      max_bytes: 20480    # 单条日志的大小限制,将其从默认10M降低到20k,按照公式计算 20k * 4096 ~= 80M

问题反复及之后的处理

    上面的改动,以为已经控制住问题,但是在不久之后还是反复出现了多次同样的OOM问题
    filebeat限制单条消息最大不能超过20k,默认内存队列存储4096条记录,再加上明文日志,最高内存理论最高值在 20k * 4096 * 2 = 160M 左右浮动,即使做了以上限制,还是不时出现内存爆增的情况

一时没有找到问题解决方案,我们做了如下的几个阶段策略:
阶段一:降低非预期情况下对应用的影响
阶段二:重新梳理和优化配置,达到优化内存的目的

阶段一:降低非预期情况下对应用的影响
降低非预期情况下对应用的影响,是将filebeat进程的oom_score_adj值设置为999,在出现意外情况时OOM也优先 kill 掉filebeat,从而达到即使出现意外情况,也不会影响到业务进程
具体的操作是:
启动filebeat -> 获取进程PID -> 写入/proc/$pid/oom_score_adj 为999

OOM killer 机制一般情况下都会kill掉占用内存最大的进程,在kill进程时,有个算分的过程,这个算分过程是一个综合的过程,包括当前进程的占用内存情况,系统打分,还有可由用户控制的oom_score_adj分值,将影响整体算分。


oom_score_adj的取值范围是 -1000 至 1000,值越大,OOM在kill时会优先kill掉此进程,为了最大限度让系统kill掉filebeat,我们必须将此值调整到最大

阶段二:重新梳理和优化配置,达到优化内存的目的
针对多次问题的场景,进行样本的提取,发现多次问题出现的都是在大量的堆栈异常日志中出现,怀疑是多行合逻辑不对导致的问题:
下面我们来着重看一下多行最初配置:

 multiline:                            # 多行匹配日志
    pattern: '\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' # 匹配一个以 [YYYY-MM-DD HH:mm:ss 开头的行
    negate: true                        # 将 pattern 取否(即不匹配pattern的情况)
    match: after                        # 将其追加到上一行之后 pattern + negate + match 组合成一条语意为: 如果不匹配 [YYYY-MM-DD HH:mm:ss 开头的行,则将其合并到当前行的上一行
    timeout: 30s                        #默认值是5s                     

    官网对于此参数的默认值是5s,但是现在却被误配置成了30s,问题的原因应该就在 multiline.timeout这个参数之上。
    如果日志中如果包含很多错误堆栈,或者不规范的日志大量匹配多行逻辑,会产生过多的多行合并任务,但是超时时间过长,过多的任务就会最大限度的匹配和在内存中等待,占用更多的内存!

除此以外也做了其他调整包括但不限于如下:
queue.mem.events: 从默认的4096 设置到了2048
queue.mem.flush.min_events: 从默认2048设置到了1536
multiline.max_lines: 从默认500行设置到了200行

最优配置如下,在调整之后将问题样本进行测试,内存只占用350M~500M之间:

max_procs: 1                            # 限制一个CPU核心,避免过多抢占业务资源
queue.mem.events: 2048                  # 存储于内存队列的事件数,排队发送 (默认4096)
queue.mem.flush.min_events: 1536        # 小于 queue.mem.events ,增加此值可提高吞吐量 (默认值2048)
#queue.mem.flush.timeout: 1s             # 这是一个默认值,到达 min_events 需等待多久刷出
filebeat.inputs:
- type: log
  enabled: true
  ignore_older: 48h                     # 忽略这个时间之前的文件(根据文件改变时间)
  max_bytes: 20480                      # *单条日志的大小限制,建议限制(默认为10M,queue.mem.events * max_bytes 将是占有内存的一部分)
  recursive_glob.enabled: true          # 是否启用glob匹配,可匹配多级路径(最大8级):/A/**/*.log => /A/*.log ~ /A/**/**/**/**/**/**/**/**/*.log  
  paths:                                # 日志文件路径
    - /data/logs/**/*.log
  exclude_files: [.*file1.*|stdout.log|.*file2.*] # 忽略的文件列表,正则匹配
  fields:                               # 在事件json中添加字段
    appName: ${serviceName}
    agentHost: ${hostIp}
  fields_under_root: true               # 将添加的字段加在JSON的最外层
  tail_files: false                     # 不建议一直开启,从日志文件的最后开始读取新内容(保证读取最新文件),但是如果有日志轮转,可能导致文件内容丢失,建议结合 ignore_older 将其设置为false
  multiline:                            # 多行匹配日志 (https://www.elastic.co/guide/en/beats/filebeat/7.2/multiline-examples.html)
    pattern: '\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' # 匹配一个以 [YYYY-MM-DD HH:mm:ss 开头的行
    negate: true                        # 将 pattern 取否(即不匹配pattern的情况)
    match: after                        # 将其追加到上一行之后 pattern + negate + match 组合成一条语意为: 如果不匹配 [YYYY-MM-DD HH:mm:ss 开头的行,则将其合并到当前行的上一行
    max_lines: 200                      # 最多匹配多少行,如果超出最大行数,则丢弃多余的行(默认500)
    timeout: 1s                         # 超时时间后,即使还未匹配到下一个行日志(下一个多行事件),也将此次匹配的事件刷出 (默认5s)


output.kafka:
  enabled: true
  hosts: ['ip1:9092','ip2:9092']
  topic: 'my_topic'
  partition.round_robin:
    reachable_only: true
  worker: 4
  required_acks: 1
  compression: gzip
  max_message_bytes: 1000000            # 10MB

使用cgroup限制资源用量

    对于业务机器来说,让filebeat独占一个CPU去进行日志收集,显然不被业务人员所接受,因为在业务高峰期日志量会很大,filebaat进行大吞吐量的日志收集、多行合并、消息发送;很有可能会限制业务的性能,可能没有filebeat我原本需要10台主机,但是有了filebeat我就需要15台主机来承载高峰业务。
    上面的配置虽然已经基本控制住内存用量,但也有可能出现不同的不可预期的情况导致内存增长

如何限制CPU使用量和应对意想不到的内存增长情况?
绝对性的控制CPU/内存在一个范围内,我们可以使用cgroup来实现

1、什么是cgroup

cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 cpu,内存等资源实现精细化的控制,容器 Docker 技术就使用了 cgroups 提供的资源限制能力来完成cpu,内存等部分的资源控制。


另外,开发者也可以使用cgroup提供的精细化的控制能力,来限制某一组/一个进程的资源使用,比如我们的日志agent需要部署到应用服务器,为了保证系统稳定性,可以限制agent的资源用量在合理范围。

2、使用cgroup
    cgroup相关的所有操作都是基于内核中的cgroup virtual filesystem,使用cgroup很简单,挂载这个文件系统即可。一般情况默认已经挂载到/sys/fs/cgroup目录下了

    mount | grep cgroup 查看系统默认是否挂载cgroup,cgroup包含很多子系统,用来控制进程不同的资源使用,我们只用其中cpu和memory这两个
在这里插入图片描述

3、用到的cgroup的子系统

cpu 子系统,主要限制进程的 cpu 使用率
memory 子系统,可以限制进程的 memory 使用量

4、创建子cgroup
    如果需要限制cpu,则在已挂载的 /sys/fs/cgroup/cpu 子系统下建立任意目录,如filebeat_cpu
    如果需要限制内存,则在已挂载的 /sys/fs/cgroup/memory 子系统下建立任意目录,如filebeat_memory

filebeat_cpu和filebeat_memory下都会自动生成与上级目录相同的文件,我们重点关注一下几个文件

filebeat_cpu
    - cgroup.procs        # 限制的进程pid        
    - cpu.cfs_period_us   # 用来配置时间周期长度  (us)
    - cpu.cfs_quota_us    # 用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数(us)
    
# cpu.cfs_period_us&cpu.cfs_quota_us 两个文件配合起来设置CPU的使用上限
# cfs_period_us的取值范围为1毫秒(ms)到1秒(s),cfs_quota_us的取值大于1ms即可
# 如果cfs_quota_us的值为-1(默认值),表示不受cpu时间的限制
# 如限制使用1个CPU的25%(每40ms能使用10ms的CPU时间,即使用一个CPU核心的25%)
# 则echo 40000 > cpu.cfs_period_us && echo 40000 > cpu.cfs_quota_us

filebeat_memory
    - cgroup.procs            # 限制的进程pid
    - memory.limit_in_bytes   # 限制的内存大小
    - memory.oom_control     

# memory.oom_control
# 0:将启动OOM-killer,当内核无法给进程分配足够的内存时,将会直接kill掉该进程
# 1:表示不启动OOM-killer,当内核无法给进程分配足够的内存时,将会暂停该进程直到有空余的内存之后再继续运行;
# 同时,memory.oom_control还包含一个只读的under_oom字段,用来表示当前是否已经进入oom状态,也即是否有进程被暂停了。

改造的filebeat启动脚本,支持在启动后限制内存和cpu:

#!/bin/sh
# @metainfo: filebeat 启动脚本
# @scriptName: restartFilebeat.sh
# 1.设置filebeat需要的主机和应用信息,详情见filebeat.yml
# 2.设置针对filebeat CPU、内存的 cgroup 限额: CPU -> 单核25%,内存 -> 500M 
# 3.停止旧进程,检查环境变量,重启filebeat
# 4.写入filebeat进程oom_score_adj值(999),写入进程号至为filebeat准备的cgroup.procs中
# 5.review 配置信息

source /etc/profile
source ~/.bash_profile

# ==========================variable============================= #
_hostIp=`ifconfig eth0|grep "inet "|awk '{print $2}'`
_serviceName=(`ps -ef |grep -Ev 'grep' | grep -Eo 'applicationName=.*? ' | awk '{print  $1}' | awk -F = '{print $2}'`)

filebeat_oom_score_adj=999              # OOM时,将优先OOM掉filebeat,虽然现在占用不大,为避免特殊情况影响业务
filebeat_memory_limit_mb=500M     # 内存限额
filebeat_single_cpu_scale=0.25      # 占单个cpu的比例
filebeat_cfs_period_us=40000            # CPU的时间周期
filebeat_cfs_quota_us=`echo $filebeat_single_cpu_scale $filebeat_cfs_period_us | awk '{printf "%0.0f\n" ,$1*$2}'`           # 周期内的限额 

# ========================== ENV ============================= #
export hostIp=$_hostIp
export serviceName=$_serviceName

# ========================== cgroup check and setting ============================= #
if [[ -d "/sys/fs/cgroup/cpu/filebeat_cpu" ]] && [[ -d "/sys/fs/cgroup/memory/filebeat_memory" ]];then
    echo 'cgroup [filebeat_cpu、filebeat_memory] is exist'
else
    echo 'cgroup [filebeat_cpu、filebeat_memory] is not exist , now mkdir and setting quota'
    # CPU
    mkdir /sys/fs/cgroup/cpu/filebeat_cpu
    # 内存
    mkdir /sys/fs/cgroup/memory/filebeat_memory
fi

# cfs_period_us用来配置时间周期长度
# cfs_quota_us用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数
# 两个文件配合起来设置CPU的使用上限。两个文件的单位都是微秒(us),
# cfs_period_us的取值范围为1毫秒(ms)到1秒(s),cfs_quota_us的取值大于1ms即可
# 如果cfs_quota_us的值为-1(默认值),表示不受cpu时间的限制
# 限制使用1个CPU的25%(每40ms能使用10ms的CPU时间,即使用一个CPU核心的25%)
echo $filebeat_cfs_period_us > /sys/fs/cgroup/cpu/filebeat_cpu/cpu.cfs_period_us
echo $filebeat_cfs_quota_us > /sys/fs/cgroup/cpu/filebeat_cpu/cpu.cfs_quota_us

# 内存限制小于 400M,写入自动转换为bytes
echo $filebeat_memory_limit_mb > /sys/fs/cgroup/memory/filebeat_memory/memory.limit_in_bytes
# 0:即使系统有交换空间,也不使用交换空间
#echo 0 > /sys/fs/cgroup/memory/filebeat_memory/memory.swappiness
# 0:OOM-killer 1:暂停进程等有可用内存再继续运行
#echo 0 > /sys/fs/cgroup/memory/filebeat_memory/memory.oom_control


# ==========================kill old============================= #
oldPid=(`ps -ef|grep 'agent7.2/filebeat-7.2.0-linux-x86_64/filebeat' |grep -v 'grep'|awk '{print $2}'`)
if [[ ! $oldPid ]]; then
    echo "filebeat agent not runing,now run!"
else
    echo "oldPid => [${oldPid[*]}],kill now"
    kill ${oldPid[*]}
fi

sleep 1s

# ========================== check ENV and run ============================= #
if [[ ! $hostIp ]]||[[ ! $serviceName ]]; then
  echo "env [hostIp] or [serviceName] is not set, cancel start!"
else
  if [ ${#serviceName[*]} -ne 1 ]; then
   echo "env [serviceName] more than one:serviceName => [${serviceName[*]}],cancel start!"
  else
   nohup /data/agent7.2/filebeat-7.2.0-linux-x86_64/filebeat run -e -c /data/agent7.2/filebeat-7.2.0-linux-x86_64/filebeat.yml  >> /data/agent7.2/nohup.out 2>&1 &
  fi
fi

# !!! 确保已经在运行,延时可能在cgroup未生效的情况下应用内存就增长了,但是不会超过限制内存的20%
sleep 1s

pid=(`ps -ef|grep 'agent7.2/filebeat-7.2.0-linux-x86_64/filebeat' |grep -v 'grep'|awk '{print $2}'`)


if [[ ! $pid ]];then
    echo "filebeat not runing,cgroup no check"
else
    # ========================== setting oom_socre_adj ============================= #
    echo $filebeat_oom_score_adj > /proc/$pid/oom_score_adj
    # ========================== setting filebeat cgroup procs ============================= #
    echo $pid > /sys/fs/cgroup/cpu/filebeat_cpu/cgroup.procs
    echo $pid > /sys/fs/cgroup/memory/filebeat_memory/cgroup.procs
fi

# ========================== review setting ============================= #
echo "pid->$pid hostName->$HOSTNAME hostIp->$hostIp appName->$serviceName" \
"filebeat_oom_score_adj->"`cat /proc/$pid/oom_score` \
"filebeat_cpu/cpu.cfs_period_us->"`cat /sys/fs/cgroup/cpu/filebeat_cpu/cpu.cfs_period_us` \
"filebeat_cpu/cpu.cfs_quota_us->"`cat /sys/fs/cgroup/cpu/filebeat_cpu/cpu.cfs_quota_us` \
"filebeat_memory/memory.limit_in_bytes->"`cat /sys/fs/cgroup/memory/filebeat_memory/memory.limit_in_bytes`

猜你喜欢

转载自blog.csdn.net/m0_37886429/article/details/113366975