Fluent Bit 是主流的 log 收集器之一,典型的用法是收到 log 後再轉發給下一手,廣義的說它不只收 log,也收 metric 等其他資料,這裡直接引用 Fluent Bit 網站的概念圖:

Fluent Bit

發 log 就發 log,為什麼要過一手 Fluent Bit?以我自己而言,每個服務的 log 會集中到 CloudWatch,對於自己寫的程式這沒什麼問題,但對於像是 Traefik、Caddy、Nginx 這類被廣泛使用的服務,它們並不原生支援 CloudWatch,偏偏 web server 的 log 對於稽核或安全方面的分析又很重要,以 Fluent Bit 來收集並轉發就成為主流的解決方案之一。

雖然 AWS 有出 CloudWatch Agent,但我覺得它的目的太過單一、有侷限性,要是哪天想跳槽離開 AWS,又要匆匆忙忙連滾帶爬把它換掉,相較之下支援多輸入/多輸出的 Fluent Bit 是更泛用的選擇。

選用 Fluent Bit 的另一個理由是 Docker logging driver 原生支援 Forward 協議,而 Forward 協議正是 Fluent Bit 團隊開發的,因此採用 Fluent Bit 成為理所當然的選項之一,之所以說「之一」,是因為其他收集器也大多支援 Forward,主流的選手有:

  • Fluent Bit:本文主角
  • Fluentd:Fluent Bit 的前一代,還活著不過推薦改用 Fluent Bit
  • Vector:大廠 DataDog 出的收集器

幾乎每款收集器都標榜自己輕巧不吃資源、處理 log 快吞吐又快、支援各式各樣輸入/輸出,看起來都差不多的情況下挑一套自己看得順眼的就好,也有些人跑了他們之間的評比 benchmark,有的 A 好有的 B 好,參考就好。

配置 Fluent Bit

在使用前必須先寫 Fluent Bit 的配置文件,這裡我們採用較新的 YAML 配置文件,下面是一份簡單的配置文件:

service:
  # Log_Level
  # =========
  # Set the verbosity level of the service, values can be:
  #
  # - error
  # - warning
  # - info
  # - debug
  # - trace
  #
  # by default 'info' is set, that means it includes 'error' and 'warning'.
  # log_level: info

  # Parsers File
  # ============
  # specify an optional 'Parsers' configuration file
  parsers_file: parsers.conf

  # Plugins File
  # ============
  # specify an optional 'Plugins' configuration file to load external plugins.
  plugins_file: plugins.conf

pipeline:
  inputs:
    - name: forward
      port: 24224

  outputs:
    - name: stdout
      match: "*"

    - name: cloudwatch_logs
      match: "*"
      region: ${AWS_DEFAULT_REGION}
      log_group_name: ${LOG_GROUP_NAME}

能夠望文生義的部分就不提,其中 service 區塊是配置 Fluent Bit 本身,旗下的 parsers_file、.plugins_file` 兩項指定的檔案不用自己生,都存在現成的 Fluent Bit 映像內。

Pipeline 指的是資料的輸入、處理、輸出的流程,這裡我們不對資料做處理,所以只有 inputsoutputs

前面提到 Docker logging driver 支援 Forward 協議輸出 log,在 Fluent Bit 這邊,就要設定收 Forward log 的 input,設定 input 識別名 forward 與 port 24224 就可以了,非常簡單。

Output 的話就是 stdout 和 CloudWatch,stdout output 的識別名就是 stdoutmatch: "*" 就是不比對(不過濾)。

CloudWatch output 配置項目比較多一點,但也滿好懂,其中 regionlog_group_name 的值取自 OS 環境變數。

要打資料給 AWS CloudWatch 還需要 AWS 認證,最簡單的方法是在 OS 環境變數中就設好 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY,CloutWatch output 會自己使用,或者是讓運行 Fluent Bit 的 EC2 本身就有具備 CloudWatch permission 的 IAM role,要採用哪種取決於你有多追求把安全性無限上綱以及有多想被 AWS vendor-lock。

配置完存成檔名 fluent-bit.yaml,接著來搞容器。

容器 log 到 Fluent Bit

前面提過,自己寫的服務多半可以利用 AWS SDK 直接把 log 打給 CloudWatch,所以我們關注的重點放在怎麼把 Traefik log 打給 Fluent Bit 再打給 CloudWatch。

先看 compose.yaml:

services:
  fluent-bit:
    image: cr.fluentbit.io/fluent/fluent-bit:4.2
    restart: unless-stopped
    ports:
      - "24224:24224" # Forward protocal
    volumes:
      - ./fluent-bit-data/fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml
    command:
      - /fluent-bit/bin/fluent-bit
      - --config=/fluent-bit/etc/fluent-bit.yaml
    environment:
      - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

  traefik:
    image: traefik:v3.6
    restart: unless-stopped
    security_opt:
      - no-new-privileges=true
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    logging:
      driver: fluentd
      options:
        fluentd-address: 127.0.0.1:24224 # macOS: host.docker.internal:24224
        fluentd-async: 'true'
        tag: traefik

這份 compose.yaml 只保留我們關注的重點,省略其他元素。

在 Fluent Bit 方面,我們把前面的 fluent-bit.yaml 掛載進容器內,並自訂 command 讓 Fluent Bit 讀取之。

在 Traefik 方面,我們啟用 Fluentd logging driver,它本質上就是支援 Forward 協議的 logging driver。

前面提到,Fluent Bit 的 Forward input 的 port 為 24224,而 Traefik 的 fluentd-address 不能填 Docker container network 的位址,所以我們把 fluent-bit:24224 映射到實體素主機的 24224,Traefik 的 fluentd-address 則填入 127.0.0.1:24224,如此方可讓 Traefik log 送到 Fluent Bit,如果宿主機是 Mac,那又多了一層 Docker VM,所以得改成 host.docker.internal:24224

fluent-bit 服務作為非主要之週邊服務,它不應成為其他服務的依賴,不應列入其他服務的 depends_on,所以啟用 fluentd-async 來讓 Traefik 能自行啟動並送出 log,不需要關心 Fluent Bit 啟用與否。

至此一套具體而微的 Fluent Bit + Traefik 系統就配置完成了,一套組合拳下來,Traefik log 輸出到容器的 stdout / stderr,然後由 Docker logging driver 轉發給 Fluent Bit,再轉發給 CloudWatch,一筆筆 log 就這麼風塵僕僕的抵達了 CloudWatch。