日志分析作为掌握业务情况的一个重要手段,目前使用最多最成熟的莫过于ELK方案,其中也有各种搭配组合,像rsyslog->ES->kibana、rsyslog->Redis->Logstash->ES->kibana、rsyslog->kafka->Logstash->ES->kibana等等,复杂点的有spark的引用。每种方案适合不同的应用场景,没有好坏,我的集群用的是rsyslog->kafka->Logstash->ES->kibana和rsyslog->rsyslog中继->kafka->Logstash->ES->kibana方案,目前4台ES每天索引10多亿条日志,包含nginx、redis、php等,运行比较健壮,每条日志的索引在10个字段左右,每天Primary Shard的索引量在500个G左右,考虑到性能和日志性质,我们没要复制分片,日志保留7天。
总结一下,其实就是采集->清洗->索引->展现几个环节,再去考虑各环节中缓存、队列的使用。下面介绍一下此方案集群的搭建和配置。希望对同行有所帮助,也算我积的福德,在ELK探索过程中多谢远川和冯超同学的奉献交流。
一、采集(使用rsyslog)
http://www.rsyslog.com/rhelcentos-rpms/
将yum源配置好后:
yum
install
rsyslog
yum
install
rsyslog-kafka
安装好后对应rsyslog的配置文件如下:
module(load=
"imfile"
)
module(load=
"omkafka"
)
$PreserveFQDN on
main_queue(
queue.workerthreads=
"10"
# threads to work on the queue
queue.dequeueBatchSize=
"1000"
# max number of messages to process at once
queue.size=
"50000"
# max queue size
)
##########################nginx log################################
$template nginxlog,
"%$myhostname%`%msg%"
if
$syslogfacility-text ==
'local6'
then
{
action(
broker=[
"10.13.88.190:9092"
,
"10.13.88.191:9092"
,
"10.13.88.192:9092"
,
"10.13.88.193:9092"
]
type
=
"omkafka"
topic=
"cms-nginx"
template=
"nginxlog"
partitions.auto=
"on"
)
stop
}
############################redis log#########################
$template redislog,
"%$myhostname%`%msg%"
ruleset(name=
"redis7215-log"
) {
action(
broker=[
"10.13.88.190:9092"
,
"10.13.88.191:9092"
,
"10.13.88.192:9092"
,
"10.13.88.193:9092"
]
type
=
"omkafka"
topic=
"redis-log"
template=
"redislog"
partitions.auto=
"on"
)
}
input(
type
=
"imfile"
File=
"/data1/ms/log/front/redis7215.log"
Tag=
""
ruleset=
"redis7215-log"
freshStartTail=
"on"
#start tailf
reopenOnTruncate=
"on"
#Truncate reopen
)
input(
type
=
"imfile"
File=
"/data1/ms/log/front/redis7243.log"
Tag=
""
ruleset=
"redis7215-log"
freshStartTail=
"on"
reopenOnTruncate=
"on"
)
############################php curl log#############################
$template phpcurl-log,
"%$myhostname%`%msg%"
ruleset(name=
"phpcurl-log"
) {
action(
broker=[
"10.13.88.190:9092"
,
"10.13.88.191:9092"
,
"10.13.88.192:9092"
,
"10.13.88.193:9092"
]
type
=
"omkafka"
topic=
"phpcurl-log"
template=
"phpcurl-log"
partitions.auto=
"on"
)
}
input(
type
=
"imfile"
File=
"/data1/ms/log/php_common/php_slow_log"
Tag=
""
ruleset=
"phpcurl-log"
freshStartTail=
"on"
reopenOnTruncate=
"on"
)
为了避免在日志发送错误时,丢在message日志里,瞬间将磁盘占满,同时配置丢弃策略
*.info;mail.none;authpriv.none;
cron
.none;local6.none
/var/log/messages
目前收集了nginx、redis、php curl三种日志,说一下收集方案。
1、对于nginx
方案1:采用nginx的rsyslog模块将日志打到local6,对应nginx的配置如下
##########elk#############################
access_log syslog:local6 STAT;
然后通过如上rsyslog的配置,将日志直接入kafka队列,kafka集群是4个broker。
方案2:线上还有另一个传输方案,rsyslog设置一个中继,通过udp的方式将日志传到中继的rsyslog,由中继rsyslog入kafka,这么做的目的是方便了管理,当时还有个考虑是udp不会堵,但经过多轮测试后,nginx的rsyslog模块也是很健壮,不会堵的。
2、对于redis、php curl的日志
通过rsyslog的imfile模块,直接对文件监听,配置见上面的rsyslog配置,在日志轮转时通过超链接的方式进行新文件的连接,对应的超连接计划任务如下,每天0点5分执行:
5 0 * * * root sh
/usr/local/script/php_slow_log
.sh &>
/dev/null
对应的php_slow_log.sh的脚本如下:
#!/bin/bash
DATE=`
date
+%F`
ln
-sf
/data1/ms/log/php_common/curl-
$DATE
/data1/ms/log/php_common/php_slow_log
备注:
a、rsyslog向4个kafka的broker推送消息时,是以轮训的方式;
b、rsyslog通过udp或tcp向外转发日志时,会默认加上时间、主机名、主机ip的属性。
二、队列(kafka+zookeeper)
队列用的是kafka,kafka集群使用zookeeper管理,我们用了4台服务器混装了4个kafka和3个zookeeper,kafka和zookeeper的安装地址如下:
http://kafka.apache.org/downloads 注意:下载Binary downloads版本,别下错了,解压后就能用
http://zookeeper.apache.org/ 注意:安装过程很简单,按照文档来即可,不在说明
1、关于kafaka
a、配置比较简单,基本默认即可,常调整的配置项如下:
配置文件:server.properties
broker.
id
=190
#id
num.partitions=20
#默认kafka的partion数量
log.
dirs
=
/data1/kafka-logs
#日志文件存放目录
log.retention.hours=3
#日志保留时间长短
zookeeper.connect=10.13.88.190:2181,10.13.88.191:2181,10.13.88.192:2181
#zookeeper指定
delete.topic.
enable
=
true
#topic是可以删除的
b、安装后测试(假设kafka和zookeeper都装了):
开两个终端,两个终端分别运行如下命令
启动:.
/bin/kafka-server-start
.sh
/usr/local/kafka/config/server
.properties &
终端1:.
/bin/kafka-console-producer
.sh --broker-list localhost:9092 --topic
test
终端2:.
/bin/kafka-console-consumer
.sh --zookeeper localhost:2181 --from-beginning --topic
test
注意两个终端的topic要一个名字,这时你在终端1输入任何数据,在终端2是同步的,证明你安装成功。
c、kafka常用管理命令
创建topic:.
/bin/kafka-topics
.sh --create --topic
test
--replication-factor 1 --partitions 32 --zookeeper localhost:2181
删除topic:.
/bin/kafka-topics
.sh --delete --topic
test
--zookeeper localhost:2181
查看topic列表:.
/bin/kafka-topics
.sh --list --zookeeper localhost:2181
查看某个topic详细:.
/bin/kafka-topics
.sh --describe --topic
test
--zookeeper localhosts:2181
监控某个topic的消费:.
/bin/kafka-console-consumer
.sh --zookeeper localhost:2181 --topic
test
指定消费组查看消费情况:.
/bin/kafka-consumer-offset-checker
.sh --zookeeper localhost:2181 --group
test
备注:topic下partitions的数量决定了并发消费的数量,在设置上要根据消息的QPS和硬盘情况合理配置。
2、关于zookeeper
a、配置比较简单,大多数默认项,最好奇数个,半数以上zookeeper存活可用
配置文件:zoo.cfg
dataDir=
/data1/zookeeper
server.1=10.13.88.190:3888:4888
server.2=10.13.88.191:3889:4888
server.3=10.13.88.192:3889:4888
注意:要在数据目录手动建立myid,myid的值是server后面的数字,数字是有范围限制的1~255
b、zookeeper的常用管理命令
zookeeper我主要是看下它的整体状态,写了个简单脚本获取zookeeper的状态,执行结果如下:
脚本内容如下:
#!/bin/sh
#writer:gaolixu
[ -z $1 ] &&
echo
"Please specify zoo.cfg like /usr/local/zookeeper/conf/zoo.cfg "
&&
exit
cat
$1 |
grep
"^server"
|
awk
-F
'[:|=]'
'{print $2}'
|
while
read
line
do
echo
-
ne
"$line\t"
echo
stat|nc -w 2 $line 2181 |
egrep
"^(Node|Zxid|Mode|Connections)"
|
tr
"\n"
"\t"
echo
stat|nc -w 2 $line 2181 |
egrep
"^(Node|Zxid|Mode|Connections)"
&>
/dev/null
||
echo
-n
"host is done."
echo
done
使用方式:zkstat.sh /配置文件zoo.cfg的位置
zookeeper是相当稳定的,基本不用管。
备注:zookeeper配置文件里不能有汉字,否则启动不起来。
三、清洗(logstash)
logstash用做清洗,并且将处理好的日志推送到es里,安装过程很简单详见网址:
https://www.elastic.co/guide/en/logstash/current/installing-logstash.html#package-repositories
我线上的nginx的配置文件如下:
input {
kafka {
zk_connect =>
"10.13.88.190:2181,10.13.88.191:2181,10.13.88.192:2181"
topic_id =>
"cms-nginx"
group_id =>
"cms-nginx"
consumer_threads => 1
reset_beginning =>
false
decorate_events =>
false
}
}
filter {
ruby {
init =>
"@kname = ['host-name','front','http_x_up_calling_line_id','request','http_user_agent','status','remote_addr_1','id','http_referer','request_time','body_bytes_sent','http_deviceid','http_x_forwarded_for','domain','cookie']"
code =>
"event.append(Hash[@kname.zip(event['message'].split('`'))]) "
remove_field => [
"@version"
,
"_score"
,
"id"
,
"tags"
,
"key"
,
"message"
,
"http_deviceid"
,
"http_x_up_calling_line_id"
,
""
,
"cookie"
]
}
if
[front] {
grok {
match => [
"front"
,
"%{HTTPDATE:logdate}"
]
}
date
{
match => [
"logdate"
,
"dd/MMM/yyyy:HH:mm:ss Z"
]
target =>
"@timestamp"
remove_field => [
"front"
,
"logdate"
]
}
}
if
[request] {
ruby {
init =>
"@kname = ['method','uri','verb']"
code =>
"event.append(Hash[@kname.zip(event['request'].split(' '))])"
remove_field => [
"request"
,
"method"
,
"verb"
]
}
}
if
[remote_addr_1] {
grok {
match => [
"remote_addr_1"
,
"%{IPV4:remote_addr}"
]
remove_field => [
"remote_addr_1"
]
}
}
mutate {
convert => [
"body_bytes_sent"
,
"integer"
,
"status"
,
"integer"
,
"request_time"
,
"float"
]
}
}
output {
elasticsearch {
hosts => [
"10.39.40.94:9200"
,
"10.39.40.95:9200"
,
"10.39.40.96:9200"
,
"10.39.40.97:9200"
]
workers => 1
index =>
"logstash-cms-nginx-%{+YYYY.MM.dd.hh}"
}
#stdout { codec => dots
#workers => 5
#} #测试性能时使用
#stdout { codec => rubydebug } #调试时使用
}
启动命令:.
/bin/logstash
-w 4 -b 1000 -f
/etc/logstash/conf
.d
/kafka_logstash_cms_nginx
.conf &
-w 后面的worker数是根据cpu的核心数大概算一下,我这里一台服务器开三个logstash,每个起4个worker
配置文件看着很长,其实阅读性很好,很易懂上手编写,无非就是定义切割点,如果大切割点下需要继续切割,就加if判断,继续切割,吐个槽里面threads和workers的数量好像不管用,我压测时去看线程数对不上,看的方法是top -H -p logstash的pid。
再就是看看哪些需要计算的变成数字型,还有个timestamp的处理,这个可以看看上面的代码,对于nginx打印的时间符合ISO8601标准,可以用他做es的时间索引,这样有个好处,如果某个环节慢索引赶不上的话,日志不会错序。时间标准详细可见:http://udn.yyuap.com/doc/logstash-best-practice-cn/filter/date.html
备注:
a、尽量去掉没用的字段,精简索引,非常重要;
b、nginx打印出来的时间是标准化的,可以用它传到es作为timestamp建索引;
c、对于响应时间、响应内容大小、状态码要转换成数字类型,方便在kibana里做计算等操作;
d、切割双引号可以使用如下配置
code =>
"event.append(Hash[@kname.zip(event['message'].split(34.chr))])"
e、抓包后发现,logstash向es推数据是轮训的,从zookeeper消费数据不是轮训,可能是1个个用,有压力或问题后再去启用后面的zookeeper。
f、尽量按照官方如下写法建立多个索引向es推送,防止单个索引巨大,search时计算不出来
index =>
"logstash-cms-nginx-%{+YYYY.MM.dd.hh}"
g、测试性能方法如下
由于没有现成工具,我们用了打点计量的方式进行压测,摘掉es后将输出变为一个点,每处理一条信息打一个点,然后将打出的点用pv命令统计出字节流量,反推出logstash的吞吐量。
cp一个配置文件,修改output如下:
output {
stdout { codec => dots
workers => 1
}
}
同时为了不影响线上业务,修改group_id,这样的话测试消费和线上消费互不影响,配置文件修改如下:
kafka {
zk_connect =>
"10.13.88.190:2181,10.13.88.191:2181,10.13.88.192:2181"
topic_id =>
"nginx"
group_id =>
"test001"
consumer_threads => 12
reset_beginning =>
false
decorate_events => flase
}
测试时执行命令:/opt/logstash/bin/logstash -f /tmp/kafka_test.conf |pv -abt > /dev/null
压测结果如下:
每个点是一个byte,等到数据稳定后,计算每s的吞吐量为2.93*1024=3000,也就是这一个logstash最大吞吐量为能处理3000条信息每s。
四、索引(es 2.X 版本)
es是硬盘io和cpu消耗比较重的部分,硬优化有ssd,软优化牵涉到java层面的GC调优、index调优、进程池调优、merge调优等,目前跑的还是比较好的,也有说将index和search分开的,防止search太大影响index索引,没去尝试,10多亿条日志,在目前的架构下性能还可以。
es的安装也是比较简单详见:https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html
a、我们线上添加的进程调优如下,不加这个配置,跑一段时间就会有出现某一台es负载特别高的情况,而且难以恢复,甚至死机,加后运行健壮,先看调整前后对比图:
bootstrap.memory_lock:
false
# centos 6版本内核不支持
bootstrap.system_call_filter:
false
indices.store.throttle.
type
: merge
#merge调优
indices.store.throttle.max_bytes_per_sec: 50m
index.merge.scheduler.max_thread_count: 1
index.merge.policy.max_merged_segment: 15gb
index.refresh_interval: -1
index.translog.flush_threshold_ops: 100000
#tranlog达到多少条进行数据平衡,默认为5000
index.translog.flush_threshold_size: 512m
threadpool.bulk.
type
: fixed
threadpool.bulk.size: 12
#12核机器,核心数
threadpool.bulk.queue_size: 1000
threadpool.index.
type
: fixed
threadpool.index.size: 12
#12核机器,核心数
threadpool.index.queue_size: 1000
threadpool.get.
type
: fixed
threadpool.get.size: 12
#12核机器,核心数
threadpool.get.queue_size: 1000
threadpool.search.
type
: fixed
threadpool.search.size: 24
#12核机器,核心数2倍
threadpool.search.queue_size: 500
index.indexing.slowlog.threshold.index.warn: 10s
index.indexing.slowlog.threshold.index.info: 5s
index.indexing.slowlog.threshold.index.debug: 2s
index.indexing.slowlog.threshold.index.trace: 500ms
index.search.slowlog.threshold.fetch.warn: 1s
index.search.slowlog.threshold.fetch.info: 800ms
index.search.slowlog.threshold.fetch.debug: 500ms
index.search.slowlog.threshold.fetch.trace: 200ms
另外的一些配置中项如下:
cluster.name: yz-search
#集群名称
node.name: yz-search94
#当前节点名称
index.number_of_shards: 4
#每个index的shards数,这个根据磁盘数量等去算,默认5
index.number_of_replicas: 0
#要不要复制分片
index.refresh_interval: 60s
#刷新时间
path.logs:
/data1/LogData/logs
#日志存放地点
network.host: 10.39.40.94
#绑定ip地址
http.port: 9200
#http管理端口
discovery.zen.
ping
.unicast.hosts: [
"10.39.40.94:9300"
,
"10.39.40.95:9300"
,
"10.39.40.96:9300"
,
"10.39.40.97:9300"
]
#用来发现集群存在的es主机
discovery.zen.minimum_master_nodes: 2
#最少有几个主机就可以进行master选举
b、heap数的设置,官方说是不能超过30G,我们64G的内存,设置是25G,GC方式采用G1
c、常用es的集群管理命令,当然只是看信息的可以浏览器里直接输入查看
curl http:
//10
.39.40.94:9200
/_cat/nodes
?
v
#节点概况
curl http:
//10
.39.40.94:9200
/_cat/shards
?
v
#查看shards的信息
curl http:
//10
.39.40.94:9200
/_cat/indices
?
v
#查看索引信息,如果新推的日志,可以看这个确认是否索引成功
curl -X DELETE
"http://10.39.40.94:9200/索引名称"
#删除指定历史索引,速度很快
对于我们线上的日志,默认保存7天,每天晚上清除一次,清除的脚本如下:
#!/bin/bash
DATES=`
date
+%Y.%m.%d -d
'-7 day'
`
curl -X DELETE
ES升级5.2.1详见:ELK之ES2.4.1双实例平滑升级至5.2.1问题解决及supervisor管理记
五、展现(kibana)
展现kibana没什么可说的,直接安装后,配置好es的地址就可以用,安装很简单有rpm包,前端可以用nginx做个代理,做限制,安装详见:https://www.elastic.co/downloads/kibana
安装后模型搭建也比较人性化,用几次就熟练了。
备注:像logstash、kafka这种加&号启动的服务(有些启动后自己fork新进程然后退出的其实不合适)可以用supervisor管理,比较方便。配置相当简单,可以在浏览器看状态。
supervisor的配置文件如下:
[unix_http_server]
file
=
/tmp/supervisor
.sock ; (the path to the socket
file
)
[inet_http_server] ; inet (TCP) server disabled by default
port=*:9001 ; (ip_address:port specifier, *:port
for
all iface)
#username=cms ; (default is no username (open server))
#password=123 ; (default is no password (open server))
[supervisord]
logfile=
/tmp/supervisord
.log ; (main log
file
;default $CWD
/supervisord
.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=
/tmp/supervisord
.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=
false
; (start
in
foreground
if
true
;default
false
)
minfds=1024 ; (min. avail startup
file
descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:
///tmp/supervisor
.sock ; use a unix:
//
URL
for
a unix socket
serverurl=http:
//
*:9001 ; use an http:
//
url to specify an inet socket
#username=cms ; should be same as http_username if set
#password=123 ; should be same as http_password if set
[program:logstash_cms_php_slow_log]
command
=
/opt/logstash/bin/logstash
-w 4 -b 1000 -f
/etc/logstash/conf
.d
/kafka_logstash_cms_php
.conf ; the program (relative uses PATH, can take args)
process_name=%(process_num)d ; process_name
expr
(default %(program_name)s)
numprocs=1 ; number of processes copies to start (def 1)
umask
=022 ;
umask
for
process (default None)
priority=999 ; the relative start priority (default 999)
autostart=
true
; start at supervisord start (default:
true
)
redirect_stderr=
true
; redirect proc stderr to stdout (default
false
)
stdout_logfile=
/var/log/logstash_php_log
; stdout log path, NONE
for
none; default AUTO
[program:logstash_cms_nginx_log]
command
=
/opt/logstash/bin/logstash
-w 4 -b 1000 -f
/etc/logstash/conf
.d
/kafka_logstash_cms_nginx
.conf ; the program (relative uses PATH, can take args)
process_name=%(process_num)d ; process_name
expr
(default %(program_name)s)
numprocs=3 ; number of processes copies to start (def 1)
umask
=022 ;
umask
for
process (default None)
priority=999 ; the relative start priority (default 999)
autostart=
true
; start at supervisord start (default:
true
)
redirect_stderr=
true
; redirect proc stderr to stdout (default
false
)
stdout_logfile=
/var/log/logstash_nginx_log
; stdout log path, NONE
for
none; default AUTO
;[include]
;files = relative
/directory/
*.ini