有个段子是说现在创业公司招人的如果说自己是「大数据」(Big Data),意思其实是说他们会把日志收集上来,但是从来不看。段子归段子,近些年所谓「微服务」「容器化」等「热门技术」的发展,的确促进了日志收集等技术的发展。而 ELK (Elasticsearch + Logstash + Kibana) 也不再是日志收集与展示系统的铁三角了。本文介绍用 Filebeat 代替 Logstash Shipper,用 Elasticsearch Ingest Node 代替 Logstash Indexer 实现一个更加轻量高效的日志收集与展示系统。

一、Beats

与 Logstash 一样,Beats 也是由 Elastic 公司出品;与 Logstash 不同,Beats 只是 data shipper。Beats 家族共享 libbeat 这个库,每个产品分别实现对不同数据来源的收集。目前官方实现有:

  • Filebeat —— 文件
  • Metricbeat —— 系统及应用指标
  • Packetbeat —— 网络抓包分析,如 SQL, DNS
  • Winlogbeat —— Windows 系统日志
  • Auditbeat —— 审计数据
  • Heartbeat —— ICMP, TCP, HTTP 监控

此外还有数十个社区实现

Beats 由 Go 实现,因此可以方便地做到单文件无依赖运行,十分适合于大面积部署于不同发行版、不同版本的服务器集群之中。相比 Logstash Shipper 需要在每台机器上安装巨大的 JRuby 依赖,Beats 对小内存机器或容器更加友好。

Filebeat

Filebeat 用于从文件中读取日志,相当于一个高性能的 tail -f,可用于各种 HTTP 服务器的日志收集。以下是一份收集 Nginx 日志的简易配置。完整的配置及模板可在文末的 Ansible Playbook 懒人包中找到。

# 日志源
filebeat.prospectors:

  - type: log
    paths:
      - /var/log/nginx/access.log
      - /var/log/nginx/*.access.log
    fields:
      type: nginx.access

  - type: log
    paths:
      - /var/log/nginx/error.log
    fields:
      type: nginx.error

# 当检测到运行于 AWS, GCE, DigitalOcean 等平台时,自动添加机器信息
processors:
  - add_cloud_metadata:

# 因为我使用了自定义模板,所以这里禁止它加载默认模板
setup.template.enabled: false

# 输出至 Elasticsearch
output.elasticsearch:

  hosts: ["http://localhost:9200/"]

  # 根据不同的日志类型,使用不同的 Ingest Pipeline
  pipelines:
    - pipeline: nginx.access
      when.equals:
        fields.type: nginx.access
    - pipeline: nginx.error
      when.equals:
        fields.type: nginx.error

Cloudfrontbeat

除了 Filebeat,另外值得一提的是社区实现 Cloudfrontbeat。由于我的博客前置了 CloudFront 作为加速和缓存,因此 Nginx 日志中的信息并不完整,而 Cloudfrontbeat 则可以用来解析并上传 CloudFront 边缘节点的日志。

Cloudfrontbeat 的原理是:

  • CloudFront 将边缘节点日志记录到 S3 中;
  • S3 将 ObjectCreate 事件发布到一个 SQS 消息队列中;
  • Cloudfrontbeat 订阅 SQS 得知新日志产生的事件,从 S3 获取日志文件,解析后灌进 ES。

此外 Cloudfrontbeat 提供了 backfill 模式,可以遍历指定日期范围内已经存在于 S3 内的日志文件,将其信息灌进 SQS,然后再切换成正常的 worker 模式跑起来,就能把旧日志也灌进 ES 了。

Cloudfrontbeat 作者提供了一个 CloudFormation,可以一键把 S3 + SQS 配置好。

二、Elasticsearch Ingest Node

Elasticsearch Ingest Node 是 Elasticsearch 5.0 起新增的功能。在 Ingest Node 出现之前,人们通常会在 ES 前置一个 Logstash Indexer,用于对数据进行预处理。有了 Ingest Node 之后,Logstash Indexer 的大部分功能就可以被它替代了,grok, geoip 等 Logstash 用户所熟悉的处理器,在 Ingest Node 里也有。对于数据量较小的 ES 用户来说,省掉一台 Logstash 的开销自然是令人开心的,对于数据量较大的 ES 用户来说,Ingest Node 和 Master Node, Data Node 一样也是可以分配独立节点并横向扩展的,也不用担心性能瓶颈。

目前 Ingest Node 已支持数十种处理器,其中的 script 处理器具有最大的灵活性。

/_template 类似,Ingest API 位于 /_ingest 下面。用户将 pipeline 定义提交之后,在 Beats 中即可指定某 pipeline 为数据预处理器。

对于新安装单节点 ES 集群来说,默认那个节点已经具有 Ingest Node 的 role,无需配置即可直接使用。

文末的 Ansible Playbook 懒人包中包含了我使用的 Ingest Pipeline 配置。在 nginx.access 中,我使用了自定义的 grok pattern:

"grok": {
  "field": "message",
  "patterns": [
    "%{IP:nginx.access.remote_addr} - %{DATA:nginx.access.remote_user} \\[%{HTTPDATE:nginx.access.time_local}\\] \"%{DATA:nginx.access.request}\" %{NUMBER:nginx.access.status} %{NUMBER:nginx.access.bytes_sent} \"%{DATA:nginx.access.http_referrer}\"
TA:nginx.access.http_user_agent}\"( \"%{DATA:nginx.access._kvs}\")?"
  ],
  "ignore_missing": true
}

[...]

"kv": {
  "field": "nginx.access._kvs",
  "field_split": "\\|",
  "value_split": "=",
  "target_field": "nginx.access",
  "ignore_missing": true,
  "ignore_failure": true
}

这个 pattern 兼容标准的 Nginx "combined" 日志格式,也可以支持在此基础上附加任意 K=V 字段,用于记录你关心的、但是不在默认日志格式里其他字段。比如我的 Nginx 日志格式是这样:

log_format main
  '$remote_addr - $remote_user [$time_local] '
  '"$request" $status $body_bytes_sent '
  '"$http_referer" "$http_user_agent" '
  '"'
  'host=$host|'
  'request_time=$request_time|'
  'http_x_forwarded_for=$http_x_forwarded_for|'
  'http_cloudfront_viewer_country=$http_cloudfront_viewer_country|'
  'upstream_addr=$upstream_addr|'
  'upstream_status=$upstream_status|'
  'upstream_response_time=$upstream_response_time'
  '"'
;

前半段是的 "combined" 日志格式,之后你可以附加任意 "k1=v1|k2=v2|k3=v3" 的字段,这些字段都会被 Ingest Pipeline 处理后被 ES 索引。

三、Kibana

Kibana 是使用 Node.js 构建的 Elasticsearch 展示前端。推荐使用最新版本(6.x),至少也要 5.5 以上,因为 Kibana 5.5 新增了非常好用的 filter UI,可以方便地进行字段过滤,而不用再手写 Lucene 或 ES Query DSL 了:

kibana-filter-ui_2017-12-21_01-48-56

oauth2_proxy

严格来说这和 Kibana 没什么关系。但是由于 Kibana 自身没有健全的用户认证(默认配置下无任何认证),oauth2_proxy 非常适合前置于 Kibana 作为用户认证。正如它的名字所暗示的,oauth2_proxy 能让任何 HTTP 服务无需改动直接支持 OAuth2 认证。它可以与服务串联使用,也可以在 Nginx auth_request 模块的配合下,进行旁路认证:

location /oauth2/ {
  proxy_pass       http://127.0.0.1:4180;
  proxy_set_header Host                    $host;
  proxy_set_header X-Real-IP               $remote_addr;
  proxy_set_header X-Scheme                $scheme;
  proxy_set_header X-Auth-Request-Redirect $request_uri;
}

location /kibana/ {
  satisfy any;

  auth_request /oauth2/auth;
  error_page 401 = /oauth2/sign_in;

  allow 127.0.0.1;
  deny all;

  proxy_pass http://127.0.0.1:5601/;
}

四、Ansible Playbook 懒人包

我写了一个简单的 Ansible Playbook,用于安装以上提到 Filebeat, Elasticsearch, Kibana 和 oauth2_proxy。


欢迎留下评论。评论前,请先阅读《隐私声明》。