通過本篇內容,你可以學到如何解決Logstash的常見問題、理解Logstash的運行機制、集群環(huán)境下如何部署ELKStack。
本文目錄如下:
前言一、部署架構圖二、Logstash 用來做什么?三、Logstash 的原理3.1 從 Logstash 自帶的配置說起3.2 Input 插件3.3 Filter 插件3.4 Output 插件3.5 完整配置四、Logstash 怎么跑起來的4.1 Logstash 如何運行的4.2 Logstash 的架構原理五、Logstash 宕機風險5.1 Logstash 單點部署的風險5.2 開機啟動 Logstash六、總結前言
通過本篇內容,你可以學到如何解決 Logstash 的常見問題、理解 Logstash 的運行機制、集群環(huán)境下如何部署 ELK Stack。
在使用 Logstash 遇到了很多坑,本篇也會講解解決方案。
【資料圖】
一、部署架構圖
上次我們聊到了 ELK Stack 的搭建:
一文帶你搭建一套 ELK Stack 日志平臺
最近悟空正在我們的測試環(huán)境部署這一套 ELK,發(fā)現還是有很多內容需要再單獨拎幾篇出來詳細講講的,這次我會帶著大家一起來看下 ELK 中的 Logstash 組件的落地玩法和踩坑之路。
測試環(huán)境目前有 12 臺機器,其中 有 4 臺給后端微服務、Filebeat、Logstash 使用,3 臺給 ES 集群和 Kibana 使用。
部署拓撲圖如下:
部署說明:
4 臺服務器給業(yè)務微服務服務使用,微服務的日志會存放本機上。4 臺服務器都安裝 Filebeat 日志采集器,采集本機的微服務日志,其中一臺服務器安裝 Logstash ,Filebeat 發(fā)送日志給 Logstash。Logstash 將日志輸出到 Elasticsearch 集群中。3 臺服務器都安裝有 Elasticsearch 服務,組成 ES 集群。其中一臺安裝 Kibana 服務,查詢 ES 集群中的日志信息。二、Logstash 用來做什么?
你是否還在苦惱每次生產環(huán)境出現問題都需要遠程到服務器查看日志文件?
你是否還在為了沒有統(tǒng)一的日志搜索入口而煩心?
你是否還在為從幾十萬條日志中搜索關鍵信息而苦惱?
沒錯,Logstash 它來啦,帶著所有的日志記錄來啦。
Logstash 它是幫助我們收集、解析和轉換日志的。作為 ELK 中的一員,發(fā)揮著很大的作用。
當然 Logstash 不僅僅用在收集日志方面,還可以收集其他內容,我們最熟悉的還是用在日志方面。
三、Logstash 的原理
3.1 從 Logstash 自帶的配置說起
Logstash 的原理其實還比較簡單,一個輸入,一個輸出,中間有個管道(不是必須的),這個管道用來收集、解析和轉換日志的。如下圖所示:
Logstash 組件
Logstash 運行時,會讀取 Logstash 的配置文件,配置文件可以配置輸入源、輸出源、以及如何解析和轉換的。
Logstash 配置項中有兩個必需元素,輸入(inputs)和輸出(ouputs),以及一個可選元素 filters 過濾器插件。input 可以配置來源數據,過濾器插件在你指定時修改數據,output 將數據寫入目標。
我們來看下 Logstash 軟件自帶的一個示例配置,文件路徑:\logstash-7.6.2\config\logstash-sample.conf
是不是很簡單,一個 input 和 一個 output 就搞定了。如下圖所示:
但是這種配置其實意義不大,沒有對日志進行解析,傳到 ES 中的數據是原始數據,也就是一個 message 字段包含一整條日志信息,不便于根據字段搜索。
3.2 Input 插件
配置文件中 input 輸入源指定了 beats,而 beats 是一個大家族,Filebeat 只是其中之一。對應的端口 port = 5044,表示 beats 插件可以往 5044 端口發(fā)送日志,logstash 可以接收到通過這個端口和 beats 插件通信。
在部署架構圖中,input 輸入源是 Filebeat,它專門監(jiān)控日志的變化,然后將日志傳給 Logstash。在早期,Logstash 是自己來采集的日志文件的。所以早期的日志檢索方案才叫做 ELK,Elasticsearch + Logstash + Kibana,而現在加入了 Filebeat 后,這套日志檢索方案屬于 ELK Stack,不是 ELKF,摒棄了用首字母縮寫來命名。
另外 input 其實有很多組件可以作為輸入源,不限于 Filebeat,比如我們可以用 Kafka 作為輸入源,將消息傳給 Logstash。具體有哪些插件列表,可以參考這個 input 插件列表 1
3.3 Filter 插件
而對于 Logstash 的 Filter,這個才是 Logstash 最強大的地方。Filter 插件也非常多,我們常用到的 grok、date、mutate、mutiline 四個插件。
對于 filter 的各個插件執(zhí)行流程,可以看下面這張圖:
圖片來自 Elasticsearch 官網
3.3.1 日志示例
我以我們后端服務打印的日志為例,看是如何用 filter 插件來解析和轉換日志的。
logback.xml 配置的日志格式如下:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
日志格式解釋如下:
記錄日志時間:%d{yyyy-MM-dd HH:mm:ss.SSS}記錄是哪個線程打印的日志:%thread記錄日志等級:%-5level打印日志的類:%logger記錄具體日志信息:%msg%n,這個 msg 的內容就是 log.info("abc") 中的 abc。通過執(zhí)行代碼 log.info("xxx") 后,就會在本地的日志文件中追加一條日志。
3.3.2 打印的日志內容
從服務器拷貝出了一條日志,看下長什么樣,有部分敏感信息我已經去掉了。
2022-06-16 15:50:00.070 [XNIO-1 task-1] INFO com.passjava.config - 方法名為:MemberController-,請求參數:{省略}
那么 Logstash 如何針對上面的信息解析出對應的字段呢?比如如何解析出打印日志的時間、日志等級、日志信息?
3.3.3 grok 插件
這里就要用到 logstash 的 filter 中的 grok 插件。filebeat 發(fā)送給 logstash 的日志內容會放到 message 字段里面,logstash 匹配這個 message 字段就可以了。配置項如下所示:
filter { grok { match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s+\[(?.*)\]\s+(?\w*)\s{1,2}+(?\S*)\s+-\s+(?.*)\s*"] match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s{1,2}+(?\w*)\s{1,2}+.\s---+\s\[(?.*)\]+\s(?\S*)\s*:+\s(?.*)\s*"] }}
坑:日志記錄的格式復雜,正則表達式非常磨人。
大家發(fā)現沒,上面的 匹配 message 的正則表達式還是挺復雜的,這個是我一點一點試出來的。Kibana 自帶 grok 的正則匹配的工具,路徑如下:
http://:5601/app/kibana#/dev_tools/grokdebugger
我們把日志和正則表達式分別粘貼到上面的輸入框,點擊 Simulate 就可以測試是否能正確匹配和解析出日志字段。如下圖所示:
Grok Debugger 工具
有沒有常用的正則表達式呢?有的,logstash 官方也給了一些常用的常量
來表達那些正則表達式,可以到這個 Github 地址查看有哪些常用的常量。
https://github.com/logstash-plugins/logstash-patterns-core/blob/main/patterns/ecs-v1/grok-patterns
比如可以用 IP 常量來代替正則表達式 IP (?:%{IPV6}|%{IPV4})
。
好了,經過正則表達式的匹配之后,grok 插件會將日志解析成多個字段,然后將多個字段存到了 ES 中,這樣我們可以在 ES 通過字段來搜索,也可以在 kibana 的 Discover 界面添加列表展示的字段。
坑:我們后端項目的不同服務打印了兩種不同格式的日志,那這種如何匹配?
再加一個 match 就可以了。
filter { grok { match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s+\[(?.*)\]\s+(?\w*)\s{1,2}+(?\S*)\s+-\s+(?.*)\s*"] match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s{1,2}+(?\w*)\s{1,2}+.\s---+\s\[(?.*)\]+\s(?\S*)\s*:+\s(?.*)\s*"] }}
當任意一個 message 匹配上了這個正則,則 grok 執(zhí)行完畢。假如還有第三種格式的 message,那么雖然 grok 沒有匹配上,但是 message 也會輸出到 ES,只是這條日志在 ES 中不會展示 logTime、level 等字段。
3.3.4 multiline 插件
還有一個坑的地方是錯誤日志一般都是很多行的,會把堆棧信息打印出來,當經過 logstash 解析后,每一行都會當做一條記錄存放到 ES,那這種情況肯定是需要處理的。這里就需要使用 multiline 插件,對屬于同一個條日志的記錄進行拼接。
3.3.4.1 安裝 multiline 插件
multiline 不是 logstash 自帶的,需要單獨進行安裝。我們的環(huán)境是沒有外網的,所以需要進行離線安裝。
介紹在線和離線安裝 multiline 的方式:
在線安裝插件。在 logstash 根目錄執(zhí)行以下命令進行安裝。
bin/logstash-plugin install logstash-filter-multiline
在有網的機器上在線安裝插件,然后打包。
bin/logstash-plugin install logstash-filter-multilinebin/logstash-plugin prepare-offline-pack logstash-filter-multiline
拷貝到服務器,執(zhí)行安裝命令。
bin/logstash-plugin install file:///home/software/logstash-offline-plugins-7.6.2.zip
安裝插件需要等待 5 分鐘左右的時間,控制臺界面會被 hang 住,當出現 Install successful
表示安裝成功。
檢查下插件是否安裝成功,可以執(zhí)行以下命令查看插件列表。當出現 multiline 插件時則表示安裝成功。
bin/logstash-plugin list
3.3.4.2 使用 multiline 插件
如果要對同一條日志的多行進行合并,你的思路是怎么樣的?比如下面這兩條異常日志,如何把文件中的 8 行日志合并成兩條日志?
多行日志示例
思路是這樣的:
第一步:每一條日志的第一行開頭都是一個時間,可以用時間的正則表達式匹配到第一行。第二步:然后將后面每一行的日志與第一行合并。第三步:當遇到某一行的開頭是可以匹配正則表達式的時間的,就停止第一條日志的合并,開始合并第二條日志。第四步:重復第二步和第三步按照這個思路,multiline 的配置如下:
filter { multiline { pattern => "^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}.\d{3}" negate => true what => "previous" }}
時間的正則表達式就是這個 pattern 字段,大家可以根據自己項目中的日志的時間來定義正則表達式。
pattern: 這個是用來匹配文本的表達式,也可以是grok
表達式what: 如果pattern
匹配成功的話,那么匹配行是歸屬于上一個事件,還是歸屬于下一個事件。previous: 歸屬于上一個事件,向上合并。
next: 歸屬于下一個事件,向下合并
negate: 是否對 pattern 的結果取反false: 不取反,是默認值。
true: 取反。將多行事件掃描過程中的行匹配邏輯取反(如果 pattern 匹配失敗,則認為當前行是多行事件的組成部分)
參考 multiline 官方文檔 2
3.3.5 多行被拆分
坑:Java 堆棧日志太長了,有 100 多行,被拆分了兩部分,一部分被合并到了原來的那一條日志中,另外一部分被合并到了不相關的日志中。
如下圖所示,第二條日志有 100 多行,其中最后一行被錯誤地合并到了第三條日志中。
日志合并錯亂
為了解決這個問題,我是通過配置 filebeat 的 multiline 插件來截斷日志的。為什么不用 logstash 的 multiline 插件呢?因為在 filter 中使用 multiline 沒有截斷的配置項。filebeat 的 multiline 配置項如下:
multiline.type: patternmultiline.pattern: "^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}.\d{3}"multiline.negate: truemultiline.match: aftermultiline.max_lines: 50
配置項說明:
multiline.pattern:希望匹配到的結果(正則表達式)multiline.negate:值為 true 或 false。使用 false 代表匹配到的行合并到上一行;使用 true 代表不匹配的行合并到上一行multiline.match:值為 after 或 before。after 代表合并到上一行的末尾;before 代表合并到下一行的開頭multiline.max_lines:合并的最大行數,默認 500multiline.timeout:一次合并事件的超時時間,默認為 5s,防止合并消耗太多時間導致 filebeat 進程卡死我們重點關注 max_lines 屬性,表示最多保留多少行后執(zhí)行截斷,這里配置 50 行。
注意:filebeat 和 logstash 我都配置了 multiline,沒有驗證過只配置 filebeat 的情況。參考 Filebeat 官方文檔 3
3.3.6 mutate 插件
當我們將日志解析出來后,Logstash 自身會傳一些不相關的字段到 ES 中,這些字段對我們排查線上問題幫助不大??梢灾苯犹蕹?。
坑:輸出到 ES 的日志包含很多無意義字段。
這里我們就要用到 mutate 插件了。它可以對字段進行轉換,剔除等。
比如我的配置是這樣的,對很多字段進行了剔除。
mutate { remove_field => ["agent","message","@version", "tags", "ecs", "input", "[log][offset]"]}
注意:一定要把 log.offset 字段去掉,這個字段可能會包含很多無意義內容。
關于 Mutate 過濾器它有很多配置項可供選擇,如下表格所示:
Mutate 過濾器配置選項
參考 Mutate 參考文章 4
3.3.7 date 插件
到 kibana 查詢日志時,發(fā)現排序和過濾字段 @timestamp
是 ES 插入日志的時間,而不是打印日志的時間。
這里我們就要用到 date
插件了。
上面的 grok 插件已經成功解析出了打印日志的時間,賦值到了 logTime
變量中,現在用 date 插件將 logTime
匹配下,如果能匹配,則會賦值到 @timestamp
字段,寫入到 ES 中的 @timestamp
字段就會和日志時間一致了。配置如下所示:
date { match => ["logTime", "MMM d HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]}
但是經過測試寫入到 ES 的 @timestamp
日志時間和打印的日志時間相差 8 小時。如下圖所示:
我們到 ES 中查詢記錄后,發(fā)現 @timestamp
字段時間多了一個字母 Z
,代表 UTC
時間,也就是說 ES 中存的時間比日志記錄的時間晚 8 個小時。
我們可以通過增加配置 timezone => "Asia/Shanghai" 來解決這個問題。修改后的配置如下所示:
date { match => ["logTime", "MMM d HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]}
調整后,再加一條日志后查看結果,Kibana 顯示 @timestamp 字段和日志的記錄時間一致了。
3.4 Output 插件
Logstash 解析和轉換后的日志最后輸出到了 Elasticsearch 中,由于我們 ES 是集群部署的,所以需要配置多個 ES 節(jié)點地址。
output { stdout { } elasticsearch { hosts => ["10.2.1.64:9200","10.2.1.65:9200","10.27.2.1:9200"] index => "qa_log" }}
注意這里的 index 名稱 qa_log 必須是小寫,不然寫入 es 時會報錯。
3.5 完整配置
logstah 配置文件內容如下:
input { beats { port => 9900 }}filter { multiline { pattern => "^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}.\d{3}" negate => true what => "previous" } grok { match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s+\[(?.*)\]\s+(?\w*)\s{1,2}+(?\S*)\s+-\s+(?.*)\s*"] match => [ "message", "(?\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s{1,2}+(?\w*)\s{1,2}+.\s---+\s\[(?.*)\]+\s(?\S*)\s*:+\s(?.*)\s*"] match => [ "source", "/home/passjava/logs/(?\w+)/.*.log" ] overwrite => [ "source"] break_on_match => false } mutate { convert => { "bytes" => "integer" } remove_field => ["agent","message","@version", "tags", "ecs", "_score", "input", "[log][offset]"] } useragent { source => "user_agent" target => "useragent" } date { match => ["logTime", "MMM d HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"] timezone => "Asia/Shanghai" }}output { stdout { } elasticsearch { hosts => ["10.2.1.64:9200","10.2.1.65:9200","10.2.1.66:9200"] index => "qa_log" }}
四、Logstash 怎么跑起來的
4.1 Logstash 如何運行的
你會好奇 Logstash 是怎么運行起來的嗎?
官方提供的啟動方式是執(zhí)行 logstash -f weblog.conf 命令來啟動,當執(zhí)行這個命令的時候其實會調用 Java 命令,以及設置 java 啟動參數,然后傳入了一個配置文件 weblog.conf 來啟動 Logstash。
cd /home/logstash-7.6.2sudo ./bin/logstash -f weblog.conf
當啟動完之后,我們通過命令來看下 Logstash 的運行狀態(tài)
ps -ef | grep logstash
執(zhí)行結果如下圖所示,可以看到用到了 Java 命令,設置了 JVM 參數,用到了 Logstash 的 JAR 包,傳入了參數。
所以建議 Logstash 單獨部署到一臺服務器上,避免服務器的資源被 Logstash 占用。
Logstash 默認的 JVM 配置是 -Xms1g -Xmx1g,表示分配的最小和最大堆內存大小為 1 G。
那么這個參數是在哪里配置的呢?全局搜索下 Xms1g,找到是在這個文件里面配置的,config\jvm.options,我們可以修改這里面的 JVM 配置。
我們可以調整 Logstash 的 JVM 啟動參數,來優(yōu)化 Logstash 的性能。
另外 Kibana 上面還可以監(jiān)控 Logstash 的運行狀態(tài)(不在本篇討論范圍)。
4.2 Logstash 的架構原理
本內容參考這篇 Logstash 架構 5
Logstash 有多個 input,每個 input 都會有自己的 codec。
數據會先存放到 Queue 中,Logstash 會把 Queue 中的數據分發(fā)到不同的 pipeline 中。
然后每一個 pipeline 由 Batcher、filter、output 組成
Batcher 的作用是批量地從 Queue 中取數據。Batcher 可以配置為一次取一百個數據。
五、Logstash 宕機風險
5.1 Logstash 單點部署的風險
因為 Logstash 是單點部署到一臺服務器上,所以會存在兩個風險:
logstash 突然崩了怎么辦?logstash 所在的機器宕機了怎么辦?Logstash 所在的機器重啟了怎么辦?對于第一個問題,可以安裝 Keepalived 軟件來保證高可用。另外即使沒有安裝,當手動啟動 Logstash 后,Logstash 也能將未及時同步的日志寫入到 ES。
對于第二個問題,所在的機器宕機了,那可以通過安裝兩套 Logstash,通過 keepalived 提供的虛擬 IP 功能,切換流量到另外一個 Logstash。關于如何使用 Keepalived,可以參考之前的 實戰(zhàn) MySQL 高可用架構
對于第三個問題,就是把啟動 Logstash 的命令放到開機啟動腳本中就可以了,但是存在以下問題:
Ubuntu 18.04 版本是沒有開機啟動文件的Logstash 無法找到 Java 運行環(huán)境接下來我們來看下怎么進行配置開機自啟動 Logstash。
5.2 開機啟動 Logstash
5.2.1 創(chuàng)建自動啟動腳本
建立 rc-local.service 文件
sudo vim /etc/systemd/system/rc-local.service
將下列內容復制進 rc-local.service 文件
[Unit]Description=/etc/rc.local CompatibilityConditionPathExists=/etc/rc.local [Service]Type=forkingExecStart=/etc/rc.local startTimeoutSec=0StandardOutput=ttyRemainAfterExit=yesSysVStartPriority=99 [Install]WantedBy=multi-user.target
創(chuàng)建文件 rc.local
sudo vim /etc/rc.local
添加啟動腳本到啟動文件中
#!/bin/sh -e# 啟動 logstash#nohup /home/software/logstash-7.6.2/bin/logstash -f /home/software/logstash-7.6.2/weblog.conf 啟動 filebeatnohup /home/software/filebeat-7.6.2-linux-x86_64/filebeat -e -c /home/software/filebeat-7.6.2-linux-x86_64/config.yml &exit 0
5.2.2 修改 Java 運行環(huán)境
因在開機啟動中,logstash 找不到 java 的運行環(huán)境,所以需要手動配置下 logstash。
cd /home/software/logstash-7.6.2/bin/sudo vim logstash.lib.sh
在 setup_java() 方法的第一行加入 JAVA_HOME 變量,JAVA_HOME 的路徑需要根據自己的 java 安裝目錄來。
JAVA_HOME="/opt/java/jdk1.8.0_181"
修改 Java 運行環(huán)境
5.2.3 權限問題
給 rc.local 加上權限, 啟用服務
sudo chmod +x /etc/rc.localsudo systemctl enable rc-localsudo systemctl stop rc-local.servicesudo systemctl start rc-local.servicesudo systemctl status rc-local.service
Logstash 啟動成功
然后重啟機器,查看 logstash 進程是否正在運行,看到一大串 java 運行的命令則表示 logstash 正在運行。
ps -ef | grep logstash
六、總結
本篇講解了 Logstash 在集群環(huán)境下的部署架構圖、Logstash 遇到的幾大坑、以及 Logstash 的運行機制和架構原理。
Logstash 還是非常強大的,有很多功能未在本篇進行講解,本篇也是拋磚引玉,感興趣的讀者朋友們可以加我好友 passjava 共同探索。
更多好文請查看:
實戰(zhàn) MySQL 高可用架構
一文帶你搭建一套 ELK Stack 日志平臺
巨人的肩膀
https://blog.csdn.net/xzk9381/article/details/109571087https://www.elastic.co/guide/en/beats/filebeat/current/multiline-examples.htmlhttps://github.com/logstash-plugins/logstash-patterns-core/blob/main/patterns/ecs-v1/grok-patternshttps://www.runoob.com/regexp/regexp-syntax.htmlhttps://www.elastic.co/guide/en/beats/libbeat/current/config-file-permissions.htmlhttps://www.tutorialspoint.com/logstash/logstash_supported_outputs.htm
參考資料
1input 插件列表: https://www.elastic.co/guide/en/logstash/current/input-plugins.html
2multiline 官方文檔: https://www.elastic.co/guide/en/logstash/current/plugins-codecs-multiline.html#plugins-codecs-multiline-negate
3Filebeat 官方文檔: https://www.elastic.co/guide/en/beats/filebeat/current/multiline-examples.html
4Mutate 參考文章: https://blog.csdn.net/UbuntuTouch/article/details/106466873
5Logstash 架構: https://jenrey.blog.csdn.net/article/details/107122930
END -