2013年11月8日

Centrialized Log Management with logstash 集中日誌管理

Auto Scaling 加上伺服器數量越來越多之後就產生 log management 的問題
正所謂撒了一個謊就需要撒更多的謊來彌補它
目前已經在使用 sentry 來做集中管理
sentry 非常棒,簡單清爽容易設定介面又漂亮,預設就有通知和人員管理等功能
sentry 設定上比 logstash 簡單10倍,但是對於系統 log 還沒支援,客製化功能也比較少
logstash 的特色就是可以自定輸出規則、過濾規則,自定 log 頁面、圖表等等
一開始最簡單的管理方法的寫個 cron job 把 log 檔案傳到同一個伺服器
或是透過 syslog
一般 system log 是透過 syslog 服務,比進階的有 rsyslog & syslog-ng
rsyslog & syslog-ng 這兩個都是 syslog-based,可以做到集中化,ssh tcp/udp 傳輸等
但是這些方法還是不利於搜尋管理和監控
於是找到了 logstash with elasticsearch and kibana
logstash 主要由三個元件構成 input, filter, output
input 來源可以是 file, stdin, syslog, udp, redis, elasticsearch, etc.
filter 主要用來"操作"這些 input
output 則是把最後的資料放到某個地方 stdout, syslog, elasticsearch, file, etc
所以可以想像 logstash 可以被放在 centrialized server / cluster / instances
但是佔用的記憶體可不小,至少也要 200mb 才堪用
所以我的作法是用 rsyslog 蒐集資料後吐給 centrialized logstash 後存到 elasticsearch server
app server 上面使用老牌 rsyslog 搭配嘻哈 logstash 這樣的組合

Installation 安裝 logstash and elastic search

雖然 logstash jar 自帶一組 embedded elasticsearch,應該算是測試用的
還是自己裝一個 elastic search吧
install elastic search
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.5.noarch.rpm sudo yum install elasticsearch-0.90.5.noarch.rpm sudo service elasticsearch start curl http://localhost:9200
參考 http://logstash.net/docs/1.2.2/tutorials/getting-started-simple
wget https://download.elasticsearch.org/logstash/logstash/logstash-1.2.2-flatjar.jar vi simple.conf input { stdin { } } output { stdout { codec => rubydebug } } java -jar logstash-1.2.2-flatjar.jar agent -f simple.conf


grok

grok 是 regex 的進階,grok 是為了讓 regex 更能夠被重複使用和理解
但是實際上在使用的時候一直在查表,花了許多時間
grok 可以參考
https://github.com/logstash/logstash/blob/v1.2.2/patterns/grok-patterns
gork 小工具
http://grokdebug.herokuapp.com/
不過小工具也不是完全正確,不是實際上在使用的 syntax parser
像是 YEAR 有時候最多對應到 2 位數,但是一些使用 YEAR 的延伸 grok 像是一些 DATESTAMP/TIMESTAMP 有時候卻可以對應到 4 位數
實際上 jar 使用的 parser 有非常多 bug,許多 regex 無法正確的轉換成 grok
尤其是在 -- web 的時候
更糟糕的是,debugger 非常的鳥,不管有什麼地方錯誤大概八成都是回報第一行錯誤
所以在寫 regex to grok pattern 的時候盡量拆分成小的 grok pattern 一球一球對決
grok pattern 也盡量做成另外的檔案和 logstash.conf 拆開
常見的鳥 bug 像是%符號如果緊黏著前面的字符很容易出錯,log 中有空白字元就盡量直接寫出來
最後發現罪魁禍首是內建的 web ,不用之後才沒有 grok syntax 問題

logstash config

範例 mkdir patterns vi patterns/nginx.grok nginx.grok
# nginx NGINXACCESS %{SYSLOGHOST:syslog_hostname} %{SYSLOGPROG}:%{IPORHOST:addr} - %{USERNAME} \[%{HTTPDATE:date}\] "(%{DATA:method} %{DATA:uri} %{DATA:protocol}|%{DATA})" %{INT:status} %{INT} %{QS:referer};? %{QS:user_agent} %{QS} logstash.conf 範例,假設這裡只看nginx log
# date match format "EEE MMM dd HH:mm:ss YYYY Z" input { udp { port => 5544 type => syslog } } filter { #nginx grok { patterns_dir => "./patterns" match => { "message" => "%{NGINXACCESS}" } add_tag => ["nginx","iso8601"] tag_on_failure => [] } if "iso8601" in [tags] { date { match => {"date" => ["ISO8601"]} } } if [status] in ["400","404","500"]{ mutate { add_tag => ["error"] } } } output { elasticsearch { host => "127.0.0.1" } } start logstash java -jar logstash-1.2.2-flatjar.jar agent -f logstash.conf run logstash as service
mv logstash /opt/ vi logstash.sh sudo mv logstash.sh /etc/init.d/logstash sudo chmod +x /etc/init.d/logstash #! /bin/sh #!/bin/sh # # chkconfig: 2345 70 40 # description: logstash startup script # author: Michael Ladd with several improvements by Matt Reid # websites: http://www.mjladd.com && http://themattreid.com # license: GPL v2 # date: 2012-12-06 # version: 0000.1 # LOGHOME="/opt/logstash" LOGSTASH="$LOGHOME/logstash-1.2.2-flatjar.jar" CONF="$LOGHOME/logstash.conf" TMPDIR=/dev/shm LOGFILE="/var/log/logstash.log" JAVA="/usr/bin/java" export TMPDIR export JAVA function missing_jar() { echo "Failed to find logstash jar file: [$LOGSTASH]"; echo "Check /etc/init.d/logstash file for correct settings." RETVAL=1; exit 1; } function missing_conf() { echo "Failed to find logstash config file:[$CONF]"; echo "Check /etc/init.d/logstash file for correct settings." RETVAL=1; exit 1; } test -f $LOGSTASH || missing_jar test -f $CONF || missing_conf . /etc/rc.d/init.d/functions RETVAL=0 case "$1" in start) echo -n "Starting logstash: " #check to see if we're already running pgrep -f ${LOGSTASH} > /dev/null RUNNING=$? if [ $RUNNING -eq 0 ]; then echo "[FAILED]" echo echo "Reason: logstash is already running." RETVAL=1 exit 1; fi #start it up daemon "cd $LOGHOME;$JAVA -jar $LOGSTASH agent -f $CONF &" 2> $LOGFILE; pgrep -f ${LOGSTASH} > /var/run/logstash.pid #record PID in pid file RETVAL=$? echo if [ $RETVAL -eq 0 ]; then touch /var/lock/subsys/logstash else echo "RETVAL for daemon = $RETVAL" echo "PID missing for logstash, PID value not recorded. Something is wrong!" pgrep ${LOGSTASH} fi ;; stop) echo -n "Shutting down logstash: " killproc logstash RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/logstash ;; restart|reload) $0 stop $0 start RETVAL=$? ;; status) status logstash RETVAL=$? ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 esac exit $RETVAL


一些其他可以改進小地方像是修改 nginx.conf 關閉不需要的 log location /elb-status { access_log off; include uwsgi_params; uwsgi_pass django; } 和修改 timestamp 的方法,改成 iso8601 format log_format main '$remote_addr - $remote_user [$time_iso8601] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main;

以上是接收端(log server)的部份, 發送端可以使用 rsyslog 並參考 http://cookbook.logstash.net/recipes/rsyslog-agent/ $ModLoad imfile # Load the imfile input module $ModLoad imudp $ModLoad imuxsock #$ModLoad imklog $template RemoteHost, "%HOSTNAME% %syslogtag%%msg%" # watch nginx access log $InputFileName /var/log/nginx/access.log $InputFileTag prod-nginx-access: $InputFileStateFile state-nginx-access $InputRunFileMonitor $InputFilePollInterval 10 #discard health check, can be deprecated if I stop nginx log by user agent if $programname == 'dhclient' or \ $programname == 'kernel' then ~ :msg, contains, "Amazon Route 53 Health Check Service" ~ :msg, contains, "ELB-HealthChecker" ~ *.* @my.log.server:5544;RemoteHost "@"代表使用 udp 傳輸,"@@"代表使用 tcp
$InputFileTag 就是 $programname
以上這樣的設定會建議在 Virtual Private Cloud 裡面運作,以免資料被別人看光光


Debug

rsyslog debug 請參考 http://www.rsyslog.com/doc/troubleshoot.html
基本上就是停掉服務改用 native mode sudo service rsyslog stop sudo /sbin/rsyslogd -c5 -dn -f /etc/rsyslog.conf |grep error > r.log

logstash date format
http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html