手把手教程:使用 Fluentbit 採集夜鶯日誌寫入 ElasticSearch

SRETALK發表於2024-11-05

Fluentbit 是非常流行的日誌採集器,作為 Fluentd 的子專案,是 CNCF 主推的專案,本文以夜鶯的日誌舉例,使用 Fluentbit 採集,並直接寫入 ElasticSearch,最終使用 Kibana 檢視。藉此實踐過程,讓讀者熟悉 Fluentbit 的使用。

測試環境介紹

  • Macbook M1 晶片
  • ElasticSearch、Kibana 7.15.0,使用 Docker compose 啟動
  • Nightingale 7.5.0,使用 Docker compose 啟動
  • Fluentbit 3.1.9,使用 Homebrew 安裝

如果你使用 X86 的 Linux,整個過程會更簡單,而我這裡使用的是 Macbook M1 晶片,有些映象就不太好搞。下面,我們開始準備環境。

準備 ElasticSearch 和 Kibana

下面是我使用的 docker-compose.yml 檔案:

networks:
  elk:
    driver: bridge

services:
  elasticsearch:
    networks:
      - elk
    image: docker.elastic.co/elasticsearch/elasticsearch:7.15.0
    container_name: elk-es
    restart: always
    environment:
      # 開啟記憶體鎖定
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      # 指定單節點啟動
      - discovery.type=single-node
    ulimits:
      # 取消記憶體相關限制  用於開啟記憶體鎖定
      memlock:
        soft: -1
        hard: -1
    volumes:
      - ./data:/usr/share/elasticsearch/data
      - ./logs:/usr/share/elasticsearch/logs
      - ./plugins:/usr/share/elasticsearch/plugins
      - ./config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
    ports:
      - 9200:9200

  kibana:
    networks:
      - elk
    image: docker.elastic.co/kibana/kibana:7.15.0
    container_name: elk-kibana
    volumes:
      - ./config/kibana.yml:/usr/share/kibana/config/kibana.yml
    restart: always
    environment:
      ELASTICSEARCH_HOSTS: http://elk-es:9200
      I18N_LOCALE: zh-CN
    ports:
      - 5601:5601

這個 Docker compose 檔案是從網上隨便找的,唯一做了一個修改是把 elasticsearch.yml 和 kibana.yml 掛載到了本地,方便修改配置。

elasticsearch.yml 內容如下:

cluster.name: "docker-cluster"
network.host: 0.0.0.0
xpack.security.enabled: true

kibana.yml 內容如下:

server.host: "0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://elasticsearch:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
elasticsearch.username: "kibana_system"
elasticsearch.password: "MhxzKhl"

這裡設定了一個使用者名稱和密碼,怎麼配置這些密碼呢?進入 elasticsearch 容器,執行如下命令:

bin/elasticsearch-setup-passwords interactive

然後,根據提示設定密碼即可。

之後,透過 elastic 賬號登入 Kibana 做個測試,比如進入 DevTools,看一下索引資訊:

indices

說明都是正常的。

準備 Nightingale

從 Nightingale 的 github releases 頁面下載 7.5.0 版本的 release 包,解壓後,進入 docker/compose-bridge 目錄。

本來呢,直接使用 docker-compose up -d 就啟動了,夜鶯的日誌會預設打到 stdout,但是這次為了測試演示 Fluentbit,這裡我會調整一下配置,讓夜鶯的日誌打到具體的檔案中。然後透過 volume 掛載到宿主上。

  1. docker/compose-bridge 目錄下建立一個 n9e-logs 目錄
  2. 修改 docker-compose.yaml,調整 nightingale 這個 service,增加 volume 掛載:
  nightingale:
    image: flashcatcloud/nightingale:latest
    container_name: nightingale
    hostname: nightingale
    restart: always
    environment:
      GIN_MODE: release
      TZ: Asia/Shanghai
      WAIT_HOSTS: mysql:3306, redis:6379
    volumes:
      - ./etc-nightingale:/app/etc
      - ./n9e-logs:/app/logs
    networks:
      - nightingale
    ports:
      - "17000:17000"
      - "20090:20090"
    depends_on:
      - mysql
      - redis
      - victoriametrics
    command: >
      sh -c "/app/n9e"

volumes 下面本來只是掛載了一個配置檔案目錄,現在增加了一個 n9e-logs 目錄。

  1. 修改 etc-nightingale/config.toml 配置檔案,調整 Log 部分如下所示:
[Log]
# log write dir
Dir = "/app/logs"
# log level: DEBUG INFO WARNING ERROR
Level = "DEBUG"
# stdout, stderr, file
Output = "file"
# # rotate by time
KeepHours = 4
# # rotate by size
# RotateNum = 3
# # unit: MB
# RotateSize = 256

Output 從 stdout 改為 file,這樣日誌就會寫到檔案中。寫到哪個目錄呢?由 Dir 指定。日誌檔案會按小時切割,最多保留 KeepHours 個小時的日誌。

  1. 啟動 Nightingale:
cd docker/compose-bridge
docker-compose up -d

這樣,Nightingale 就啟動了,日誌會寫到 n9e-logs 目錄中。我的環境如下:

ulric@ulric-flashcat n9e-logs % ll
total 29088
drwxr-xr-x  32 ulric  staff     1024 10 25 09:00 .
drwxr-xr-x   9 ulric  staff      288 10 25 09:23 ..
-rw-r--r--   1 ulric  staff        0 10 25 09:00 ALL.log
-rw-r--r--   1 ulric  staff        0 10 25 05:00 ALL.log.2024102505
-rw-r--r--   1 ulric  staff        0 10 25 06:00 ALL.log.2024102506
-rw-r--r--   1 ulric  staff        0 10 25 07:00 ALL.log.2024102507
-rw-r--r--   1 ulric  staff        0 10 25 08:00 ALL.log.2024102508
-rw-r--r--   1 ulric  staff  1325106 10 25 09:26 DEBUG.log
-rw-r--r--   1 ulric  staff  3054850 10 25 06:00 DEBUG.log.2024102505
-rw-r--r--   1 ulric  staff  3037797 10 25 07:00 DEBUG.log.2024102506
-rw-r--r--   1 ulric  staff  3038315 10 25 08:00 DEBUG.log.2024102507
-rw-r--r--   1 ulric  staff  3037741 10 25 09:00 DEBUG.log.2024102508
-rw-r--r--   1 ulric  staff        0 10 25 09:00 ERROR.log
-rw-r--r--   1 ulric  staff        0 10 25 05:00 ERROR.log.2024102505
-rw-r--r--   1 ulric  staff        0 10 25 06:00 ERROR.log.2024102506
-rw-r--r--   1 ulric  staff        0 10 25 07:00 ERROR.log.2024102507
-rw-r--r--   1 ulric  staff        0 10 25 08:00 ERROR.log.2024102508
-rw-r--r--   1 ulric  staff        0 10 25 09:00 FATAL.log
-rw-r--r--   1 ulric  staff        0 10 25 05:00 FATAL.log.2024102505
-rw-r--r--   1 ulric  staff        0 10 25 06:00 FATAL.log.2024102506
-rw-r--r--   1 ulric  staff        0 10 25 07:00 FATAL.log.2024102507
-rw-r--r--   1 ulric  staff        0 10 25 08:00 FATAL.log.2024102508
-rw-r--r--   1 ulric  staff    17394 10 25 09:26 INFO.log
-rw-r--r--   1 ulric  staff    44614 10 25 06:00 INFO.log.2024102505
-rw-r--r--   1 ulric  staff    40707 10 25 06:59 INFO.log.2024102506
-rw-r--r--   1 ulric  staff    40815 10 25 07:59 INFO.log.2024102507
-rw-r--r--   1 ulric  staff    41242 10 25 08:59 INFO.log.2024102508
-rw-r--r--   1 ulric  staff        0 10 25 09:00 WARNING.log
-rw-r--r--   1 ulric  staff        0 10 25 05:00 WARNING.log.2024102505
-rw-r--r--   1 ulric  staff        0 10 25 06:00 WARNING.log.2024102506
-rw-r--r--   1 ulric  staff        0 10 25 07:00 WARNING.log.2024102507
-rw-r--r--   1 ulric  staff        0 10 25 08:00 WARNING.log.2024102508

配置 Fluentbit

我是 Macbook 的環境,直接透過 Homebrew 安裝 Fluentbit:

brew install fluent-bit

接下來我們準備 Fluentbit 的配置檔案,希望達成的效果是:Fluentbit 從 Nightingale 的日誌檔案中讀取日誌,做 ETL,然後寫入 ElasticSearch。這裡我會拆成兩個配置檔案:

  • fluent-bit-n9e.conf Fluentbit 主配置檔案
  • fluent-bit-n9e-parser.conf Fluentbit Parser 配置檔案

Fluentbit Parser 配置檔案

fluent-bit-n9e-parser.conf 內容如下:

[MULTILINE_PARSER]
  name          multiline-regex-n9e
  type          regex
  flush_timeout 1000
  #
  # Regex rules for multiline parsing
  # ---------------------------------
  #
  # configuration hints:
  #
  #  - first state always has the name: start_state
  #  - every field in the rule must be inside double quotes
  #
  # rules |   state name  | regex pattern                  | next state
  # ------|---------------|--------------------------------------------
  rule      "start_state"   "/\d+-\d+-\d+ \d+\:\d+\:\d+\.\d+(.*)/"  "cont"
  rule      "cont"          "/^[a-z]+.*$/"                     "cont"

[PARSER]
  Name   regex-n9e
  Format regex
  Regex  /^(?<time>[^ ]* [^ ]*) (?<level>[^ ]*) (?<location>[^ ]*) (?<message>.*)/m
  # 2024-10-24 16:54:24.257468
  Time_Key  time
  Time_Format %Y-%m-%d %H:%M:%S.%L
  Time_Offset +0800

這裡配置了一個普通 PARSER,一個 MULTILINE_PARSER。用於解析 Nightingale 的日誌。為了讓大家有個直觀的感受,我們貼幾行 Nightingale 的日誌:

ulric@ulric-flashcat n9e-logs % tail -n 1 INFO.log; tail -n 1 DEBUG.log
2024-10-25 09:33:43.194922 INFO memsto/target_cache.go:182 timer: sync targets done, cost: 2ms, number: 2
2024-10-25 09:33:40.905035 DEBUG process/process.go:464 rule_eval:alert-1-28 event:&{Id:0 Cate:prometheus Cluster:vm01 DatasourceId:1 GroupId:3 GroupName:雲平臺-DevOps Hash:bbbfba0fecf94cd8a517998234b97535 RuleId:28 RuleName:test RuleNote: RuleProd:metric RuleAlgo: Severity:2 PromForDuration:0 PromQl:diskio_io_time != -1 RuleConfig:{"queries":[{"keys":{"labelKey":"","metricKey":"","valueKey":""},"prom_ql":"diskio_io_time != -1","severity":2}]} RuleConfigJson:map[queries:[map[keys:map[labelKey: metricKey: valueKey:] prom_ql:diskio_io_time != -1 severity:2]]] PromEvalInterval:10 Callbacks: CallbacksJSON:[] RunbookUrl: NotifyRecovered:1 NotifyChannels:dingtalk email NotifyChannelsJSON:[dingtalk email] NotifyGroups:1 NotifyGroupsJSON:[1] NotifyGroupsObj:[] TargetIdent:categraf01 TargetNote: TriggerTime:1729820020 TriggerValue:0 TriggerValues: Tags:__name__=diskio_io_time,,ident=categraf01,,name=zram0,,rulename=test,,source=categraf TagsJSON:[__name__=diskio_io_time ident=categraf01 name=zram0 rulename=test source=categraf] TagsMap:map[__name__:diskio_io_time ident:categraf01 name:zram0 rulename:test source:categraf] OriginalTags: OriginalTagsJSON:[] Annotations:{} AnnotationsJSON:map[] IsRecovered:false NotifyUsersObj:[] LastEvalTime:1729820020 LastSentTime:0 NotifyCurNumber:0 FirstTriggerTime:0 ExtraConfig:<nil> Status:0 Claimant: SubRuleId:0 ExtraInfo:[] Target:0x400441c140 RecoverConfig:{JudgeType:0 RecoverExp:}} fire

上面是讀取了一行 DEBUG 日誌,一行 INFO 日誌。可以看出來,夜鶯的日誌第一個欄位是時間,第二個欄位是日誌級別,第三個欄位是位置(列印日誌的檔案和行號),第四個欄位是日誌內容。

我們要使用正規表示式來解析這個日誌,使之從一行原始文字變成一個結構化的 JSON 物件,後面才方便檢索分析。按理說,只需要如下這麼一行正則就可以了:

^(?<time>[^ ]* [^ ]*) (?<level>[^ ]*) (?<location>[^ ]*) (?<message>.*)

但是,夜鶯的告警引擎在做查詢的時候,可能會列印多行日誌,所以不得已,我們還需要一個 MULTILINE_PARSER,用於處理多行日誌,即上面的 MULTILINE_PARSER 部分的配置。MULTILINE_PARSER 關鍵是配置首行正則和次行正則,並且給這兩個正則分別取個名字,一般首行正則取名為 start_state,次行正則取名為 cont

整個邏輯就變成了,Fluentbit 逐行讀取日誌內容,先使用 MULTILINE_PARSER 做多行匹配,這個 MULTILINE_PARSER 僅僅是把原本放到多行的日誌內容拼接成一行,然後再使用 PARSER 做結構化解析,從中提取出時間、日誌級別、位置、日誌內容等欄位。其中 PARSER 的正則後面加了一個 /m,表示啟用多行模式。

由於日誌中沒有體現時區資訊,所以我們在 PARSER 配置中指定了時區為東八區:Time_Offset +0800

Fluentbit 主配置檔案

fluent-bit-n9e.conf 內容如下:

[SERVICE]
  flush           1
  log_level       info
  parsers_file    /Users/ulric/works/tmp/fluent-bit-n9e-parser.conf

[INPUT]
  Name               tail
  Tag                log.n9e
  Path               /Users/ulric/works/n9e.tarball/n9e-v7.4.0-linux-arm64/docker/compose-bridge/n9e-logs/*.log
  Read_from_head     true
  DB                 /Users/ulric/works/tmp/tail_n9e.db
  multiline.parser   multiline-regex-n9e

[FILTER]
  name             parser
  match            log.n9e
  key_name         log
  parser           regex-n9e

[OUTPUT]
  Name es
  Match log.n9e
  Host 127.0.0.1
  Port 9200
  Index n9e-%Y.%m.%d
  # Logstash_Format On
  # Logstash_Prefix nightingale
  # Logstash_DateFormat %Y.%m.%d-%H
  HTTP_User elastic
  HTTP_Passwd MhxzKhl

[OUTPUT]
  Name stdout
  Match log.n9e

首先走 INPUT,INPUT 先採用 multiline-regex-n9e 做多行匹配,然後再走 FILTER,透過 regex-n9e 做結構化解析,最後走 OUTPUT,將解析後的日誌寫入 ElasticSearch。

multiline-regex-n9e 和 regex-n9e 是我們在 fluent-bit-n9e-parser.conf 中定義的兩個 parser。

那個 stdout 的 OUTPUT 不用關注,是我做測試用的。

啟動 Fluentbit

fluent-bit -c fluent-bit-n9e.conf

因為有兩個 OUTPUT,一個是寫往 ElasticSearch,一個是寫往 stdout,所以一旦啟動 Fluentbit,你會看到日誌不斷地列印到 stdout,同時,你可以在 Kibana 中看到索引 n9e-2024.10.25 已經建立,裡面有我們解析後的日誌。去 Kibana 建立一個 n9e* 的索引模式,然後在 Discover 中檢視日誌。

Kibana Discover

一切正常!

總結

本文介紹瞭如何使用 Fluentbit 採集夜鶯的日誌,並寫入 ElasticSearch,最終透過 Kibana 檢視。Fluentbit 是一個非常流行的日誌採集器,本文透過實踐,讓讀者熟悉了 Fluentbit 的使用。

相關文章