日志分析平台建设方案
1.建设原因
- 日志文件分散在各个应用服务器,人员需要远程登录才能查看日志,不利于服务器安全管控,加大服务器的风险
- 各服务器日志配置不统一,分布杂乱,需要进行规范与管理
- 日志文件信息默认显示不够简洁直观,不利于问题的快速定位,降低排错效率
2.建设目标
- 无需登录各线上服务器就能查看日志
- 统一规范日志的配置和输出格式
- 实时的将日志文件从服务器中迁出
- 提供友好的日志的检索和统计分析的平台
3.平台选择
为了解决以上的日志问题,经过评估后,选择了ELK作为日志数据管理解决方案。它主要包括elasticsearch,logstash,kibana三个系统。由于logstash程序在启动时需要消耗较大的资源,在经过研究了解后,发现filebeat作为日志采集组件,因其使用go语言编写的特性,其性能明显强于logstash;还有部署简单,对接方便等优点。因此选用filebeat作为各客户端的日志采集工具,logstah仅用来过滤采集的日志并输出到es。
为了使日志的采集与输出更加平滑可靠,选择了redis作为数据的缓存,且elk对redis的兼容性也很好。因此日志平台服务选择使用ELK+reds+filebeat来搭建
4.系统架构
- 最左边的是各业务的服务器,上面安装filebeat做日志采集,同时把采集的日志发送给redis服务
- logstash服务会实时的去redis服务里拉数据,并经过过滤与修改转发给es服务
- es会将收到的数据写入磁盘持久化并建立索引库
- kibana主要协调es,处理数据的检索请求和数据展示
5.实施方案
系统配置
1.软件
- elasticsearch-7.1.1-x86_64
- logstash-7.1.1-x86_64
- kibana-7.1.1-x86_64
- filebeat-7.1.1-x86_64
- jdk-1.8.0
- redis-2.8.17
2.硬件
两台linux服务器,centos7系统
配置:内存:16GB,硬盘:200G,CPU:4核8线程 网络:50M带宽;
系统搭建
- 收集192.168.10.36服务器中/usr/local/nginx/log目录下的access.log和error.log
- 收集192.168.10.72服务器中/usr/local/nginx/log目录下的access.log和error.log
redis
编辑安装脚本,变量部分根据实际需求更改
[root@localhost ~]# cat redis_install.sh
#!/bin/bash
RD_SRC_PATH=/root
RD_TAR_NAME=redis-2.8.17.tar.gz
RD_INSTALL_NAME=redis-2.8.17
RD_INSTALL_PATH=/usr/local
PORT=6378
PASSWD=123
yum -y install gcc gcc++
if [ $? -ne 0 ];then
echo "yum failed"
exit 1
fi
mv ${RD_SRC_PATH}/${RD_TAR_NAME} ${RD_INSTALL_PATH}
cd ${RD_INSTALL_PATH}
tar xf ${RD_TAR_NAME}
cd ${RD_INSTALL_NAME}
make
##修改配置文件
sed -ri "s#(daemonize ).*#\1yes#g" ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.conf
sed -ri "s#(pidfile ).*#\1/var/run/redis_${PORT}.pid#g" ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.conf
sed -ri "s#^(port).*#\1 6378#g" ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.conf
sed -ri "s#^(logfile).*#\1 ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.log#g" ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.conf
echo "requirepass $PASSWD" >> ${RD_INSTALL_PATH}/${RD_INSTALL_NAME}/redis.conf
cp -a /root/redis /etc/rc.d/init.d
chkconfig --add redis
编写自启动脚本
[root@localhost ~]# cat redis
#!/bin/bash
#chkconfig: 2345 80 80
#description:redis
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
REDISPORT=6378
PASSWORD=123
REDISPATH=/usr/local/redis-2.8.17/src
CONFSPATH=/usr/local/redis-2.8.17
EXEC=${REDISPATH}/redis-server
CLIEXEC=${REDISPATH}/redis-cli
PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="${CONFSPATH}/redis.conf"
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
echo "Starting Redis server..."
$EXEC $CONF &
fi
if [ "$?"="0" ]
then
echo "Redis is running..."
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo "Stopping ..."
$CLIEXEC -p $REDISPORT -a "$PASSWORD" shutdown
sleep 2
while [ -x /proc/${PID} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
echo "Redis stopped"
fi
;;
restart)
${0} stop
${0} start
;;
*)
echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
;;
esac
给脚本赋予执行权限,并运行脚本
[root@localhost ~]# chmod +x redis redis_install.sh
[root@localhost ~]# bash redis_install.sh
启动redis服务,若有报警建议根据报警信息进行对应操作,否则容易导致数据丢失
[root@localhost ~]# service redis start
[4789] 31 May 11:12:59.355 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
es
yum -y install java-1.8.0
下载es安装包
[root@localhost local]# wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.1.1-linux-x86_64.rpm
[root@localhost local]# yum -y localinstall elasticsearch-7.1.1-linux-x86_64.rpm
更改以下文件,以免启动时报错
[root@localhost local]# tail /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65535
[root@localhost local]# sysctl -w vm.max_map_count=262144
更改es配置文件,指定host为本地主机ip,由于搭建为单机,因此指定一个node号即可
注意:有时指定本地ip为host时,kibana会识别不到es的证书,导致kibana打不开(网页报错:kibana is not ready)。建议在安装es和kibana时,先不要更改配置,待kibana连接成功,可访问时,再修改相应的配置,最后重启。
[root@elk ~]# cat /usr/local/elasticsearch-7.1.1/config/elasticsearch.yml |grep -Ev "^#"
network.host: 192.168.10.36
http.port: 9200
cluster.initial_master_nodes: ["node-1"](集群初始主节点,有多少个节点就分配几个node号)
切换到elk用户 启动es服务
[root@elk ~]# su - elk -s /bin/bash
-bash-4.2$ /usr/local/elasticsearch-7.1.1/bin/elasticsearch -d
查看服务是否启动成功
[root@elk ~]# ss -antl|grep 9
LISTEN 0 128 ::ffff:192.168.10.36:9200 :::*
LISTEN 0 128 ::ffff:192.168.10.36:9300 :::*
创建配置文件,使es服务开机自启
[root@elk ~]# cat /etc/systemd/system/elasticsearch.service
[Unit]
Description=elasticsearch
[Service]
User=elk
LimitNOFILE=100000
LimitNPROC=100000
ExecStart=/usr/local/elasticsearch-7.1.1/bin/elasticsearch -d
[Install]
WantedBy=multi-user.target
[root@elk ~]# systemctl enable elasticsearch
Created symlink from /etc/systemd/system/multi-user.target.wants/elasticsearch.service to /etc/systemd/system/elasticsearch.service.
filebeat
- 192.168.10.36服务器
客户端安装filebeat
[root@wind ~]# yum -y localinstall filebeat-7.1.1-x86_64.rpm
配置yml文件
[root@wind ~]# cat /etc/filebeat/filebeat.yml
指定日志的输入源,即需要收集的日志路径
filebeat.inputs:
- type: log #指定类型为log
enabled: true #启用该配置
paths:
- "/usr/local/nginx/log/access.log" #日志路径
tags:
- "scgj36_nginx_access" #指定日志的标签,达到不同的日志进行分类的效果
fields:
list: "scgj36_nginx_access" #自定义变量,为后面的logstash日志过滤提供筛选条件
- type: log
enabled: true
paths:
- "/usr/local/nginx/log/error.log"
tags:
- "scgj36_nginx_error"
fields:
list: "scgj36_nginx_error"
output.redis: #指定输出源,即将收集到的日志数据传到何处,此处为redis
hosts: "192.168.10.36:6378" #指定redis的ip和端口
key: "%{[fields.list]}" #指定将日志数据存放在哪个key中,此处运用了变量,根据 list的值来决定key的值
password: "123" #redis验证密码
启动服务
[root@wind ~]# systemctl enable filebeat && systemctl start filebeat
- 192.168.10.72服务器
[root@lianxi2 ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- "/usr/local/nginx/log/access.log"
tags:
- "nmgcw72_nginx_access"
fields:
list: "nmgcw72_nginx_access"
- type: log
enabled: true
paths:
- "/usr/local/nginx/log/error.log"
tags:
- "nmgcw72_nginx_error"
fields:
list: "nmgcw72_nginx_error"
output.redis:
hosts: "192.168.10.36:6378"
key: "%{[fields.list]}"
password: "123"
logstash
下载rpm包,并安装
[root@localhost ~]# yum -y install logstash-7.1.1.rpm
创建logstash配置文件
[root@localhost ~]# cat /etc/logstash/conf.d/logstash_nginx.conf
指定redis为输入源
input {
redis {
host => "192.168.10.36" #指定redis的ip
type => "scgj36_nginx_access" #指定type类型,对不同日志做区分
port => "6378" #redis端口号
key => "scgj36_nginx_access" #key值
data_type => "list" #数据的类型,此处选择list
password => "123" #redis的密码
}
redis {
host => "192.168.10.36"
type => "scgj36_nginx_error"
port => "6378"
key => "scgj36_nginx_error"
data_type => "list"
password => "123"
}
redis {
host => "192.168.10.36"
type => "nmgcw72_nginx_access"
port => "6378"
key => "nmgcw72_nginx_access"
data_type => "list"
password => "123"
}
redis {
host => "192.168.10.36"
type => "nmgcw72_nginx_error"
port => "6378"
key => "nmgcw72_nginx_error"
data_type => "list"
password => "123"
}
}
对收集到的日志做过滤
#如果日志的类型是以access结尾的,则对日志进行以下过滤
filter {
if [type] =~ "access$" {
#过滤的正则规则为%{COMBINEDAPACHELOG}(此规则是软件自带的规则,使用find / -name patterns/httpd可以找到路径)
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
#配置geoip,此插件可以将ip地址转换为对应的地理坐标
geoip {
source => "clientip"
target => "geoip"
database => "/usr/share/elasticsearch/modules/ingest-geoip/GeoLite2-City.mmdb"
add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ]
add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ]
}
#对提取出来的日志进行修改
mutate {
convert => [ "[geoip][coordinates]", "float" ]
convert => [ "response","integer" ]
convert => [ "bytes","integer" ]
remove_field => "message"
remove_field => "timestamp"
}
if [user_agent] =~ ".*10\.0\;.*"{
mutate {
replace => { "user_agent" => "win10" }
}
}
if [user_agent] =~ ".*Apache.*"{
mutate {
replace => { "user_agent" => "Linux" }
}
}
}
#当type不为access结尾时(即错误日志),按以下正则匹配
else{
grok {
match => { "message" => "(?<timestamp>%{YEAR}[./-]%{MONTHNUM}[./-]%{MONTHDAY}[- ]%{TIME}) \[%{LOGLEVEL:severity}\] %{POSINT:pid}#%{NUMBER}: %{GREEDYDATA:errormessage}(?:, client: (?<clientip>%{IP}|%{HOSTNAME}))(?:, server: %{IPORHOST:server}?)(?:, request: %{QS:request})?(?:, upstream: (?<upstream>\"%{URI}\"|%{QS}))?(?:, host: %{QS:request_host})?(?:, referrer: \"%{URI:referrer}\")?" }
}
#geoip插件
geoip {
source => "clientip"
target => "geoip"
database => "/usr/share/elasticsearch/modules/ingest-geoip/GeoLite2-City.mmdb"
add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ]
add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ]
}
#修改过滤后的日志
mutate {
convert => [ "[geoip][coordinates]", "float" ]
convert => [ "response","integer" ]
convert => [ "bytes","integer" ]
remove_field => "message"
remove_field => "timestamp"
}
}
}
指定输出源,按照type的不同,输出不同的索引(idnex)给es,且索引按日期动态生成
output {
if [type] == "scgj36_nginx_access"{
elasticsearch {
hosts => "192.168.10.36:9200"
index => "yl_scgj_access_%{+YYYY.MM.dd}"
}
}
if [type] == "scgj36_nginx_error"{
elasticsearch {
hosts => "192.168.10.36:9200"
index => "yl_scgj_error_%{+YYYY.MM.dd}"
}
}
if [type] == "nmgcw72_nginx_access"{
elasticsearch {
hosts => "192.168.10.36:9200"
index => "yl_nmgcw_access_%{+YYYY.MM.dd}"
}
}
if [type] == "nmgcw72_nginx_error"{
elasticsearch {
hosts => "192.168.10.36:9200"
index => "yl_nmgcw_error_%{+YYYY.MM.dd}"
}
}
}
logstash.yml修改一下bind IP,方便后期直接输出日志至logstash
[root@localhost ~]# cat /etc/logstash/logstash.yml|grep -Ev ^#
http.host: "192.168.10.36"
启动logstash
[root@localhost ~]# systemctl enable logstash
[root@localhost ~]# systemctl start logstash
logstahs配置文件大致解释:
Logstash 分为 Input、Output、Filter、Codec 等多种plugins。
Input:数据的输入源也支持多种插件,如elk官网的beats、file、graphite、http、kafka、redis、exec等等等、、、
Output:数据的输出目的也支持多种插件,如本文的elasticsearch,当然这可能也是最常用的一种输出。以及exec、stdout终端、graphite、http、zabbix、nagios、redmine等等、、、
Filter:使用过滤器根据日志事件的特征,对数据事件进行处理过滤后,在输出。支持grok、date、geoip、mutate、ruby、json、kv、csv、checksum、dns、drop、xml等等、、
Codec:编码插件,改变事件数据的表示方式,它可以作为对输入或输出运行该过滤。和其它产品结合,如rubydebug、graphite、fluent、nmap等等。
具体以上插件的细节可以去官网,介绍的挺详细的。下面说下该篇中的配置文件的含义:
filter段:
grok:数据结构化转换工具
match:匹配条件格式,将nginx日志作为message变量,并应用grok条件NGINXACCESS进行转换
geoip:该过滤器从geoip中匹配ip字段,显示该ip的地理位置
source:ip来源字段,这里我们选择的是日志文件中的clientip
target:指定插入的logstash字断目标存储为geoip
database:geoip数据库的存放路径
add_field: 增加的字段,坐标经度
add_field: 增加的字段,坐标纬度
mutate: 数据的修改、删除、类型转换
convert: 将坐标转为float类型
convert: http的响应代码字段转换成 int
convert: http的传输字节转换成int
remove_field: 移除message和timestamp 的内容,因为数据已经过滤了一份,这里不必在用到该字段了。不然会相当于存两份
if语句:条件语句,由于在后续的可视化中,需要对访问客户的操作系统进行统计,而user_agent字段有很长一段,不利于图形展示,因此将user_agent字段提取出来后修改为只包含操作系统信息的简短信息
*output段:
elasticsearch:输出到es中
host: es的主机ip+端口或者es 的FQDN+端口
index: 为日志创建索引,这里也就是kibana那里添加索引时的名称
kibana
安装kibana
[root@localhost ~]# yum -y localinstall kibana-7.1.1-x86_64.rpm
修改配置文件
[root@localhost ~]# cat /etc/kibana/kibana.yml |grep -E "host:|hosts:"
server.host: "192.168.10.36"
elasticsearch.url: ["http://192.168.10.36:9200"]
启动服务
[root@localhost ~]# systemctl enable kibana && systemctl start kibana
一、Kibana之Visualize 功能
在首页上Visualize 标签页用来设计可视化图形。你可以保存之前在discovery中的搜索来进行画图,然后保存该visualize,或者加载合并到 dashboard 里。一个可视化可以基于以下几种数据源类型:
一个新的交互式搜索
一个已保存的搜索
一个已保存的可视化
下面是kibana自带的几种visualize类型
类型 用途
Area chart 用区块图来可视化多个不同序列的总体贡献。
Data table 用数据表来显示聚合的原始数据。其他可视化可以通过点击底部的方式显示数据表。
Line chart 用折线图来比较不同序列。
Markdown widget 用 Markdown 显示自定义格式的信息或和你仪表盘有关的用法说明。
Metric 用指标可视化在你仪表盘上显示单个数字。
Pie chart 用饼图来显示每个来源对总体的贡献。
Tile map 用瓦片地图将聚合结果和经纬度联系起来。
Vertical bar chart 用垂直条形图作为一个通用图形。
画图步骤:Visualize–>选择类型–>搜索栏筛选结果1–> 在2工具栏内选择聚合构建器–>3实时预览–>将该图保存。
下面主要说下http 服务器的一些图的画法。至于其它的分析,还有待学习。
二、HTTP dashboard的一步步构建
1.字段以及字段所对应的值,如下:
2.Area chart类型,统计网站各个时间段的请求响应传输量分布
之后保存为visualize,这些图放在dashboard上会”实时“刷新的。
3.Data table类型。统计访问请求页面TOP10
4.Line chart 统计ip在某个时间段的点击量
- Gauge 查看网站来访者数量(如果校园网的话或者代理的话,可能反映的不太真实)
6.Pie chart 大饼,统计响应码所回应的请求页面分布图
7.Coordinate Map 网站访问者的ip 归属地理位置
8.Vertical bar chart ,以客户端使用的代理类型为区别,查看某时间段响应的响应代码
9.Pie 客户端操作系统扇形图
四、最终的dashboard
图1:
图2:
6.优化方案
logstash优化相关配置
- pipeline线程数,官方建议等于cpu的内核数
默认配置:pipeworkers:2
可优化为:pipeline.workers:cpu内核数
- 实际output时的线程数
默认配置:pipeline.output.workers:1
可优化为:pipeline.output.workers:不超过pipeline线程数
- 每次发送的事件数
默认配置:pipeline.batch.size:125
可优化为:pipeline.batch.size:1000
- 发送延迟
默认配置:pipeline.batch.delay:5
可优化为:pipeline.batch.size:10
通过设置-w参数指定pipeline worker的数量,也可直接修改配置文件logstash.yml.这会提高filter和output的线程数,若需要的话,将其设置为cpu核心数的几倍也是可以的,线程在i/o上是空闲的
默认每个输出在一个pipline worker线程上活动,可以在输出output中设置workers设置,不要将该值设置大于pipeline worker数。
还可以设置输出的batch_size数,例如ES输出和batch size一致
filter设置 multiline后,pipeline worker会自动变为1,因此建议在filebeat中使用multiline
logstash是一个基于java开发的程序,需要运行在Jvm中,可以配置jvm.option来针对jvm进行设定。比如内存的最大最小,垃圾清理机制等等。jvm的内存分配不能太大也不能太小,太大会拖慢系统,太小导致无法启动。推荐如下:
-Xms1g #最小使用内存
-Xmx1 g #最大内存
redis优化
- filebeat可以直接输入到logstash,但logstash没有存储功能,如果需要重启需要先停所有连入的beat,再停logstash,造成运维麻烦;另外如果logstash发生异常则会丢失数据;引入redsi作为数据缓冲池,当logstash异常停止后可以从redis的客户端看到缓存的数据
- redis可以使用list或发布订阅存储模式
- redis的bind改为0.0.0.0 #不要监听本地端口
- redis添加密码,以安全运行
- save “” appendonly no #只做队列,没必要持久存储,把所有持久化功能关掉(快照和追加式文件),性能更好
- 把内存的淘汰策略关掉,把内存空间最大化;maxmemory 0
es节点优化
-
服务器硬件配置,Os参数
- /etc/sysctl.conf
vm.swappiness = 1 #ES 推荐将此参数设置为 1,大幅降低 swap 分区的大小,强制最大程度的使用内存,注意,这里不要设置为 0, 这会很可能会造成 OOM ② net.core.somaxconn = 65535 #定义了每个端口最大的监听队列的长度 ③ vm.max_map_count= 262144 #限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。当VMA 的数量超过这个值,OOM ④ fs.file-max = 518144
sysctl -p 生效
- limits.conf设置
vim /etc/security/limits.conf elasticsearch soft nofile 65535 elasticsearch hard nofile 65535 elasticsearch soft memlock unlimited elasticsearch hard memlock unlimited
- 为了使以上参数永久生效,还要设置两个地方
vim /etc/pam.d/common-session-noninteractive vim /etc/pam.d/common-session 添加如下属性: session required pam_limits.so 需重启后生效
-
elasticsearch 中的JVM配置文件
-Xms2g
-Xmx2g
① 将最小堆大小(Xms)和最大堆大小(Xmx)设置为彼此相等。
② Elasticsearch可用的堆越多,可用于缓存的内存就越多。但请注意,太多的堆可能会使您长时间垃圾收集暂停。
③ 设置Xmx为不超过物理RAM的50%,以确保有足够的物理内存留给内核文件系统缓存。
④ 不要设置Xmx为JVM用于压缩对象指针的临界值以上;确切的截止值有所不同,但接近32 GB。不要超过32G,如果空间大,多跑几个实例,不要让一个实例太大内存
- elasticsearch 配置文件优化参数
① vim elasticsearch.yml
bootstrap.memory_lock: true #锁住内存,不使用swap
#缓存、线程等优化如下
bootstrap.mlockall: true
transport.tcp.compress: true
indices.fielddata.cache.size: 40%
indices.cache.filter.size: 30%
indices.cache.filter.terms.size: 1024mb
threadpool:
search:
type: cached
size: 100
queue_size: 2000
② 设置环境变量
vim /etc/profile.d/elasticsearch.sh export ES_HEAP_SIZE=2g #Heap Size不超过物理内存的一半,且小于32G