3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDK で Infrastructure as Code する: Fluent Bit編1。ローカルのDockerで疎通確認

Last updated at Posted at 2025-08-25

さてさて前回 AWS CDK で Infrastructure as Code する: SpringBoot編2 までで

  • SpringBootのDockerイメージを作成して
  • ECRとかAWS環境を整備して
  • CDKを使ってECSにデプロイする

などをやりましたが、今回は Fluent Bit についてです。

Fluent Bit ってなに??って話ですが、いわゆるログを収集・処理・転送するためのツールです。AWS では 「ECSで動くアプリが出力するログ(標準出力とかに出してるログ)」をCloudWatch LogsやS3やKinesisなどに転送(保存)するために使われてます。

CloudWatch Logsに送るだけなら特別な設定をしなくてもでデフォルトでそうなったりするのですが「エラーログだけはCloudWatch Logsにおくる1」とか「その他のログは課金の安いS3へ送る」とかカスタマイズしようとするとFluent Bitなどが必要になってきます。

記事の前提条件・前提知識

Fluent Bit 使い方概要

img

(画像は公式サイト より引用させてもらいました)

定石の使い方では、アプリのECSの横に「サイドカー」としてFluent Bitのコンテナを配置し、そのサイドカーにログの処理をお願いする、みたいな方式になります。

もうすこし具体的に書くと、まず前回の記事のようなHelloworld 程度の ECSのタスク定義では、下記のようにFluent Bitなどがでてこない記述をしていました。

logConfiguration: {
  logDriver: 'awslogs',
  options: {
    'awslogs-create-group': 'true',
    'awslogs-group': `/ecs/app-taskdefinition`,
    'awslogs-region': `${region}`,
    'awslogs-stream-prefix': 'ecs',
  },
},

このように logDriverを 「awslogs」 と指定していますが、こうするとECSアプリのコンソールログは直接 Cloudwatch Logsの上記のロググループに送信されます。

Fluent Bitを使うばあいはこのへんの記述を「ログをサイドカーに送るよ」という記述、

 "logConfiguration": {
   "logDriver": "awsfirelens",
   "options": {}
 },

logDriverを awsfirelens にし、さらに下記のようにサイドカー用のコンテナを合わせて設定しておきます。

  "containerDefinitions": [
    {
      "name": "log_router",
      "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:latest",
      ...
      },
      "firelensConfiguration": {
        "type": "fluentbit",
        "options": {
          "config-file-type": "file",
          "config-file-value": "/fluent-bit/etc/fluent-bit-custom.conf"
        }
      }
    },

で、実際のログの送信先とかは上記で指定した設定ファイル fluent-bit-custom.conf でおこなう、そんな仕組みです。

やってみる

AWS ECS上でいろいろ動かそうとするとそれなりに環境設定が必要なので、今回はローカルのDocker上で挙動を確認してみます。
この場合もちろん、Fluent Bit から他のサービス( CloudWatch Logs、S3、Kinesisなど) への稼働確認はできないのですが、アプリが出力したログがFluent Bitにどのように連携されて、どう加工されたかを見るには十分な環境だと思います。

fig1.png

環境の確認とソースの取得

JavaなどPC上のインフラ環境はこんな感じ。手元の環境はLinux(Ubuntu) でしたが、WSLでも問題なく動くはずです2

$ java --version
openjdk 21.0.7 2025-04-15 LTS
OpenJDK Runtime Environment Temurin-21.0.7+6 (build 21.0.7+6-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.7+6 (build 21.0.7+6-LTS, mixed mode, sharing)

$ mvn --version
Apache Maven 3.9.2 (c9616018c7a021c1c39be70fb2843d6f5f9b8a1c)
Maven home: /opt/JavaTools/apache-maven-3.9.2
Java version: 21.0.7, vendor: Eclipse Adoptium, runtime: /home/sysmgr/.sdkman/candidates/java/21.0.7-tem
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-216-generic", arch: "amd64", family: "unix"

$ docker --version
Docker version 24.0.2, build cb74dfc
$ 

DockerでSpringBootを起動する

アプリはいままでの記事でも使っているspring-boot-sample-tomcat を使います。このアプリの横(?)にFluent Bitのコンテナを作成し、アプリのログをFluent Bitに流し込むようにします。

特に理由はないですが、今回は docker compose をつかってSpringBootを起動することにしました。

$ git clone --branch fluentbit  https://github.com/masatomix/spring-boot-sample-tomcat.git
$ cd spring-boot-sample-tomcat/
spring-boot-sample-tomcat $ mvn package

...

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.745 s
[INFO] Finished at: 2025-08-24T13:47:53Z
[INFO] ------------------------------------------------------------------------

ここまででまず、Dockerのイメージに入れるjarファイルができました。今後ソースを修正した場合はjarの再作成を忘れないようにしてください。

つづいて docker compose の設定を見ておきましょう。まず docker-compose.yaml は以下の通り。

  • ./docker-compose.yaml
version: "3.7"

services:
  spring-boot-sample-tomcat:
    build:
      context: .
      dockerfile: Dockerfile
      platforms:
        - "linux/amd64"
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: production

環境設定で、プロファイルを「production」に指定しています。
SpringBoot側にはあらかじめ、プロファイルがproductionの場合はログ出力がJSON形式になるような設定が入れてあります。
参考: ログのフォーマットを環境によって変更したい

つづいてDockerfileです。

  • ./Dockerfile
FROM openjdk:17-alpine
VOLUME /tmp

COPY target/app.jar app.jar
EXPOSE 8080

ENTRYPOINT ["java","-jar","/app.jar"]

特に前回と変わったところはなく、さきほどの mvn packageで生成されたjarを指定しているだけです。

SpringBootを起動します。

spring-boot-sample-tomcat$  docker compose up
[+] Building 0.0s (0/0)
[+] Running 1/0
 ✔ Container spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  Created                               
Attaching to spring-boot-sample-tomcat-spring-boot-sample-tomcat-1
...
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |   .   ____          _            __ _ _
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |   '  |____| .__|_| |_|_| |_\__, | / / / /
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |  =========|_|==============|___/=/_/_/_/
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |  :: Spring Boot ::                (v3.1.2)
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  |
...
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  | {"@timestamp":"2025-08-24T13:54:59.814799039Z","@version":"1","message":"Tomcat started on port(s): 8080 (http) with context path ''","logger_name":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer","thread_name":"main","level":"INFO","level_value":20000}
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  | {"@timestamp":"2025-08-24T13:54:59.82463053Z","@version":"1","message":"Started SampleTomcatApplication in 3.178 seconds (process running for 3.648)","logger_name":"nu.mine.kino.SampleTomcatApplication","thread_name":"main","level":"INFO","level_value":20000}

起動できたので、アクセスしてみましょう。

$ curl 'http://localhost:8080/echoLogger' -i
HTTP/1.1 200
vary: accept-encoding
Content-Type: text/plain;charset=UTF-8
Content-Length: 16
Date: Sun, 24 Aug 2025 14:26:26 GMT

Default Message.
$

起動できましたね。サーバ上(Docker composeしたコンソール)にも下記のようなJSON形式のログが出力されたと思います。

spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  | {"@timestamp":"2025-08-24T15:12:23.322356857Z","@version":"1","message":"hostname: 6468584bff0a","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-6","level":"INFO","level_value":20000}
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1  | {"@timestamp":"2025-08-24T15:12:23.322824362Z","@version":"1","message":"Default Message.","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-6","level":"INFO","level_value":20000}

Fluent Bit の設定を修正する

さて、取得したgit のブランチには Fluent BitのDockerの設定も入れてあるので、Fluent BitもDockerから起動してみます。
別のコンソールを使って以下を実行していきます。

spring-boot-sample-tomcat$ docker ps
CONTAINER ID   IMAGE                                                 COMMAND                CREATED       STATUS          PORTS                                       NAMES
6468584bff0a   spring-boot-sample-tomcat-spring-boot-sample-tomcat   "java -jar /app.jar"   7 hours ago   Up 53 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   spring-boot-sample-tomcat-spring-boot-sample-tomcat-1

SpringBootのログファイルを特定するために CONTAINER IDが必要になるので、psで調べています。具体的には、Dockerのコンテナごとのログファイルは/var/lib/docker/containers/[CONTAINER ID]*/*-json.log に記録されますので、その値つまり6468584bff0aの値をメモしておきましょう。

次にFluent Bit の設定ファイルに、先ほどのIDを記述します。具体的には下記の箇所です。

sysmgr@vscode-server:/tmp/spring-boot-sample-tomcat$ cd fluentbit/test/
sysmgr@vscode-server:/tmp/spring-boot-sample-tomcat/fluentbit/test$ cat fluent-bit-test.conf
[SERVICE]
    Flush 1
    Log_Level info
    Parsers_File /fluent-bit/etc/parsers.conf
... 省略

[INPUT]
    Name   tail
    Tag    -firelens-
    Path   /var/lib/docker/containers/6468584bff0a*/*-json.log    <- ここを先のIDに変更しておく
    parser docker
... 省略

$

今回はこのように、Docker上のコンテナが出力するコンソールログファイルを[INPUT]に設定して、Fluent Bitにアプリのログが流れ込んでくるようにしました3

また一応ですが、この[INPUT]を使ってアプリのログをFluent Bitに渡すのは今回がローカル環境だからで、AWS ECS上では AWS側(の FireLens機能 )にお任せすることになります。この辺は続きでまた記事にしようと思います。

Fluent Bitの設定を少し見てみる

このタイミングですこしFluent Bitの設定を見てみます。

  • INPUTのセクション
[INPUT]
    Name   tail
    Tag    -firelens-
    Path   /var/lib/docker/containers/6468584bff0a*/*-json.log
    parser docker

先ほど見たとおり、Fluent Bitに転送されてくる(Fluent Bitが読み込む) アプリのログファイルを指定しています。また、データ種類を識別するために Tagをつけるのですが、Tag名を-firelens- としています4

  • FILTERのセクション
[FILTER]
    Name     parser
    Match    *-firelens-*
    Key_Name log
    Parser   json
    # Preserve_Key ON
    Reserve_Data ON

今回説明は割愛しますが、-firelens- というTagがついたJSONデータのlogというプロパティの値を取り出す、みたいなことをやっています。

参考: https://docs.fluentbit.io/manual/pipeline/filters/parser

[FILTER]
    Name          rewrite_tag
    Match         *-firelens-*
    Rule          $level (ERROR) error-$container_id true

-firelens- というTagがついたJSONデータについて、 levelプロパティがERRORという文字の場合にTag名を「error-$container_id」に変更しています。ERRORログについてはTag名を変更して識別できるようにしているってことですね。

末尾の true は、既存データを元Tagにも残し、新Tag名のデータはコピーで作成する、みたいな意味です。エラーログの取扱を完全に分けたい場合は falseにしておきましょう。

  • OUTPUT のセクション
[OUTPUT]
    Name file
    Match error*
    Path /fluent-bit/etc/output_dir

tag名がerrorで始まるデータについて(さきほどERRORログですね)、/fluent-bit/etc/output_dir/tag名 ファイルに出力します。

[OUTPUT]
    Name   stdout
    Match  *

全てのtagのデータについて、Fluent Bitのコンソールにデータを出力します。

などなどがFluent Bitがやってくれる機能です。流れてくるデータに対して、通常はコンソールに出力しつつ、エラーログだけ別の宛先に転送している、などのイメージがつかめればよいとおもいます。

Docker で Fluent Bitを起動、正常系ログを確認

docker-compose.yaml も、少しだけみてみます。

spring-boot-sample-tomcat/fluentbit/test$ cat docker-compose.yaml
version: "3.7"

services:
  fluent-bit:
    image: amazon/aws-for-fluent-bit:latest
    volumes:
      - ./fluent-bit-test.conf:/fluent-bit/etc/fluent-bit.conf
      - ./input.txt:/fluent-bit/etc/input.txt
      - ./output_dir:/fluent-bit/etc/output_dir
      - /var/lib/docker/containers:/var/lib/docker/containers:ro

AWSが用意してくれているFluent Bitのイメージを利用しつつ、./output_dir:/fluent-bit/etc/output_dir の記述で、./output_dir のディレクトリがコンテナ内の/fluent-bit/etc/output_dirのディレクトリにマウントされるようにしてあります。

さて、Fluent Bit側のコンテナも起動します。

spring-boot-sample-tomcat/fluentbit/test$ docker compose up
[+] Building 0.0s (0/0)
[+] Running 1/0
 ✔ Container test-fluent-bit-1  Created                                                                                                                                                                                                     0.0s
Attaching to test-fluent-bit-1
test-fluent-bit-1  | Fluent Bit v1.9.10
test-fluent-bit-1  | * Copyright (C) 2015-2022 The Fluent Bit Authors
test-fluent-bit-1  | * Fluent Bit is a CNCF sub-project under the umbrella of Fluentd
test-fluent-bit-1  | * https://fluentbit.io
test-fluent-bit-1  |
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [fluent bit] version=1.9.10, commit=a2eaf59628, pid=1
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [storage] version=1.4.0, type=memory-only, sync=normal, checksum=disabled, max_chunks_up=128
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [cmetrics] version=0.3.7
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [sp] stream processor started
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [input:tail:tail.0] inotify_fs_add(): inode=1100314 watch_fd=1 name=/var/lib/docker/containers/7c9d4cfb1ace6faeda294fb7c38ee95ea8b737dd7bd0e945fe00be721b1e7b81/7c9d4cfb1ace6faeda294fb7c38ee95ea8b737dd7bd0e945fe00be721b1e7b81-json.log
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [output:stdout:stdout.1] worker #0 started
test-fluent-bit-1  | [2025/08/24 15:10:41] [ info] [output:file:file.0] worker #0 started

サイドカーとなるコンテナが起動したので、再度疎通をしてみましょう。

$ curl 'http://localhost:8080/echoLogger' -i
HTTP/1.1 200
vary: accept-encoding
Content-Type: text/plain;charset=UTF-8
Content-Length: 16
Date: Sun, 24 Aug 2025 15:57:39 GMT

Default Message.
$

SpringBootのコンソールとは別に、Fluent Bit側のコンテナのコンソールにも

test-fluent-bit-1  | [0] -firelens-: [1756048334.786843777, {"@timestamp"=>"2025-08-24T15:12:14.786605637Z", "@version"=>"1", "message"=>"hostname: 7c9d4cfb1ace", "logger_name"=>"nu.mine.kino.web.EchoController", "thread_name"=>"http-nio-8080-exec-4", "level"=>"INFO", "level_value"=>20000, "stream"=>"stdout", "time"=>"2025-08-24T15:12:14.786843777Z"}]
test-fluent-bit-1  | [1] -firelens-: [1756048334.788296016, {"@timestamp"=>"2025-08-24T15:12:14.787033992Z", "@version"=>"1", "message"=>"Default Message.", "logger_name"=>"nu.mine.kino.web.EchoController", "thread_name"=>"http-nio-8080-exec-4", "level"=>"INFO", "level_value"=>20000, "stream"=>"stdout", "time"=>"2025-08-24T15:12:14.788296016Z"}]

などが出力されたと思います。よさそうですね。

エラーログは別のファイルに出力される

続いてエラーが発生するリクエストを投げてみます。細かい説明は割愛しますが、下記で呼ばれるAPIはLog LevelがERRORのログが出力されるようにコーディングしてあります。

$ curl 'http://localhost:8080/error-sim/throw-exception' -i
HTTP/1.1 500
vary: accept-encoding
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 24 Aug 2025 15:21:40 GMT
Connection: close

{"timestamp":"2025-08-24T15:21:40.092+00:00","status":500,"error":"Internal Server Error","path":"/error-sim/throw-exception"}
$

エラーが返ってきました。

先ほどFluent Bitの設定で見たとおり、エラーは別のログファイル ( コンテナ内だと/fluent-bit/etc/output_dir/、外の世界では./output_dir) に出力する設定だったので、それを見てみます。

spring-boot-sample-tomcat/fluentbit/test$ tail ./output_dir/error-
error-: [1756048900.091458643, {"@timestamp":"2025-08-24T15:21:40.090477238Z","@version":"1","message":"Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: わざと例外をスローしました] with root cause","logger_name":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]","thread_name":"http-nio-8080-exec-1","level":"ERROR","level_value":40000,"stack_trace":"java.lang.RuntimeException: わざと例外をスローしました\n\tat nu.mine.kino.web.ErrorController.throwException(ErrorController.java:42)\n\tat ...java.base/java.lang.Thread.run(Thread.java:831)\n","stream":"stdout","time":"2025-08-24T15:21:40.091458643Z"}]
spring-boot-sample-tomcat$

エラーログだけ、別のファイルにちゃんと転送されていることが確認できました。

おつかれさまでした。

まとめ

Fluent Bitなどのログ転送とかって、なんとなくつかみどころがなくてとっつきにくい機構ではありますが、手元で動かすことである程度イメージをつかむことはできたでしょうか。

さて、いったんまとめます。

  • ローカルで動いているDocker上でFluent Bitの挙動を確認しました。
    • Dockerのコンテナ単位のログファイルの情報を指定してあげることで、ECS上でなくローカルのDocker環境でもFluent Bitの稼働確認を行うことができました。
  • ログデータのtagをうまく使い分けることで、転送先を複数指定できることを確認しました。
    • AWS ECSの標準機能では、こういう振り分けはできなかったですね
  • 今回は割愛しましたが、[FILTER]セクションでparserを用いる事で、ネストされたログデータを抽出したり、などができたりしますが、この辺はまたの機会に説明したいと思います!

お疲れさまでしたー

関連リンク

  1. さらにはそのエラーログ用のロググループの出力カウント数をCloudWatchメトリクスに設定して(CloudWatch Logsのメトリクスフィルタ機能を使う)、そのメトリクスがカウントアップしたら通知する(CloudWatch Alarms機能)、とかやったりします。

  2. MacのDockerだけは、ログファイルの場所が仕様が違うようで、正しく動かすことができませんでした、、。

  3. この[INPUT]で入力データを指定する方式を使うことで、任意のデータをFluent Bitに渡して挙動を確認することができたりします。具体的には[INPUT] で所定のファイル名を指定して、そのファイルにFluent Bitに渡したいログデータを書き込んで保存、とかやる感じです。

  4. AWS上でFirelens経由でFluent Bitに流れてくるデータのtag名がコレなので、それにあわせた

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?