Elastic:使用 Fluentd 及 Elastic Stack 进行应用日志采集

日志记录是任何应用程序中最重要的方面。 每个应用程序都有不同风格的日志记录机制。 但是,设计良好的日志记录机制对于系统管理员和开发人员来说是一个巨大的实用工具。 日志对于调试影响你的服务的原因至关重要。

现在我们知道了在应用程序设计中收集日志的价值,我们将使用 Bunyan 库在 NodeJS 应用程序中实现日志记录。 Bunyan 是一个用于 NodeJS 服务的简单快速的 JSON 日志库。
作为应用程序设计的日志记录还应该考虑诸如记录什么、何时以及记录多少,如何控制日志记录等问题。

例如:你不能仅为了调试,记录密码或任何敏感信息,这是使用日志记录的一种糟糕方式。日志记录具有 fatal、error、info、debug、warn、trace等级别。 每个编程都有自己的级别,但这是主要所有日志库中可用的标准级别。

Fluentd Elasticsearch Kibana stack

在我今天的练习中,我将使用如下的架构来部署:

我们在 Ubuntu OS 上部署 Fluentd。我们使用一个 nodejs 的应用程序来生产 JSON 格式的应用日志,并让 Fluentd 来收集并发送至 Elasticsearch。

 

Fluentd

Fluentd 是一个开源的数据收集器,它可以让你统一数据的收集和使用,以便更好地使用和理解数据。 Fluentd 是一个广泛使用的用 Ruby 编写的工具用于收集日志并将其流式传输到第三方服务(如 loggly、Elasticsearch、mongo)以进行进一步处理。

Fluentd 提供了大量功能,我们将讨论其中的一些。

统一日志

JSON 格式的日志总是优于任何日志工具。 Fluentd 尝试尽可能将数据结构化为 JSON:这允许 Fluentd 统一处理日志数据的所有方面:收集、过滤、缓冲和发送日志到多个来源和目的地。

可插拔架构

Fluentd 有一个灵活的插件系统,允许用户扩展其核心功能。 用户可以编写自己的自定义插件,前提是它们应该用 ruby 编写。一些 Fluentd 插件是 fluentd-elasticsearch, fluentd-mongo, fluentd-splunk-hec, fluentd-kafka。

最少资源

因为它是用 C 和 Ruby 的组合编写的,并且需要很少的系统资源。 Fluentd vanilla 实例在 30-40MB 内存上运行,可以处理 13,000 个事件/ps。

内置可靠性

Fluentd 支持基于内存和文件的缓冲,以防止数据节点间丢失。 Fluentd 还支持强大的故障转移,并且可以设置为高可用性。

 

安装 Fluentd Linux (td-agent)

设置 Fluentd 是一个非常简单直接的过程。 在安装之前,我们只会看看 Fluentd 和 td-agent 有什么区别。Fluentd 是一个用于统一数据记录层的开源数据收集器。 Fluentd 是一个由 Treasure Data 制作和赞助的项目。 Treasure Data 负责分发 Fluentd 的稳定版本,称为 td-agent。 所以基本上这只是一个名字的不同,所有的内部部分仍然是 Fluentd。

我们可以到地址 https://docs.treasuredata.com/display/public/PD/Installing+TD+Toolbelt+and+Treasure+Agent 来查找针对 Linux 的安装:

curl -L https://docs.treasuredata.com/display/public/PD/Installing+TD+Toolbelt+and+Treasure+Agent | sh

我们可以使用如下的命令来进行启动:

sudo systemctl start td-agent

我们可以使用如下的命令来检查 td-agent 的运行状态:

systemctl status td-agent

或者:

service td-agent status
$ service td-agent status
● td-agent.service - td-agent: Fluentd based data collector for Treasure Data
     Loaded: loaded (/lib/systemd/system/td-agent.service; enabled; vendor pr>
     Active: active (running) since Tue 2021-06-01 20:00:25 CST; 3s ago
       Docs: https://docs.treasuredata.com/display/public/PD/About+Treasure+D>
    Process: 155046 ExecStart=/opt/td-agent/bin/fluentd --log $TD_AGENT_LOG_F>
   Main PID: 155068 (fluentd)
      Tasks: 8 (limit: 18984)
     Memory: 98.2M
     CGroup: /system.slice/td-agent.service
             ├─155068 /opt/td-agent/bin/ruby /opt/td-agent/bin/fluentd --log >
             └─155071 /opt/td-agent/bin/ruby -Eascii-8bit:ascii-8bit /opt/td->

上面显示 active 状态,表明它的运行是正常的。

 

安装 Elastic Stack

在今天的练习中,我将使用 docker 来部署 Elasticsearch 及 Kibana。我创建了一个叫做 fluentd 的目录:

$ pwd
/Users/liuxg/data/fluentd
$ ls -al
total 16
drwxr-xr-x    4 liuxg  staff   128 Jun  1 16:01 .
drwxr-xr-x  134 liuxg  staff  4288 Jun  1 15:59 ..
-rw-r--r--    1 liuxg  staff    29 Jun  1 16:01 .env
-rw-r--r--    1 liuxg  staff   832 Jun  1 16:16 docker-compose.yml
$ cat .env
ELASTIC_STACK_VERSION=7.13.0

上面 docker-compose.yml 的内容如下:

docker-compose.yml

version: '3.7'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_STACK_VERSION}
    container_name: es01
    environment:
      - discovery.type=single-node
      - network.host=0.0.0.0
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata01:/usr/share/elasticsearch/data        
    ports:
      - 9200:9200
    networks:
      - elastic
 
  kibana:
    image: docker.elastic.co/kibana/kibana:${ELASTIC_STACK_VERSION}
    container_name: kibana
    ports: ['5601:5601']    
    networks: ['elastic']
    environment:
      - SERVER_NAME=kibana.localhost
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - I18N_LOCALE=zh-CN
    depends_on: ['elasticsearch']
 
volumes:
  esdata01:
    driver: local
 
networks:
  elastic:
    driver: bridge

在上面需要注意的是:我需要让 Elasticsearch 被另外一台机器所访问,我做了这个设置 :

      - discovery.type=single-node
      - network.host=0.0.0.0

它将使 Elasticsearch 绑定于所有的网路接口上,也即它可以同时被 localhost:9200 访问,也可以被 <private ip>:9200 所访问。

我们把 docker 运行起来,并打入在 docker-compose.yml 所在的目录打入如下的命令:

docker-compose up

这样我们的 Elasticsearch 及 Kibana 就会被运行起来。如果你还有问题,请参考我之前的文章 “用 Docker 部署 Elastic 栈”。

 

Nodejs server

我们在 Ubuntu 上安装好 nodejs 的环境,并创建如下的 NodeJS API Server:

server.js

const bunyan     = require('bunyan');
const format     = require('bunyan-format');
const express    = require('express');
const bodyParser = require('body-parser')
const FS         = require('fs');
const _          = require('lodash');

global.logger = configureLogger();

const app = express();

// for logging request
const logRequest = function (req, res, next) {
  const start = new Date();
  const end   = res.end;

  res.end = function(payload, encoding){
    let responseTime = Date.now() - start.getTime();
    end.call(res, payload, encoding);
    const contentLength = Number(res.getHeader('Content-Length'));
    const _data = {
      req: _.pick(req, ['method', 'url']),
      res: _.pick(res, ['statusCode']),
      responseTime: responseTime,
      contentLength: isNaN(contentLength) ? 0 : contentLength
    }
    logger.info('%s %s %s %d %dms - %d bytes', (req.hostname || ''), _data.req.method, _data.req.url, _data.res.statusCode, _data.responseTime, _data.contentLength);    
  }
  next();
}

app.use(logRequest);
app.use(bodyParser.json());

function configureLogger(){
  const logFile = './api.LOG';
  const ws = FS.createWriteStream(logFile, { flags: 'a' });
  
  // formatting options for stdout
  const formatOut = format({ outputMode: 'short', levelInString: true, colorFromLevel: {
    20: 'blue',
    30: 'green',
    40: 'yellow',
    50: 'red',
    60: 'brightRed'
  }});
  
  
  const fileFormatter = format({outputMode: 'bunyan', level: true }, ws);
  
  const logger = bunyan.createLogger({
   name: 'API Logger', 
   level: 'debug',
   streams: [
     {
      level: 'debug',
      stream: fileFormatter
     },
     {
      level:  'info', 
      stream: fileFormatter
     },
     {
      level: 'debug', 
      stream: process.stdout
     }
   ]
  });
  
  logger.log = function (message) {
    logger.debug(message);
  }
  return logger;
}

app.listen(4044, () => { 
  logger.log("API Server Listening on PORT 4044");
});

在运行时,我们需要安装相应的库。我们可以使用如下的命令来运行:

nodejs server.js
$ nodejs server.js 
{"name":"API Logger","hostname":"liuxgu","pid":155774,"level":20,"msg":"API Server Listening on PORT 4044","time":"2021-06-01T12:04:04.926Z","v":0}
{"name":"API Logger","hostname":"liuxgu","pid":155774,"level":30,"msg":"ubuntu GET /favicon.ico 404 5ms - 150 bytes","time":"2021-06-01T12:04:09.389Z","v":0}

当我们从任何一个电脑上访问这个服务器时,我们可以看到不断增加的日志输出:

我们可以在当前运行的目录下找到一个叫做 api.LOG 的文件:

$ pwd
/home/liuxg/nodejs/nodejs_log
liuxg@liuxgu:~/nodejs/nodejs_log$ ls
api.LOG  index.js  node_modules  server.js

它里面包含的就是我们的应用输出日志。

 

配置 Fluentd

配置 Fluentd 或 td-agent 很简单,所有插件配置都写在 fluentd.conf 或 td-agent.conf 中。 配置位置可以是:

/etc/td-agent/td-agent.conf 

或者是:

/etc/fluentd/fluent.conf

由于我们使用 Elasticsearch 插件将数据发送到 Elasticsearch,因此我们需要 fluentd-plugin-elasticsearch 输出插件,你可以使用 gem 安装它:

sudo gem install fluent-plugin-elasticsearch

更多关于 Fluentd 插件输出插件

我们可以把之前安装默认的 td-agent.conf 保存到另外一个文件中:

cd /etc/td-agent/
mv td-agent.conf td-agent.conf-default

最终 td-agent.conf:

td-agent.conf

<system>
  workers 1
  log_level trace
</system>

# logs for api
<source>
    @type tail
    format json
    tag cli.stdout
    path /home/liuxg/nodejs/nodejs_log/api.LOG
    pos_file /var/log/td-agent/tmp/cli.log.pos
</source>

# send logs of application to Elasticsearch
<match cli.stdout>
  @type elasticsearch
  host 192.168.0.3
  port 9200
  index_name fluentd.${tag}
  flush_interval 5s
</match>

请注意:你需要依据自己的 log 的位置修改上面的 path。你也需要根据自己的配置修改上面的 elasticsearch 的 IP 地址。经过上面的修改过后,你需要使用如下的命令来重新启动 fluentd:

sudo service td-agent restart

接下来,我们访问 nodejs 服务器的网址 <ip>:4044:

 

查看收集的日志

我们可以打开 Kibana,并查看最新的索引:

GET _cat/indices

我们可以看到:

yellow open logstash                            AUIwddr8SmuwE41h6Xdv6A 1 1  9651    0   3.6mb   3.6mb
green  open .kibana_task_manager_7.13.0_001     TK3xQufNQgmgIct-A-7G-g 1 0    10 6997 798.5kb 798.5kb
yellow open fluentd.cli.stdout                  GPNyDu1tSdOuEAzTcncWUg 1 1    15    0  25.7kb  25.7kb
green  open .apm-custom-link                    QstaYgpPSlGaQZLAJ3lq8Q 1 0     0    0    208b    208b
green  open .kibana-event-log-7.13.0-000001     vcUtY3K7T6aTrI7yXYDjUg 1 0     1    0   5.6kb   5.6kb
green  open .apm-agent-configuration            Dmisg-peQ2OaEagejNzcxg 1 0     0    0    208b    208b
yellow open metricbeat-7.13.0-2021.06.01-000001 ckCl19l5TNmfrDtDzAe9rA 1 1 73050    0  60.7mb  60.7mb
green  open .kibana_7.13.0_001                  pqYzeAQ9Sk2w_1mrRO2_Lw 1 0    23    8   2.1mb   2.1mb

在上面,我们可以看到一个叫做 fluentd.cli.stdout  的索引。我们可以通过如下的命令来查询该索引:

GET fluentd.cli.stdout/_search

上面命令显示的结果为:

我们可以看到被收集的日志。

相关推荐