分布式系统中的日志落地经验总结

在过去的几年里推进公司容器云的落地,陆陆续续的和日志打了不少交道,在这里做一个总结:

  • 为什么需要日志
  • 日志如何接收与存储
  • 日志如何收集
  • 日志收集客户端分析
  • 日志的标准化
  • 日志报警
  • 日志归档
  • 其他问题

为什么需要日志

日志的作用我觉得有三点:

  1. 故障排错
  2. 数据分析
  3. 业务审计

1,关于故障排错,当线上发生异常,查看应用的错误日志、堆栈信息、代理层的访问日志是非常有帮助的,不同级别的日志能够很好的帮助我们定位到故障点,而访问日志则能让我们知道异常情况发生在哪个环节,是client到代理,还是代理到后端。这也是降低MTTD的一个很好的工具。

2,对日志数据的分析,一方面能协助分析问题、定位故障,另一方面还可以帮我们更好的了解系统运行状态甚至是了解我们的用户。比如基于HTTP状态码和响应时间可以分析出系统的稳定性和性能状况。而基于访问日志的来源IP可以分析出用户地域属性、而基于日志量级的时间分布可以了解到系统和用户的活跃时间范围。

3,上面两种多数是最近几个月的热数据,需要实时查看和分析,也有一部分需要保存的更久,以满足合规、审计等的需求。比如有些备案就要求不同级别的日志保存不同的时长以备随时调用查看。

日志的收集与存储

在分布式系统中,众多服务分散部署在成百上千台甚至成千上万台服务器上,要想快速方便的实现上述的查找、分析和归档功能,就需要有一个集中的日志系统,通过日志收集器将各类日志进行统一汇总,存储,这样不仅能方便查找所有的日志,还有可能在众多日志数据中挖掘到一些意想不到的关联关系。

有了这个定位接下来就可以开始详细的规划了,首先是日志服务器的选型,有经典的ELK,有商业的splunk,但我们并没有采取上述两种,splunk功能完全符合,但对大量级的日志而言成本偏高,ELK中的kibana也在高版本中开始商业化,这让我们开始寻找替代方案,graylog便是一种。它的绝大多数功能都是免费的,并且性能优越,上图:
分布式系统中的日志落地经验总结_第1张图片
graylog也采用Elasticsearch作为存储和索引以保障性能,MongoDB用来存储少量的自身配置信息,master-node模式具有很好的扩展性,UI上自带的基础查询与分析功能比较实用且高效,支持LDAP、权限控制并有丰富的日志类型和标准(如syslog,GELF)并支持基于日志的报警。

在日志接收方面通常是网络传输,可以是TCP也可以是UDP,在实际生产环境量级较大多数采用UDP,也可以通过MQ来消费日志。

不同日志有不同的收集方式,总结下来有如下几种:

  1. 系统日志(如/var/log/message)可通过rsyslog传输到统一服务器,也可以通过daemonset的容器进行收集发送。
$ head /etc/rsyslog.conf
*.* @your-log-server-addr:port;RSYSLOG_SyslogProtocol23Format
  1. 访问日志,如access.log这类文本,可经过日志收集器传输。
  2. 应用日志:
    • 可落地文本后再经过日志收集器传输;这样会产生多份IO对性能有影响,而且需要对历史日志进行删除以保证磁盘空间;
    • 由应用直接发送到日志服务器或消息队列:但网络异常可能会影响应用自身稳定性。
    • 日志直接输出到控制台,由收集器去收集控制台日志,但docker如果启动失败或被删除则相关的控制台日志也会被快速删除,需注意时效性保障问题。
    • 日志直接输出到控制台,通过docker提供的不同logdriver将日志以不同渠道输出,但注意如需变更配置还需要重启docker daemon。
      比如:docker版本要求在1.8以上。
$ docker --version
Docker version 1.12.0, build 8eab29e
修改配置文件/usr/lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd --storage-driver=overlay --graph=/data/dockerapp \
--log-driver=gelf \
--log-opt gelf-address=udp://xxx.com:9999 \
--log-opt tag=docker \
--log-opt env=pro \
--log-opt gelf-compression-type=gzip \
--log-opt gelf-compression-level=1

kubernetes,如果是在公有云如Google Cloud上面的Stackdriver Logging

为统一管理,如今一般选择daemonset的方式部署日志收集器,对上述日志进行统一的收集。

日志收集器主要有:

  1. Logstash
  2. Filebeat
  3. Fluentd
  4. Fluent-bit

Logstash功能强大,但性能消耗也大,相对比较重,更适合作为中间环节,elastic后来推出的Filebeat更适合,它的性能好且资源占用少。而Fluentd作为CNCF指定用品,在17年初用了一段时间,觉得性能不是很好,它基于磁盘或内存的buffer优化空间也非常有限,但随着加入CNCF后市场占比更多,也推出了Fluent-bit消耗1/10的资源。
这些收集器可以以daemonset的方式部署,确保每个节点上有且只有一个实例在收集日志。

日志标准化

上述只能实现日志的收集、存储和展示,但想要更好的分析,就需要用到日志标准化,对不同日志、不同类型做不同的管理。如为方便快速查找某个系统在过去一段时间的访问质量,对需要对代理层日志中的HTTP状态码做清晰明了的界定。
对于应用日志,一般通过内部封装好的包直接推广使用,如java的logback,golang通过封装zap等方式,集成如下通用字段:

  1. 环境信息(如;dev,test,beta)
  2. 团队信息(类似namespace)
  3. 应用名(serviceName)
  4. 时间戳
  5. 日志级别

为提高可扩展性,在应用日志和Nginx日志都采用json格式,这样就省去如logstash等组件的加工环节,同时也可基于json中的字段对日志做处理,如生产环境下logLevel=debug级别的日志不做处理,对size过大的做截断。
更新:随着单台节点上日志规模的增大,我们发现如果用filebeat进行json化日志收集存在较大的性能压力,还是需要增加一层logstash进行json日志的处理,filebeat回归到最基本的日志收集层面。

对访问日志,可添加的就更多了,如:

log-format-upstream: '{ "message":"$remote_addr $host $request_time $status", "remote_addr":
"$remote_addr", "domain_name": "$host", "remote_user": "$remote_user", "http_tracker_id":
"$http_tracker_id", "time_local": "$time_local", "request_proto":
"$server_protocol", "request_path": "$request_uri","request_args": "$args","response_status":"$status","request_time":"$request_time","body_bytes_sent":"$body_bytes_sent","request_length":"$request_length",
"http_referer":"$http_referer","http_user_agent": "$http_user_agent","upstream_addr":"$upstream_addr",
"upstream_connect_header_response_time":"$upstream_connect_time $upstream_header_time
$upstream_response_time","upstream_status":"$upstream_status","http_x_forwarded_for":
"$http_x_forwarded_for" }'

但注意,在日志量级较大的情况下如果字段设置过多会对日志收集器有一些性能压力。

日志报警

graylog自身支持对日志的报警,如某个域名\应用在某个时间段内的错误日志数如果超过某个阈值就报警。报警渠道支持邮件和webhook,对接上我们的告警中心接口即可实现特定异常发送给特定人群。全局异常做基本分析后再发给特定人群和管理员,如:
分布式系统中的日志落地经验总结_第2张图片
分布式系统中的日志落地经验总结_第3张图片

日志分析

  • 对bind日志的分析
    分布式系统中的日志落地经验总结_第4张图片

  • 对nginx异常状态码的分析
    分布式系统中的日志落地经验总结_第5张图片

  • 地域分析
    分布式系统中的日志落地经验总结_第6张图片

日志归档

graylog商业版自带归档功能,如果自己做可以通过消息队列将日志数据再存入到HDFS中一份。

总结

分布式系统中的日志落地经验总结_第7张图片

其他问题

  1. 日志依赖ES和Luence,后者对每个字段要求最大为32kb,超出的将不再保存。可以设置ignore_above =256 对过大字段不做解析y,也可以在收集端过滤较大日志,如filebeat上设置:max_bytes = 327660
  2. 注意日志生成时间与日志存入时间的区分,避免组件或网络故障后的日志时间显示不准确。
  3. 关于日志标准化的推广,可以对不同语言可以做不同的包,供开发团队直接引用。
  4. 反思之前为了确保日志不随着pod删除而删除,将日志文本化并挂载到宿主机上再做消费,这样会导致2份IO,且违反12因子中的日志事件原则,当前完全基于docker的json-file日志消费目前已经较为成熟,可以考虑直接使用该方案。
  5. graylog对日志字段有一定的要求,除syslog外,其他应用入口比如必须包含“short_message”这样的一个字段,这个不太友好。
    参考:
    [1]:https://gist.github.com/StevenACoffman/4e267f0f60c8e7fcb3f77b9e504f3bd7
    [2]:https://docs.fluentd.org/v0.12/articles/kubernetes-fluentd

你可能感兴趣的:(技术)