背景

在分布式应用的服务部署时,每个服务都会以集群的方式部署在多台机器上来防止单点故障,这种情况下多台机器都会有同一个服务的业务日志,只是每台机器都记录着该服务不同请求链路的业务日志,即使我们在日志中加入了调用链traceId,在业务异常发送需要查看业务日志时,若没有一个统一查看日志的平台系统,我们基本上都需要登陆到每一台机器上去看一遍这个服务的日志。效率极低,所以行业发展了一套比较成熟的日志采集观察的方案,简称ELK方案

Elastic-Stack是什么?

“ELK”是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch、Logstash 和 Kibana。Elasticsearch 是一个搜索和分析引擎。Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。

随着社区生态的发展,在2015年,Elastic公司向 ELK体系中加入了一系列轻量型的单一功能数据采集器,并把它们叫做 Beats,这个时候ELK 这个名称又要变了,把它叫做 BELK?BLEK?ELKB?当时的确有过继续沿用首字母缩写的想法。然而,对于扩展速度如此之快的堆栈而言,一直采用首字母缩写的确不是长久之计,所以最终确定以Elastic-Stack命名ELK的生态。

Elastic-Stack和ELK的区别

传统ELK采集日志的方案,我们一般会配套加入Kafka来完成整个日志采集的过程,整个日志采集管理的流程为:应用日志通过Application SDK的嵌入,将logger打印的日志都发送到Kafka,然后Logstash消费Kafka中的日志消息数据,并完成自定义的一些过滤规则,然后output到ES,最终在Kibana中就可以做日志查询了

上面说到ELK生态加入了Beats后,整个生态重新命名了,Beats是集合了多种单一用途数据采集器。它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据,截至当前(2021年7月)为止,官方共有7中Beats,分别是FileBeat(日志文件)、Metricbeat(指标)、Packetbeat(网络数据)、Winlogbeat(Windows事件日志)、Auditbeat(审计数据)、Heartbeat(运行时间监控)、Functionbeat(无需服务器的采集器),本文章主要介绍的FileBeat+ELK组合的日志采集方案,对其它数据采集器感兴趣的同学可以到Elastic官网学习,官方的文档还是比较详细的

FileBeat+ELK组合的日志采集方案中,Application不主动上报日志到ELK日志系统体,开发人员只需关注正常的业务开发以及日志打印,不关心日志怎么上传到ELK系统,FileBeat采集器根据配置指定的业务日志路径,主动采集解析业务日志内容上传到ELK系统,这里FileBeat其实就是业务部署机器上的一个Agent程序,这种方式的好处是做到了ELK系统和业务系统的完全解耦,对业务代码0侵入。下面就以从0到1部署一套Elastic-Stack采集JAVA应用日志的系统,让大家比较直观的了解这套体系的流程

一、ES集群部署

关于ES的集群部署,这里我选择使用docker-compose的方式部署,因为容器化管理应用是行业的趋势,且容器化管理服务部署简单、易复用,其它的好处就不再一一赘述,对docker技术不了解的同学建议可以学习一下。

Step1:环境配置

在机器安装好Docker后,需要修改一下Linux机器的句柄数,默认情况下一个进程只能打开65530个句柄,需要修改为262144,否则使用docker启用ES时会报错:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

临时修改方式:sysctl -w vm.max_map_count=262144

永久修改方式:vim /etc/sysctl.conf,添加vm.max_map_count=262144,然后重启机器

开放机器端口:9200、9300

Step2:编写docker-compose.yml文件

version: '3.8'
services:
  #集群其中一个节点的配置
  es01:
    #镜像来源
    image: elasticsearch:7.13.3
    container_name: es01
    restart: always
    environment:
      - node.name=es01
      - cluster.name=es-cluster
      #集群中其它节点的信息配置
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    #挂载配置,将ES数据持久化到本地机器磁盘,可以避免容器重启而丢失ES数据
    volumes:
      - /root/docker-compose-deploys/es/cluster/es01/data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    #声明ES集群使用的容器网络
    networks:
      - elastic
  es02:
    image: elasticsearch:7.13.3
    container_name: es02
    restart: always
    environment:
      - node.name=es02
      - cluster.name=es-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - /root/docker-compose-deploys/es/cluster/es02/data:/usr/share/elasticsearch/data
    networks:
      - elastic
  es03:
    image: elasticsearch:7.13.3
    container_name: es03
    restart: always
    environment:
      - node.name=es03
      - cluster.name=es-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - /root/docker-compose-deploys/es/cluster/es03/data:/usr/share/elasticsearch/data
    networks:
      - elastic
#创建一个ES集群使用的容器网络
networks:
  elastic:
    driver: bridge

Step3:启动服务命令

docker-compose up -d,启动后可以通过docker ps查看到3个ES容器,并通过docker logs -f <container_id>可以查看具体的容器日志,如下图

到这里,ES集群已经部署完成,还可以通过浏览器访问http://ip:9200/的方式验证ES是否准备就绪

二、Logstash部署

同理,logstash我也使用docker-compose的方式部署

Step1:docker-compose.yml的配置

version: '3.8'
services:
  logstash:
    image: logstash:7.13.3
    restart: always
    container_name: logstash
    #配置挂载,容器加载使用本地准备好的配置文件
    volumes:
      - /root/docker-compose-deploys/logstash/pipeline/:/usr/share/logstash/pipeline/
      - /root/docker-compose-deploys/logstash/conf/logstash.yml:/usr/share/logstash/config/logstash.yml
    ports:
      - 5000:5000
      - 5044:5044
      - 9600:9600
    networks:
      - elknet
#使用步骤一中创建的ES容器网络,这里需要注意,网络名通过外部执行命令docker network ls获得
networks:
  elknet:
    external:
      name: cluster_elastic

Step2:准备步骤1中加载的2个配置文件:

conf/logstash.yml,这个配置文件指定logstash输出的ES地址

http.host: "0.0.0.0"
path.config: /usr/share/logstash/pipeline
xpack.monitoring.elasticsearch.hosts: http://192.168.2.146:9200

pipeline/logstash.conf,这个配置文件配置了logstash的过滤规则、输入输出方向等,关于filter配置的作用是我的自定义配置,后面会说明为什么我需要这样配置,若不理解这里可以先不关注这个细节

input {
    beats {
	port => 5044
    }
}
filter {
 ruby {
     code => "
	log_path = event.get('log')['file']['path']
        event.set('service_name', log_path.split('/')[-2])
     "
 }
 mutate {
    add_field => {
	"serviceName" => "%{service_name}"
    }
 }
}
output {
  elasticsearch {
    hosts => ["192.168.2.146:9200"]
  }
}

Step3:启动Logstash服务

docker-compose up -d,同样,可以通过docker logs -f 观察容器日志是否有异常判断服务是否正常启动

三、Kibana部署

Step1:准备docker-compose.yml配置文件,因为使用的是5601端口,所以也要开放机器的5601端口

version: '3.8'
services:
 kibana:
    image: kibana:7.13.3
    container_name: kibana
    restart: always
    #挂载使用本地准备的配置文件
    volumes:
      - /root/docker-compose-deploys/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml
    ports:
      - 5601:5601
    networks:
      - cluster_elastic
#使用步骤一中创建的ES容器网络,这里需要注意,网络名通过外部执行命令docker network ls获得
networks:
  cluster_elastic:
    external: true

Step2:准备kibana本地的配置文件kibana.yml,配置指定了数据来源ES的地址

server.name: "kibana"
server.host: "0.0.0.0"
server.port: "5601"
elasticsearch.hosts: "http://192.168.2.146:9200"

Step3:启动Kibana服务

docker-compose up -d,同样,可以通过docker logs -f 观察容器日志是否有异常判断服务是否正常启动,服务正常的情况下通过浏览器访问:http://ip:5601/app/kibana,可以跳转到kibana的使用界面

四、fileBeat部署

关于filBeat的部署,和前三者不太一样,虽然也可以通过docker的方式部署,不过这里我选择使用非容器化的形式部署,下载官方的tar包,然后修改filebeat.yml的配置文件进行部署,因为docker方式的部署对于MacOS系统的用户不太友好,而本篇文章的案例,我将通过本地idea的启动java应用程序,将应用日志写到MacOS本地,然后再使用fileBeat采集MacOS本地的java日志,所以才选择官方的安装包方式部署,关于docker方式的部署,也可以参考官网的文档进行部署,只需要将本片文章的配置文件换成容器启动时挂载的配置文件即可

Step1:下载解压官方的安装包后,主要是修改解压目录下的filebeat.yml这个配置文件,主要修改的配置如下

# ============================== Filebeat inputs ===============================
filebeat.inputs:
- type: log
  enabled: true
  #这里配置fileBeat读取日志文件的路径,这个路径也是java应用程序logback配置中日志输出的路径,/*/*.log中的第一个*是服务名
  paths:
    - /Users/hzk/Desktop/ideaTmpLogs/*/*.log

  #这里配置是处理多行日志合并的规则,因为对于Java异常堆栈的日志,如果没有配置这里,则堆栈的每一行将作为一个消息发送到ES
  #而这里因为我的java应用日志,每一条日志的开头都是时间加[的形式,如: 2021-07-25 10:20:31.316 [
  #所以该配置的意思是,正常匹配日志文件的数据,每次遇到新匹配成功的数据时,则从新匹配成功的位置到上一次匹配成功的位置之间的数据作为1条数据。这样保证了异常堆栈日志采集的完整性
  multiline.type: pattern
  multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \['
  multiline.negate: true
  multiline.match: after

# ------------------------------ Logstash Output -------------------------------
output.logstash:
  # The Logstash hosts
  hosts: ["192.168.2.146:5044"]

关于multiline的更多配置用法,可参考官网链接:https://www.elastic.co/guide/en/beats/filebeat/7.13/multiline-examples.html

Step2:启动fileBeat服务

转到fileBeat的解压目录,以后台进程的形式启动:nohup ./filebeat -e &

启动后,可以通过查看nohup.out观察进程日志,到这里整个ELK+FileBeat就已经部署完成,准备就绪了!

五、使用验证

这里需要说明一下的是,ELK第一次安装完后,还没有数据,所有也没有建立任何ES索引,我们在第一次跑完程序采集到日志到ES后,需要手动在Kibana创建索引,后续才可以使用这个索引查询日志

Step1:启动应用程序,执行一个异常流,打印异常堆栈信息,观察本地日志文件如下

Step2:在Kibana中查看日志,展开详情可以看到完整的异常堆栈信息

至此,整个Elastic-Stack日志采集系统已经能够正常使用,并能够准确的采集我们所需要的日志信息

Step3:其它说明

在上面配置Logstash的Filter的时候,我自定义给条日志数据添加了一个serviceName的属性,这是因为我需要在kibana查看日志的时候区分当前这条日志是属于哪一个服务的,因为我的日志路径规范为/xxx/serviceName/*.log,所以我需要截取日志采集路径的serviceName这一层文件夹的名称作为服务名遍历添加到ES日志数据的属性中,这也是为什么logstash我的filter需要配置split切割日志路径

六、总结

关于Elastic-Stack的日志采集系统的部署以及使用暂时就介绍到这里,这里其实有个点时需要考虑的,就是按照目前的配置方式,所有服务只要日志打印规范,切部署机器中安装好fileBeat代理,都可以动态的接入到ELK日志采集系统中,而无需在每次接入时重启Logstash或者ES服务,但这也存在一个问题,就是所有服务的日志都将在一个ES索引上,随着日后服务的递增以及日志量的递增,单一ES索引的数据量将达到巨大的情况下,是否会影响ES的查询检索效率,如果会影响我们是否应该考虑按照某一维度拆分服务群,不同的服务群使用不同的ES索引,避免单一索引数据量过大的情况,如何拆分索引这个还得根据不同业务团队的实际业务场景来决定,感兴趣的同学可以研究一下!

发表评论

您的电子邮箱地址不会被公开。