FireLens でログ出力を分岐する記事もよかったら見てみてください。
https://qiita.com/sugimount-a/items/c43b1bd4f9ecbbb448b7
Amazon ECS におけるログ出力
Amazon ECS 上で稼働するログを、外部に保存する方法は、主に 3 つあります。
引用 : https://speakerdeck.com/prog893/aws-startup-tech-meetup-number-3-kantankontenaroginguxuan-shou-quan
awslogs logging driver
独自で Fluentd のサイドカーを構成
FireLens
awslogs logging driver は、シンプルに実装できますが、ログの出力先は CloudWatch Logs だけです。大量なログが発生する環境では、CloudWatch Logs のデータ収集の料金が気になってきます。そういった時に、FireLens が便利に活用できます。
FireLens を使うことで、Amazon S3 といった他の AWS サービスなどにログを保存できます。例えば、Kinesis Data Firehose 経由で Amazon S3 に保存することで、Athena 上で SQL クエリーを使った検索・分析が可能です。また、QuickSight で可視化も含めた、他の AWS サービスとの連携が実現できます。
アプリケーション側でなにをしないといけないの?
若干古い構成図で、最新の Fargate とは異なる部分があるのですが、わかりやすい図なので引用します。
引用 : https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/
上の図は、FireLens がどのように機能するかを示しています。画面左側にある Application
が、動かすアプリケーションを表現しています。その隣の FireLens Container
が、FireLens で管理されているサイドカーコンテナを表現しています。FireLens Container
は、Fluent Bit や Fluentd をベースに構成されています。
FireLens を使ってログを送りたいときに、自分たちが動かすアプリケーション側では以下のどちらかの対応が必要です。
-
ログを標準出力に出力する
- FireLens の仕組みで、
Applicaton
のタスクで標準出力に出されたログは、Unix Domain Socket としてFireLens Container
に送付されます。 -
FireLens Container
は、事前設定された内容に合わせて Kinesis Data Firehose といった任意の宛先にログを転送します。
- FireLens の仕組みで、
-
Fluent Logger ライブラリを使って、FireLens コンテナに TCP で送付する
-
1 つの Task の中で稼働する、2つのコンテナは、ネットワーク的に互いに通信ができます。
-
Fluent Logger ライブラリを使用することで、直接 FireLens Container にログを送付できます。
-
たとえば、Go 言語の場合は次のように、ログを送付できます。
-
port, err := strconv.Atoi(os.Getenv("FLUENT_PORT")) if err != nil { // エラー処理 } config := fluent.Config{ FluentPort: port, FluentHost: os.Getenv("FLUENT_HOST"), } // Fluentd/Fluent Bit にログを送ることができる構造体のインスタンスを作成します fluentLogger, err := fluent.New(fluent.Config{}) if err != nil { log.Fatal(err) } // 各ログに必要なタグを付けることができます tag := "app.logs" // 任意のデータをマップとして送信します data := map[string]string{ "foo": "bar", } // Fluent インスタンスに送信します err = fluentLogger.Post(tag, data)
-
FireLens が転送する送付先の指定
FireLens 転送する送付先の指定方法がわかりにくかったので、個人的に整理しました。もしかしたら漏れとかがあるかもですが、基本的には問題ないと思います。ログ送付に使うサイドカーコンテナは、Fluent Bit と Fluentd の 2 種類がありますが、軽量な Fluent Bit を取り上げて紹介します。
ログ送付先が 1種類の場合
例えば、アプリケーションで生成されたすべてのログを、Kinesis Firehose に送付する場合が「ログ送付先が 1種類の場合」に当てはまります。逆に、Kinesis Firehose と Amazon S3 の 2 種類に送付したい場合は、当てはまりません。
「ログ送付先が 1種類の場合」は、ログの送付先を、ECS のタスク定義で指定するのが簡単です。
ECS の Task 定義の中に、JSON で転送先を指定できるオプションがあります。以下のように logConfiguration.options
を指定することで、任意の送付先が選べます。
省略
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": {
"Name": "kinesis_firehose",
"region": "ap-northeast-1",
"delivery_stream": "my-stream"
}
},
省略
上記の例では、"Name": "kinesis_firehose",
となっており、Kinesis Data Firehose にログを送付します。他の宛先に関する指定方法は以下の URL を見ると調べられます。
一点注意点があります。上記の GitHub の sample は、CloudWatch, Kinesis Firehose, Kinesis Strems の指定方法が古いです。クラスメソッドさんのブログでわかりやすく紹介されているので、こちらもご確認ください。
なお、上記の logConfiguration.options
を指定することで、裏側では、Fluent Bit に関する設定変更が行われます。 logConfiguration.options
で指定した内容が、そのまま Fluent Bit の OUTPUT セクションに反映されます。
OUTPUT セクションの実際の記載方法は次の URL で確認できます。
以下のように、logConfiguration.options
で指定した内容が、各 Plugin の項目にそのまま一致していることがわかります。各 Plugin でどういったカスタマイズか可能か確認すると、知らなかった発見があるかもしれません。
ログ送付先が 2種類以上の場合
ログの送付先が 2 種類以上ある場合は、Task 定義だけでは出来ません。その代わり、実際に Fluent Bit の設定ファイルを作成して、細かく送付先を指定することができます。
Dockerfile
- Fluent Bit の設定ファイルを作成し、コンテナイメージ内に配置する
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:init-2.28.3
COPY ./extra.conf /fluent-bit/etc/extra.conf
Task 定義
- Task 定義内で、上記の独自の設定ファイルのパスを指定する
"firelensConfiguration":{
"type":"fluentbit",
"options":{
"config-file-type": "file",
"config-file-value": "/fluent-bit/etc/extra.conf"
}
この詳細な内容は、以下の記事をご確認ください。
Go のアプリケーション
ここまで簡単に FireLens を紹介してきました。実際に FireLens を動かしてみて、動作を確認してみましょう。
まず、Go 言語で適当にコンテナイメージを作ります。以下の 2 種類のログを出力する、超シンプルなコンテナイメージです。
- error : エラーログ
- info : 通常のログ
Go 言語のコード
package main
import (
"fmt"
"net/http"
"os"
log "github.com/sirupsen/logrus"
)
func init() {
// JSONフォーマット
log.SetFormatter(&log.JSONFormatter{})
// 標準出力とする
log.SetOutput(os.Stdout)
// Infoレベル以上を出力
log.SetLevel(log.InfoLevel)
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,POST,GET")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
// 適当に文字列を返却する
fmt.Fprintf(w, "Hello, I am FireLens Test App!")
// Info と Warn のログを出力する
log.Info("this is Info Log")
log.Warn("this is Warn Log")
}
func main() {
http.HandleFunc("/", handler) // ハンドラを登録してウェブページを表示させる
http.ListenAndServe(":8080", nil)
}
ローカルで動かすと、こんな感じで 2 行のログが出力されます。
> go run app.go
{"level":"info","msg":"this is Info Log","time":"2022-10-15T16:21:20+09:00"}
{"level":"warning","msg":"this is Warn Log","time":"2022-10-15T16:21:20+09:00"}
Docker Build
Docker Image をビルドします。詳細は本記事のメインではないので、適当に読み飛ばしてもらって大丈夫です。
Dockerfile
FROM public.ecr.aws/docker/library/golang:1.17
RUN mkdir /workdir
COPY app /workdir
EXPOSE 8080
CMD ["/workdir/app"]
build
go build -o app
docker build -t xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-testapp:0.0.1 .
ローカルでテスト : ちゃんとログが出る
> docker run -it -p 80:8080 xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-testapp:0.0.1
{"level":"info","msg":"this is Info Log","time":"2022-10-15T07:54:42Z"}
{"level":"warning","msg":"this is Warn Log","time":"2022-10-15T07:54:42Z"}
docker push
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
docker push xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-testapp:0.0.1
Task 定義で FireLens の指定をする
Task 定義で、FireLens の指定をしていきます。新しい Console だと設定が出来ないので、古い画面で作成をしていきます。
Task 定義の名前やロールは適当に指定します。
- Task Role に FireLens から送付する先に対する権限を適切に設定する (CloudWatch や Kinesis などへの書き込み権限)
FireLens の設定部分です。Log router integration をオンにして、fluentbit
を選択して Apply を押します。
Apply を押すことで、Task 定義の中に、log_router
という名前のコンテナ定義が作成されます。
メインのアプリケーションを追加するために、Add container を押します。
Docker Build を行ったアプリケーションを指定します。
Log configuration にある、Log driver から、awsfirelens
を選択します
注意点 : Log options にある Name の個所は、必ず × を押します。これを削除しないとエラーになります
Add を押します
次に、log_router を押します。
log_router 側は、awslogs を使います。
Configure via JSON を押します。
この JSON 定義の中で、FireLens の次に転送する宛先を指定します。
変更前
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": null
},
変更後
- CloudWatch Logs に送付
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": {
"Name": "cloudwatch_logs",
"region": "ap-northeast-1",
"log_group_name": "/aws/firelens/firelens-testapp",
"auto_create_group": "true",
"log_stream_prefix": "from-fluent-bit"
}
},
Save
Create を押す
Task Definition が作成されました
Service の作成
適当に Service を作成
ECS Exec の有効化したあとに、再度 Task を強制的に再配置。
aws ecs describe-services --cluster test-cluster01 --services firelens-testapp-service
aws ecs update-service --cluster test-cluster01 --service firelens-testapp-service --enable-execute-command
aws ecs describe-services --cluster test-cluster01 --services firelens-testapp-service
動作検証
ブラウザからアクセスして、実際のログを生成します。
CloudWatch Logs の Log Group に、Task 定義で指定したものが自動的に作成されています。
Log Stream も作成されています。
アプリケーションにアクセスするたびに、info のログと warning のログが出力されていることがわかります。
- アプリケーション側で出力されているログは、
log
の中に格納されています
{
"container_id": "ea73b448250c42e1a86bc367762a7ddf-787454787",
"container_name": "mainapp",
"ecs_cluster": "test-cluster01",
"ecs_task_arn": "arn:aws:ecs:ap-northeast-1:xxxxx:task/test-cluster01/ea73b448250c42e1a86bc367762a7ddf",
"ecs_task_definition": "firelens-testapp-task-def:3",
"log": "{\"level\":\"info\",\"msg\":\"this is Info Log\",\"time\":\"2022-10-15T10:01:04Z\"}",
"source": "stdout"
}
{
"container_id": "ea73b448250c42e1a86bc367762a7ddf-787454787",
"container_name": "mainapp",
"ecs_cluster": "test-cluster01",
"ecs_task_arn": "arn:aws:ecs:ap-northeast-1:xxxxx:task/test-cluster01/ea73b448250c42e1a86bc367762a7ddf",
"ecs_task_definition": "firelens-testapp-task-def:3",
"log": "{\"level\":\"warning\",\"msg\":\"this is Warn Log\",\"time\":\"2022-10-15T10:01:04Z\"}",
"source": "stdout"
}
付録 : Fluent Bit 設定ファイルを確認
ECR Gallery のコンテナイメージを確認
Fluent Bit は、以下の ECR Gallery に公開されている。
この Dockerfile はここに公開されている。
ローカルで起動してみる
docker run -it --rm public.ecr.aws/aws-observability/aws-for-fluent-bit:latest bash
entrypoint
bash-4.2# cat /entrypoint.sh
echo -n "AWS for Fluent Bit Container Image Version "
cat /AWS_FOR_FLUENT_BIT_VERSION
exec /fluent-bit/bin/fluent-bit -e /fluent-bit/firehose.so -e /fluent-bit/cloudwatch.so -e /fluent-bit/kinesis.so -c /fluent-bit/etc/fluent-bit.conf
conf の設定値
- この設定値は、ECS 上で動かすときに、自動的に書き換わる
bash-4.2# cat /fluent-bit/etc/fluent-bit.conf
[INPUT]
Name forward
Listen 0.0.0.0
Port 24224
[OUTPUT]
Name cloudwatch
Match **
region us-east-1
log_group_name fluent-bit-cloudwatch
log_stream_prefix from-fluent-bit-
auto_create_group true
etc ディレクトリ
bash-4.2# ls -la /fluent-bit/etc/
total 36
drwxr-xr-x 2 root root 213 Oct 11 20:54 .
drwxr-xr-x 1 root root 22 Oct 11 20:55 ..
-rw-r--r-- 1 root root 251 Oct 11 18:02 fluent-bit.conf
-rw-r--r-- 1 root root 4664 Oct 11 20:54 parsers.conf
-rw-r--r-- 1 root root 584 Oct 11 20:54 parsers_ambassador.conf
-rw-r--r-- 1 root root 226 Oct 11 20:54 parsers_cinder.conf
-rw-r--r-- 1 root root 2798 Oct 11 20:54 parsers_extra.conf
-rw-r--r-- 1 root root 240 Oct 11 20:54 parsers_java.conf
-rw-r--r-- 1 root root 845 Oct 11 20:54 parsers_mult.conf
-rw-r--r-- 1 root root 2954 Oct 11 20:54 parsers_openstack.conf
ECS Exec で動作中の log_router から確認
Task Definition の定義は、デフォルトのまま。宛先は null なので、どこにも転送しない。
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": null
},
ECS Exec で、FireLens のコンテナ内で bash を起動。
> aws ecs execute-command \
--cluster test-cluster01 \
--task a30895fa37584a258e617e894ebe402d \
--container log_router \
--interactive \
--command "bash"
entrypoint
bash-4.2# cat /entrypoint.sh
echo -n "AWS for Fluent Bit Container Image Version "
cat /AWS_FOR_FLUENT_BIT_VERSION
exec /fluent-bit/bin/fluent-bit -e /fluent-bit/firehose.so -e /fluent-bit/cloudwatch.so -e /fluent-bit/kinesis.so -c /fluent-bit/etc/fluent-bit.conf
conf ファイル
- ECR Gallery の内容から書き換わっている
- INPUT として、TCP, Unix Socket が指定されている
- FILTER で ECS クラスター名や、タスク名が入っている
- デフォルトだと、OUTPUT の name は null となっており、FireLens のコンテナはどこにもログを送信しない
bash-4.2# cat /fluent-bit/etc/fluent-bit.conf
[INPUT]
Name tcp
Listen 127.0.0.1
Port 8877
Tag firelens-healthcheck
[INPUT]
Name forward
unix_path /var/run/fluent.sock
[INPUT]
Name forward
Listen 127.0.0.1
Port 24224
[FILTER]
Name record_modifier
Match *
Record ecs_cluster test-cluster01
Record ecs_task_arn arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/a30895fa37584a258e617e894ebe402d
Record ecs_task_definition firelens-testapp-task-def:2
[OUTPUT]
Name null
Match firelens-healthcheck
ECS Exec で動作中の log_router から確認 : 宛先 CloudWatch
CloudWatch に転送する内容をタスク定義に指定
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": {
"Name": "cloudwatch_logs",
"region": "ap-northeast-1",
"log_group_name": "/aws/firelens/firelens-testapp",
"auto_create_group": "true",
"log_stream_prefix": "from-fluent-bit"
}
},
ECS Exec で、FireLens のコンテナ内で bash を起動。
> aws ecs execute-command \
--cluster test-cluster01 \
--task e9ecc683995745b8a61c6a56540a1108 \
--container log_router \
--interactive \
--command "bash"
以下の設定が自動設定されている
-
INPUT として、TCP, Unix Socket が指定されている
-
CloudWatch Logs に送付する OUTPUT セクションが自動生成されている
bash-4.2# cat /fluent-bit/etc/fluent-bit.conf
[INPUT]
Name tcp
Listen 127.0.0.1
Port 8877
Tag firelens-healthcheck
[INPUT]
Name forward
unix_path /var/run/fluent.sock
[INPUT]
Name forward
Listen 127.0.0.1
Port 24224
[FILTER]
Name record_modifier
Match *
Record ecs_cluster test-cluster01
Record ecs_task_arn arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/e9ecc683995745b8a61c6a56540a1108
Record ecs_task_definition firelens-testapp-task-def:4
[OUTPUT]
Name null
Match firelens-healthcheck
[OUTPUT]
Name cloudwatch_logs
Match mainapp-firelens*
auto_create_group true
log_group_name /aws/firelens/firelens-testapp
log_stream_prefix from-fluent-bit
region ap-northeast-1
検証を通じてわかったこと
- アプリケーションを動かすメインコンテナ側のログ設定を、マネージメントコンソールで行うときに、かならず Name の欄の × を押す。これを押さないとエラーになる
- Task Definition の、
logConfiguration
にある Options に指定する文字列が、FluentBit の [OUTPUT] にそのまま反映される
- アプリケーションで出力したログは、メタデータが付与された形で転送される。
- Fluent Bit は、ECS 上で起動されるタイミングで、conf ファイルが自動的に書き換わる。ECR Gallary で公開されているイメージを、ローカルで確認してもよくわからないので、ECS Exec で中身を確認するとわかりやすい
参考URL
- https://dev.classmethod.jp/articles/check-fluent-bit-conf/
- https://dev.classmethod.jp/articles/aws-fargate-with-firelens-minimum/
- https://aws.amazon.com/jp/blogs/news/splitting-application-logs-multiple-streams-fluent/
- https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/