使用ELK搭建日志服务器

1 安装ELK

ELK其实是Elasticsearch,Logstash 和 Kibana三个产品的首字母缩写,这三款都是开源产品。
ElasticSearch(简称ES),是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。
Logstash,是一个数据收集引擎,主要用于进行数据收集、解析,并将数据发送给ES。支持的数据源包括本地文件、ElasticSearch、MySQL、Kafka等等。
Kibana,为 Elasticsearch 提供了分析和 Web 可视化界面,并生成各种维度表格、图形。

1.1 安装ElasticSearch

下载地址:https://www.elastic.co/cn/downloads/elasticsearch
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.2-linux-x86_64.tar.gz
下载后上传到服务器/app/elk目录下,执行:

tar -zxvf  elasticsearch-8.2.2-linux-x86_64.tar.gz
cd elasticsearch-8.2.2
# 修改ES配置
vim ./config/elasticsearch.yml

ES配置修改内容:

17行: cluster.name: test
23行: node.name: node-1
33行: path.data: /app/elk/elasticsearch-8.2.2/data 
37行: path.logs: /app/elk/elasticsearch-8.2.2/logs
56行: network.host: 0.0.0.0
61行: http.port: 9200
74行: cluster.initial_master_nodes: ["node-1"]
88行: action.destructive_requires_name: false
98行: xpack.security.enabled: false
100行:xpack.security.enrollment.enabled: false
104行:enabled: false
109行:enabled: false

保存后启动ES,
注意:一定要用非root用户启动:

# 创建用户
useradd yehongzhi
# 设置密码
passwd yehongzhi
# 赋予用户权限
chown -R yehongzhi:yehongzhi /app/elk/elasticsearch-8.2.2/
# 切换用户
su yehongzhi
# 启动 -d表示后台启动
./bin/elasticsearch -d

如果报错:

max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

编辑文件/etc/sysctl.conf

sudo vi /etc/sysctl.conf 

在末尾添加:

vm.max_map_count=655360

保存后再执行:

sudo sysctl -p

再启动ES就可以了。
ES启动后访问:http://x.x.x.x:9200
返回下面的内容,代表ES启动成功了

{
  "name": "node-1",
  "cluster_name": "test",
  "cluster_uuid": "Lo18yh07T-mfMxwQ6QJM8A",
  "version": {
    "number": "8.2.2",
    "build_flavor": "default",
    "build_type": "tar",
    "build_hash": "9876968ef3c745186b94fdabd4483e01499224ef",
    "build_date": "2022-05-25T15:47:06.259735307Z",
    "build_snapshot": false,
    "lucene_version": "9.1.0",
    "minimum_wire_compatibility_version": "7.17.0",
    "minimum_index_compatibility_version": "7.0.0"
  },
  "tagline": "You Know, for Search"
}

提供删除ES索引的脚本:

#/bin/bash

# 清理几天前的索引
dayn=2
# ES IP & port
esip=127.0.0.1
esport=9200
# 匹配日期正则表达式,日期格式为:YYYY.mm.dd
regular="[0-9]{4}(.[0-9]{2}){2}"

#指定日期
date1=`date -d "$dayn day ago" +%Y-%m-%d`
#当前日期
time=`date "+%Y-%m-%d %H:%M:%S"`
echo "${time}:开始清理  $date1(${dayn}天)之前ES索引"


res=`curl -XGET "http://${esip}:${esport}/_cat/indices/?v"|grep -E $regular|awk '{print $3}'`

t1=`date -d "$date1" +%s`
for var in $res; 
do
    # 如果索引中的日期格式已经是YYYY-mm-dd格式,就不用使用sed替换了,本例索引中日期格式为:YYYY.mm.dd
    date2=`echo $var| tr -cd $regular|sed 's/\./-/g'`
    t2=`date -d "$date2" +%s`
    if [ $t1 -gt $t2 ]; then
        #echo "$date1 > $date2"
        echo "${time}: 清理 $var 索引"
        #curl --user account:pwd -XDELETE "http://${esip}:${esport}/$var"
        curl -XDELETE "http://${esip}:${esport}/$var"
    fi
done

1.2 安装Kibana

下载地址:https://www.elastic.co/cn/downloads/kibana
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/kibana/kibana-8.2.2-linux-x86_64.tar.gz
下载后上传服务器/app/elk目录下解压:

tar -zxvf kibana-8.2.2-linux-x86_64.tar.gz

修改配置文件 kibana-8.2.2/config/kibana.yml

6行: server.port: 5601
11行: server.host: "x.x.x.x" 
26行: server.publicBaseUrl: "http://x.x.x.x:5601"
32行: server.name: "kibana"
# ES ip
43行: elasticsearch.hosts: ["http://x.x.x.x:9200"]
63行: elasticsearch.requestTimeout: 30000
# 设置中文版,默认英文版
167行: i18n.locale: "zh-CN"

同样使用非root用户启动:

nohup ./bin/kibana &

这里提供一段重启脚本:

#!/bin/bash
kibanapid=`ps -ef|grep -v grep|grep kibana-8.2.2|awk '{print $2}'`
echo old kibana-pid=$kibanapid
echo kill $kibanapid
kill -9 $kibanapid
sleep 5s
echo start kibana
nohup /app/elk/kibana-8.2.2/bin/kibana >/dev/null 2>&1 &
sleep 5s
kibanapid=`ps -ef|grep -v grep|grep kibana-8.2.2|awk '{print $2}'`
echo new kibana-pid=$kibanapid

其他服务ES、logstash启动脚本类似。
启动kibana后访问:http://x.x.x.x:5601

kibana.png

1.3 安装Logstash

下载地址:https://www.elastic.co/cn/downloads/logstash
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/logstash/logstash-8.2.2-linux-x86_64.tar.gz
下载后上传服务器/app/elk目录下解压:

tar -zxvf logstash-8.2.2-linux-x86_64.tar.gz

编辑logstash-8.2.2/config/logstash.yml

19行: node.name: logstash
133行: api.http.host: x.x.x.x
295行: xpack.monitoring.enabled: true
# ES IP
299行: xpack.monitoring.elasticsearch.hosts: ["http://x.x.x.x:9200"]
311行: xpack.monitoring.elasticsearch.sniffing: true
312行: xpack.monitoring.collection.interval: 10s
313行: xpack.monitoring.collection.pipeline.details.enabled: true
# ES IP
322行: xpack.management.elasticsearch.hosts: ["http://x.x.x.x:9200"]

使用非root用户启动logstash,使用默认配置logstash-sample.conf,后面会根据实际业务需求进行配置修改。

cd logstash-8.2.2
nohup ./bin/logstash -f /usr/logstash-8.2.2/config/logstash-sample.conf &

2 日志上传配置

2.1 log4j2

项目中使用了log4j2,则直接修改log4j2.xml配置文件,实现日志上传。
官方给了说明:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-tcp.html

2.1.1 log4j2.xml修改

log4j2.xml添加配置:




  
     
       
    
  

  
    
    
      
    
  


2.1.2 logstash配置修改

在logstash-8.2.2/config/目录下创建配置文件tcp.conf,该配置文件包括3部分:input、filter、output

2.1.2.1 input

通过命令:

./bin/logstash-plugin input
./bin/logstash-plugin codec

可以查看当前logstash版本支持的input插件和codec插件,如果缺少插件,还可以手工安装插件:

# 插件名不要带版本号
./bin/logstash-plugin install --no-verify 插件名

需要的插件可以在这里搜索:https://rubygems.org/
input的知识可参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/input/index.html
log4j2使用TCP传送日志:

input {
  tcp {
    host => "0.0.0.0"
    # 和log4j2.xml配置的端口保持一致
    port => "1234"
    mode => "server"
    type => "microservice"
    codec => plain
    add_field => {"appName" => "microservice"}
  }
}

2.1.2.2 filter

filter参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/filter/index.html
有关grok,参考:https://blog.51cto.com/u_12827626/3318179

filter {
   #默认是UTC日期,把变量index_date改成UTC+8日期,用于输出到ES时用做索引
   ruby { 
    code => "event.set('index_date', event.get('@timestamp').time.localtime + 8*60*60)" 
   } 
   mutate { 
     convert => ["index_date", "string"] 
     gsub => ["index_date", "T([\S\s]*?)Z", ""] 
     gsub => ["index_date", "-", "."] 
   }

   # 不需要的字段也可以删掉
   mutate {
     remove_field => "@version"
   }
   
   grok {
     match => {
        "message" => "%{TIMESTAMP_ISO8601:timestamp}\|%{LOGLEVEL:loglevel}\|%{DATA:thread}\|%{DATA:classpath}:%{INT:line}\|TID: %{DATA:traceId}\|%{GREEDYDATA:logdata}"
     } 
     #remove_field => ["index_date"]
   }
   
   date {
     match => ["[log][timeMillis]", "UNIX_MS"]
     target => "@timestamp"
   }
}

2.1.2.3 output

参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/output/index.html

output {
  if [type] == "microservice" {
    elasticsearch {
      # ES IP
      hosts => ["http://x.x.x.x:9200"]
      # 这样写,凌晨时写入的日志,还是写入到前一天的索引中
      #index => "log-microservice-%{+YYYY.MM.dd}"
      # UTC+8日期
      index => "log-microservice-%{index_date}"
      #user => "elastic"
      #password => "changeme"
    }
  }
}

2.2 logback

logback比log4j2麻烦多了,推荐使用log4j2。
实现logback传输日志到logstash服务器,主要有两种方法
1、socket通信,使用SocketAppender
先看一段话:
SocketAppender被设计为通过序列化ILoggingEvent实例把记录输出到远程实体。被序列化的事件的真实类型是LoggingEventVO, 它实现了ILoggingEvent接口。尽管如此, 就记录事件而言, 远程记录仍然是无损的。在接收和反序列化后, 事件像是从本地产生的一样被记录。运行在不同机器上的多个SocketAppender实例可以把各自的记录输出到一个格式固定的中央记录服务器。
SocketAppender不关联layout, 因为它把序列化的事件发送到远程服务器。SocketAppender运作在TCP层上, TCP层提供可靠、有序、流量控制的端到端的二进制流。因此, 如果远程服务器可访问, 则记录事件最终会到达那里。否则, 如果远程服务器关机或不可访问, 那么记录事件会被抛弃。当远程服务器恢复可用时, 会透明地继续传输事件。这种透明的重新连接是由一个连接器(connector)线程执行的, 它定时尝试连接服务器。
记录事件被本地TCP实现自动地缓冲。这意味着如果服务器连接很慢, 但快于客户端生成事件的速度, 那么客户端不会受网络连接慢的影响。但是如果网络连接慢于生成事件的速度, 那么客户端只能按网络速度执行。特别是在服务器宕机这种极端情况下, 客户端会被阻塞。
如果连接器线程仍然存在, 即使SocketAppender不再关联到任何logger, 也不会被垃圾回收。连接器线程只在当与服务器之间没有连接时存在。为避免这个垃圾回收问题, 你应当显式地关闭 SocketAppender。会创建/销毁很多SocketAppender实例的长期运行的程序应当注意这个垃圾回收问题。多数其他程序可以安全地忽略这个问题。如果宿主JVM在SocketAppender关闭之前退出了, 不管 SocketAppender被显式关闭还是交给垃圾回收, 都有可能在管道(pipe)里剩有一些未被传输的数据, 这些数据会丢失。为避免数据丢失, 一般在退出程序之前显式地调用SocketAppender的close()方法或调用LoggerContext的stop()方法就可以了。

SocketAppender发送的数据是序列化之后的数据,logstash的input缺少反序列化的插件。
以前有个logstash-input-log4j插件可以接收序列化数据,但该插件已经被放弃使用。


image.png

即使input使用codec => plain { charset => "ISO-8859-1" },接收到的数据也是存在乱码,而且会少接收很多日志,有用的日志没几条,乱七八糟的日志很多。

好在官方提供了SimpleSocketServer样例代码:https://github.com/qos-ch/logback/blob/master/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java
创建个java项目打包个jar包出来,运行后就可以接收socket发过来的日志。

使用命令:

java -jar SimpleSocketServer.jar port configurefile

官方给的configurefile例子:
https://github.com/qos-ch/logback/blob/master/logback-examples/src/main/resources/chapters/appenders/socket/server1.xml
https://github.com/qos-ch/logback/blob/master/logback-examples/src/main/resources/chapters/appenders/socket/server2.xml

如果不想自己动手去打包,我这里有已经打包好的包可以下载:https://download.csdn.net/download/kitter2008/85709895

2、使用LogstashTcpSocketAppender
这种方法需要在工程pom.xml文件里添加logstash依赖包:

 
    net.logstash.logback
    logstash-logback-encoder
    7.1.1

logback.xml配置中添加:


        
        127.0.0.1:1235
        
        
    

  
    
    
    

这种方式是简单很多,但需要修改项目的pom.xml文件。作为测试的我,不便于去修改pom.xml文件,万一出现冲突啥的,也麻烦,即使让开发去修改,也会存在很多服务器节点都需要修改。而修改logback.xml配置文件就简单多了,直接去托管服务器上修改配置文件即可。

所以,最终,我选择:使用SocketAppender通过TCP发送日志到SimpleSocketServer服务,SimpleSocketServer服务收到日志再转发给logstash,logstash存入到ES,kibana负责搜索日志。

2.2.1 logback.xml修改


        
        x.x.x.x
        9999
        10000
        false
    

2.2.2 SimpleSocketServer部署

java -jar logbacksocketserver-0.0.1-SNAPSHOT.jar 9999 server2.xml

2.2.3 logstash配置修改

2.2.2.1 input

input {
tcp {
    host => "0.0.0.0"
    port => "4568"
    mode => "server"
    type => "logbackserver"
    codec => json_lines
    add_field => {"appName" => "pg-grs"}
  }
}

2.2.2.2 filter

filter {
   ruby { 
    code => "event.set('index_date', event.get('@timestamp').time.localtime + 8*60*60)" 
   } 
   mutate { 
     convert => ["index_date", "string"] 
     gsub => ["index_date", "T([\S\s]*?)Z", ""] 
     gsub => ["index_date", "-", "."] 
   }
   
   grok {
     match => {
        # 同时匹配log4j2和logback,添加2条匹配规则
        "message" => ["%{TIMESTAMP_ISO8601:timestamp}\|%{LOGLEVEL:loglevel}\|%{DATA:thread}\|%{DATA:classpath}:%{INT:line}\|TID: %{DATA:traceId}\|%{GREEDYDATA:logdata}",
        "%{GREEDYDATA:logdata}"]
     }
   }
   
   date {
     match => ["[log][timeMillis]", "UNIX_MS"]
     target => "@timestamp"
   }
}

2.2.2.3 output

output {
if [type] == "logbackserver" {
    elasticsearch {
      hosts => ["http://x.x.x.x:9200"]
      index => "log-backserver-%{index_date}"
      codec => plain { charset => "UTF-8" }
    }
  }
}

2.3 Django的logging

实践证明,python-logstash和python3-logstash都不好使,最后选择了python-logstash-async。

2.3.1 安装python-logstash-async

pip install python-logstash-async

2.3.1 配置Django的settings.py

参考:https://python-logstash-async.readthedocs.io/en/latest/usage.html#usage-with-django

LOGGING = {
  ...
  'formatters': {
      ...
      'logstash': {
          '()': 'logstash_async.formatter.DjangoLogstashFormatter',
          'message_type': 'python-logstash',
          'fqdn': False, # Fully qualified domain name. Default value: false.
          'extra_prefix': 'dev', #
          'extra': {
              'application': PROJECT_APP,
              'project_path': PROJECT_APP_PATH,
              'environment': 'production'
          }
      },
  },
  'handlers': {
      ...
      'logstash': {
          'level': 'DEBUG',
          'class': 'logstash_async.handler.AsynchronousLogstashHandler',
          'formatter': 'logstash',
          'transport': 'logstash_async.transport.TcpTransport',
          'host': 'logstash.host.tld',
          'port': 5959,
          'ssl_enable': True,
          'ssl_verify': True,
          'ca_certs': 'etc/ssl/certs/logstash_ca.crt',
          'certfile': '/etc/ssl/certs/logstash.crt',
          'keyfile': '/etc/ssl/private/logstash.key',
          'database_path': '{}/logstash.db'.format(PROJECT_ROOT),
      },
  },
  'loggers': {
      'django.request': {
          'handlers': ['logstash'],
          'level': 'DEBUG',
          'propagate': True,
      },
  },
  ...
}

2.4 启动logstash服务

nohup ./bin/logstash -f /usr/logstash-8.2.2/config/tcp.conf &

提供重启脚本:

#!/bin/bash

logstashpid=`jps|grep -v grep|grep Logstash|awk '{print $1}'`
echo old logstash-pid=$logstashpid
echo kill $logstashpid
kill -9 $logstashpid
sleep 5s
echo start logstash
nohup /app/elk/logstash-8.2.2/bin/logstash -f  /app/elk/logstash-8.2.2/config/tcp_pg.conf >/dev/null 2>&1 &
sleep 5s
logstashpid=`jps|grep -v grep|grep Logstash|awk '{print $1}'`
echo new logstash-pid=$logstashpid

你可能感兴趣的:(使用ELK搭建日志服务器)