使用ELK分析Nginx日志实践

安装好ELK后,可以分析Nginx的日志查看访问量,甚至可以根据ip地址解析出GPS信息,最后生成用户访问热力图,本文是基于ELK 5.6.3版本。

1.配置Nginx日志的Logstash文件

在logstash的config目录下

vim logstash.conf

添加:

input {
        file {
                path => ["/data/log/nginx/www_access.log"]
        }
}
filter {
        grok {
                match => { "message" => "%{IPORHOST:clientip} - - \[%{HTTPDATE:time}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:http_status_code} %{NUMBER:bytes} \"%{GREEDYDATA:referrer}\" \"%{GREEDYDATA:agent}\" \"%{GREEDYDATA:xforwardedfor}\""}
        }
        geoip {
                source => "clientip"
                        target => "geoip"
                        # fields => ["city_name", "continent_code", "country_code2", "country_code3", "country_name", "ip", "latitude", "longitude", "postal_code", "region_name", "timezone"]
                        database => "/data/src/logstash/GeoLite2-City_20171107/GeoLite2-City.mmdb"
                        add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ]
                        add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}"  ]
        }
        mutate {
                convert => [ "[geoip][coordinates]", "float"]
        }
}
output {
        stdout { codec => rubydebug }
        elasticsearch {
                hosts  => "127.0.0.1:9200"
                        index  => "logstash-nginx-%{+YYYY.MM.dd}"
        }
}

重点说明:

以上的配置中input下的file是Nginx的access文件。

下面filter的grok的match里的规则对应的是Nginx默认配置的日志格式,一般来说Nginx的配置文件里access日志后面加上main,下面的geoip是用来将IP地址解析成GPS信息,database是在 https://dev.maxmind.com/geoip/geoip2/geolite2/ 下载的,选择的是GeoLite2 City MaxMind DB binary gzipped,解压后将数据库mmdb格式的地址写入。

最后的output里将elasticsearch的地址写入,值得注意的是index一定要logstash打头,否则后面使用Kibana做用户访问热力图的时候会报错“No Compatible Fields: The “[nginx-access-]YYYY-MM-dd” index pattern does not contain any of the following field types: geo_point”,原因是索引格式为[nginx-access-]YYYY-MM-dd的日志文件由logstash输出到Elasticsearch;在Elasticsearch中,所有的数据都有一个类型,什么样的类型,就可以在其上做一些对应类型的特殊操作。geo信息中的location字段是经纬度,我们需要使用经纬度来定位地理位置;在Elasticsearch中,对于经纬度来说,要想使用Elasticsearch提供的地理位置查询相关的功能,就需要构造一个结构,并且将其类型属性设置为geo_point,此错误明显是由于我们的geo的location字段类型不是geo_point。

2.Kibana配置Index Patterns

到Kibana选择Management->选择Index Patterns->点击Create Index Pattern按钮->在Index pattern的输入框输入logstash-nginx-*

3.Kibana配置地图

Visualize->New->选择Maps下的Coordinate Map->选择刚刚配置的logstash-nginx-*->点击Geo Coordinates->Field选择geoip.location->点击“播放键”apply changes

最后结果:

ELK安装和实践

今年博主转战创业公司,随着系统的稳定和访问量的提升,急需一个在线的日志分析系统提供帮助,业界最知名的当属ELK了。架设这套体系的目的有三个:

1. 收集多台服务器上的日志信息,不需要上不同的服务器查看日志,方便分析处理问题。

2. 可视化界面方便搜索关键词和日志类型,处理潜在的bug。

3. 分析Nginx的访问量,是数据说话,让整个系统的运行情况了然于胸。
不多说了,直接切入正题

1. 安装Elasticsearch

下载最新的Elasticsearch tar包

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.3.tar.gz

解压

tar zxvf elasticsearch-5.6.3.tar.gz

进入解压后目录

cd elasticsearch-5.6.3

如果想要设置其他服务器访问,需要修改配置,将network.host修改为本机的外网或内网ip地址,如果只要用127.0.0.1就略过

vim config/elasticsearch.yml

启动Elasticsearch

bin/elasticsearch -d

2. 安装Logstash

下载最新的logstash tar包

wget https://artifacts.elastic.co/downloads/logstash/logstash-5.6.3.tar.gz

解压

tar zxvf logstash-5.6.3.tar.gz

进入解压后目录

cd logstash-5.6.3

增加配置

vim config/logstash.conf

添加:

input {
        tcp {
                mode => "server"
                host => "localhost"
                port => 4567 
                codec => multiline {
                        pattern => "^\t"                                
                        what => "previous"
                }       
        }
}
filter {
        grok {
                match => { "message" => "%{TIMESTAMP_ISO8601:time} \[%{GREEDYDATA:thread}\] \[%{GREEDYDATA:id}\] \[%{GREEDYDATA:id2}\] %{LOGLEVEL:loglevel} %{JAVACLASS:class}(\s-\s%{GREEDYDATA:info})?"}
        }
}
output {
        stdout { codec => rubydebug }
        elasticsearch {
                hosts  => "127.0.0.1:9200"
                index => "logstash-platform-%{+YYYY.MM.dd}"
        }
}

说明:以上的配置中input下的tcp是根据log4j里配置写的,在我们的JAVA工程里log4j的配置文件部分内容如下:

<Appenders>
    <!-- 日志输出到logstash -->
    <Socket name="LogStash" host="localhost" port="4567">
        <PatternLayout pattern="%d [%t] [%X{X-UUID}] [X-UID-%X{X-UID}] %-5level %logger - %msg%n" />
    </Socket>
</Appenders>
<Loggers>
    <Root level="DEBUG">
        <AppenderRef ref="Console"/>
        <AppenderRef ref="LogStash"/>
    </Root>
</Loggers>

以上XML中的host和port就对应了logstash配置中的host和port。

codec的multiline的作用是为了将exception报错输出的内容合并到一行去,即如果是以制表符\t开头的一行输入内容都连接到上一行(previous)。

filter中的grok用与匹配输入的message并做文本的截取,这里面是正则表达式,大家可以到 http://grokdebug.herokuapp.com/ 调试自己的match规则。

output里面hosts就是elasticsearch的http地址,index是logstash在elasticsearch里面创建的索引的名字,它的默认值是”logstash-%{+YYYY.MM.dd}”,我把它配置成了”logstash-platform-%{+YYYY.MM.dd}”目的是跟nginx拉过来的日志做个区分。
如果有什么问题建议大家查阅logstash官方文档,很容易理解。

最后启动logstash

nohup bin/logstash -f config/logstash.conf & > /dev/null

3. 安装Kibana

下载最新的Kibana tar包

wget https://artifacts.elastic.co/downloads/kibana/kibana-5.6.3-linux-x86_64.tar.gz

解压

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

进入解压后目录

cd kibana-5.6.3-linux-x86_64

修改配置,将elasticsearch.url改为上面安装的elasticsearch的地址

vim config/kibana.yml

启动kibana

nohup bin/kibana & > /dev/null

4. 给kibana增加登录认证

centos需要运行htpassword,先安装httpd-tools

sudo yum install -y httpd-tools

给admin这个用户设置密码

sudo htpasswd -c /data/nginx/.htpasswd admin

最后配置nginx反向代理,这里就不说了。
随着JAVA的工程启动,会忘logstash写入日志信息,这时候可以通过

curl -XGET 127.0.0.1:9200/_cat/indices

查看是否存在logstash-platform开头的索引生成。如果不正常,可以去logstash目录下的logs去查看什么原因。正常就打开Kibana,配置index pattern为logstash-platform*。

搞定后大家就可以自己去把玩Kibana发掘下它的牛逼之处了!

一次JVM引起的服务器load过高问题排查经历

2016-11-08早上9:25分,公司运维的同事给我报警,称我们团队的一台服务器CPU的压力很大,zabbix的图显示:

1接到警报后,我在该服务器上使用w和top命令查看服务器的load和CPU情况,1分钟的load average在3-5之间,系统在高负荷运转,查看了我们的线上应用系统发现网页打开正常,但一个別列表的查询缓慢,chrome浏览器的network查看http接口的timing在十几秒。这部分列表走的是搜索引擎,我第一反应是会不会是搜索引擎出问题了?

于是我在服务器上查看tomcat日志,获取我们的应用调用搜索引擎的接口和参数,拼装后使用

curl -v '$url'

命令访问,发现访问的速度没有问题。那说明搜索引擎没有问题,那一定是我们的应用从搜索引擎返回值到展示给用户的部分处理有问题。

由于有大量用户反馈系统速度慢,于是我使用jps获取java进程的pid,随后使用:

jmap -dump:format=b,file=crm.bin [pid]

命令dump jvm的信息“保留犯罪现场”,然后重启线上应用。

启动应用后检测到服务器的load渐渐越来越高,最后保持在重启前的样子,于是使用

jstat -gc [pid] 3000

查看服务器每隔3秒的gc情况,发现3秒钟的full gc能够达到两次,jvm的状况十分异常。在启动服务器的这个时间里我利用eclipse MAT工具分析jmap dump文件信息,未诊断出异常。

后来我把原来jvm的Xms参数配置由1024m提高到3072m,发现双机的full gc次数分别稳定在0和2后再也没有full gc过了,但是young GC却十分频繁,3秒钟young GC次数可以达到15-20次。

到底是什么东西这么占内存???我使用jmap -histo pid查看,发现排在第一位的[C对象经常飙升到1个多G然后降到200MB,就是young GC频繁的原因,这通常是String类型的对象存在问题。

随后我使用

top -H -p [pid]

看到了如下结果:

2这一排的PID都占用了大量的CPU资源,于是随机取了一个PID为1077的进程,使用

printf "%x\n" 1077

转换成16进制的数字为435

随后使用jstack工具:

jstack [pid] | less

输出当前的堆栈信息,然后搜索435,发现如下内容:

3这块代码就是调用搜索引擎的代码,它处于RUNNABLE的状态,一直在执行中,说明这段代码一定是追魁祸首!打开代码看看!原来是这样的一段代码:

while ((line = in.readline()) != null) {
    result += line;
}

真相就是:循环里每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。

将String改成StringBuilder,发布上线后load过高的问题解决。这就解释了为什么一般的页面打开速度正常,而搜索客户列表接口速度缓慢以及为什么char类型的数组占用大量内存。

Nginx报错open failed 13: Permission denied while reading upstream的解决方法

今天我们的系统出现了一个很诡异的情况,一个页面里有几个js文件和图片无法读到,Chrome浏览器里的status是:net::ERR_CONTENT_LENGTH_MISMATCH。而同一个页面其他的js、css和图片都没有问题,status是200。

遇到这个问题我第一反应是这个系统的tomcat我们也没有人动过,这几个资源文件在服务上是用的,那就是看看Nginx有没有问题,刚好今天有人动过Nginx,那就就去看看Nginx的access.log和error.log的日志。发现error.log里面有一个报错:

2016/06/05 17:58:06 [crit] 1017#0: *182 open() “/usr/local/nginx/proxy_temp/4/02/0000000024” failed (13: Permission denied) while reading upstream, client: ***.***.***.***, server: *****, request: “GET /idp/resources/images/mitreid-connect.ico HTTP/1.1”, upstream: “https://*****:8444/idp/resources/images/mitreid-connect.ico”, host: “*****”, referrer: “******”

proxy_temp目录是干什么的?我们的Nginx有个配置proxy_temp_file_write_size,当文件超过该参数设置的大小时,Nginx会先将文件写入临时目录,默认为Nginx安装目下/proxy_temp目录。

默认情况下,nginx是以www启动的,用ll命令查看proxy_temp目录,我们发现用户和用户组是nobody,难怪Permission denied!

我们用

chown -R www:www proxy_temp

把proxy_temp目录改成nobody的用户组权限,重启Nginx后问题就解决了!

Nginx报504 gateway timeout错误的解决方法

最近在工作中,需要做Excel导入的功能,由于Excel的数据比较多,而且我们的服务端程序需要对数据的内容做校验,会调用很多的外部服务接口,所以毫无悬念的导入Excel接口调用超过了一分钟,并且报错:504 gateway timeout。以下是两种解决思路:
1. 优化业务代码
一个接口调用超过一分钟,一定有可以优化的地方,看看数据库或者接口的调用是否合理,是否可以合并请求。
2. 修改Nginx的服务器配置
如果实在是优化不了了,可以把Nginx的超时时间上调。
看看时间是否符合要求,在nginx.config里面的三个参数:
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;

以上的单位是秒。

如果使用了Nginx的代理,可以在块里加上:
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
变成:
location /foo {
     proxy_pass http://xxx.xxx.xxx.xxx:8080/foo;
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_connect_timeout 300s;
     proxy_send_timeout 300s;
     proxy_read_timeout 300s;
     access_log /var/log/nginx/access.foo.log main;
     error_log /var/log/nginx/error.foo.log;
}