はじめに
FargateのログはデフォルトだとCloudWatch Logsに収集される。
このままでも良いのだけど、加工したり転送したりと色々やりたいときに手間にならない方法としてFireLensが使えるらしいので、試してみる。
以前書いた記事をベースに、以下の記事と同様のことをTerraformで実装する。
【Developers.IO】[アップデート] ECS/Fargateでログ出力先をカスタマイズできる「FireLens」機能がリリースされました
事前準備
上記の記事の構成でFireLensを使うのに必要なのは以下。
まずはこれを用意していく。
- ログの書き込み先となるFirehoseの準備
- ECSのタスクロール(Notタスク実行ロール)へのFirehoseへの書き込み権限のアタッチ
- Firehoseに対する書き込み先のS3の書き込み権限のアタッチ
これまでの記事に書いているように、local
なリソースは名前の定義程度なので、テキトーに良い感じな名前を振ってもらえれば良い。
まずは、以下のような感じでログの書き込み先を作成する。
s3_configuration
のcloudwatch_logging_options
は有ってもなくても良い。
エラー時の原因調査にログが必要であればつけておこう(これがないと、本当に何の手掛かりもなくログが出力されないことになる)
エラー時に出力するlog_group_name
は、別のリソースで作っておく。
################################################################################
# CloudWatch Logs #
################################################################################
resource "aws_cloudwatch_log_group" "firelenstest"{
name = "${local.loggroup_name}"
}
################################################################################
# Kinesis Firehose #
################################################################################
resource "aws_kinesis_firehose_delivery_stream" "firelenstest" {
name = "${local.stream_name}"
destination = "s3"
s3_configuration {
role_arn = "${data.aws_iam_role.firehose.arn}"
bucket_arn = "${data.aws_s3_bucket.firelenstest.arn}"
cloudwatch_logging_options {
enabled = "true"
log_group_name = "${data.aws_cloudwatch_log_group.firelenstest.name}"
log_stream_name = "kinesis_error"
}
}
}
次に、ECSのタスクロールにFirehoseへの書き込み権限をアタッチする。
タスク実行ロールではなくてタスクロールの信頼ポリシーの設定なのに、Principalsがecs-tasks.amazonaws.com
なのは不思議だが、どうやらこれでOKらしい。
################################################################################
# IAM Role for ECS Task #
################################################################################
resource "aws_iam_role" "ecs_task" {
name = "${local.ecs_task_role_name}"
assume_role_policy = "${data.aws_iam_policy_document.ecs_task_trust.json}"
}
data "aws_iam_policy_document" "ecs_task_trust" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"ecs-tasks.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy_attachment" "ecs_task" {
role = "${aws_iam_role.ecs_task.name}"
policy_arn = "${aws_iam_policy.ecs_task_custom.arn}"
}
resource "aws_iam_policy" "ecs_task_custom" {
name = "ecs-task-role-policy"
description = "ECS Task Role Policy for Firehose"
policy = "${data.aws_iam_policy_document.ecs_task_custom.json}"
}
data "aws_iam_policy_document" "ecs_task_custom" {
version = "2012-10-17"
statement {
sid = "FirehosePutRecord"
effect = "Allow"
actions = [
"firehose:PutRecordBatch",
]
resources = [
"*",
]
}
}
最後に、Firehoseに対する書き込み先のS3の書き込み権限のアタッチをすれば準備完了だ。
書き込み先のS3もここでリソースを準備してしまおう。
こんかい、エラー時にCloudWatch Logsにも出力するつもりなので、logs:PutLogEvents
のアクセス権限をFirehoseにアタッチしている。CloudWatch Logsに出さないという強気戦略であれば、不要だろう。
↑の準備とiam.tfで名前が重複しているので、コンカチするか分けるかはお任せする。やりやすい方で良いと思う。
################################################################################
# S3 Bucket #
################################################################################
resource "aws_s3_bucket" "firelenstest" {
bucket = "${local.bucket_name}"
acl = "private"
}
################################################################################
# IAM Role for Kinesis Firehose #
################################################################################
resource "aws_iam_role" "firehose" {
name = "${local.firehose_role_name}"
assume_role_policy = "${data.aws_iam_policy_document.firehose_trust.json}"
}
data "aws_iam_policy_document" "firehose_trust" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"firehose.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy_attachment" "firehose" {
role = "${aws_iam_role.firehose.name}"
policy_arn = "${aws_iam_policy.firehose_custom.arn}"
}
resource "aws_iam_policy" "firehose_custom" {
name = "firehose-role-policy"
description = "Firehose Role Policy"
policy = "${data.aws_iam_policy_document.firehose_custom.json}"
}
data "aws_iam_policy_document" "firehose_custom" {
version = "2012-10-17"
statement {
sid = "S3Access"
effect = "Allow"
actions = [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:PutObject"
]
resources = [
"arn:aws:s3:::${aws_s3_bucket.firelenstest.bucket}",
"arn:aws:s3:::${aws_s3_bucket.firelenstest.bucket}/*",
]
}
statement {
sid = "CloudWatchLogsAccess"
effect = "Allow"
actions = [
"logs:PutLogEvents"
]
resources = [
"${aws_cloudwatch_log_group.firelenstest.arn}"
]
}
}
ECSサービスとタスク定義の修正
ECSサービスとタスク定義は以下の通りに修正する。
ポイントは以下の通り。
-
aws_ecs_task_definition
にFireLensのコンテナ(Fluentbitのコンテナ)を仕込んでいるので、通常のHTTPサーバとは構成が異なる可能性あり - コンテナイメージのパスは公式のAWS Fargate 用ユーザーガイドに書かれている値を設定する。他所のリポジトリから取得することでイメージが未来永劫同じであることの担保がされなくなってしまうため、それを嫌うのであれば、自分のECRリポジトリにコピーを行い、安定板として自分で管理するのが良い。が、その後のメンテ性とは裏返しになるので、注意が必要な部分ではある。
ポイントになるのは以下の部分。
デフォルトではawslogs
で定義する部分を以下のように変更する。
options
で指定できるプロパティは、AWS Fargate用ユーザーガイドによると、
Fluentd または Fluent Bit 出力設定の生成に使用されます。
らしい。が、Fluent Bitのドキュメントには言及がない。おそらくこの辺が使えるのだろうが、イマイチよく分からないな……。
"logConfiguration": {
"logDriver": "awsfirelens",
"options": {
"Name": "firehose",
"region": "${data.aws_region.current.name}",
"delivery_stream": "${aws_kinesis_firehose_delivery_stream.firelenstest.name}"
}
}
また、以下の部分で、Fluent Bitをサイドカーコンテナとして起動している。
imageのパスは、↑のユーザーガイドに書かれているものを設定する。リージョンごとに違うので注意。Fluent Bitそのものが出力するログは、awslogsでCloudWatch Logsに転送する(自分のストリームにはログは出せないというか、出してしまうと障害解析ができない)。
{
"name" : "log_router",
"image": "906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latest",
"firelensConfiguration": {
"type": "fluentbit"
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${data.aws_cloudwatch_log_group.firelenstest.name}",
"awslogs-region": "${data.aws_region.current.name}",
"awslogs-stream-prefix": "ecs"
}
}
}
ECSを定義するHCLの全文は以下。
ecs.tf
resource "aws_ecs_cluster" "firelenstest" {
name = "${local.ecs_cluster_name}"
}
resource "aws_ecs_task_definition" "firelenstest" {
family = "${local.ecs_family_name}"
task_role_arn = "${data.aws_iam_role.ecs_task.arn}"
execution_role_arn = "${data.aws_iam_role.task_execution.arn}"
network_mode = "awsvpc"
cpu = "256"
memory = "1024"
requires_compatibilities = [
"FARGATE",
]
container_definitions = <<EOF
[
{
"name" : "${local.ecs_container_name}",
"image": "${data.aws_ecr_repository.firelenstest.repository_url}:${local.container_image_tag}",
"cpu": 0,
"memoryReservation": 512,
"portMappings": [
{
"containerPort": 9000,
"hostPort": 9000,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awsfirelens",
"options": {
"Name": "firehose",
"region": "${data.aws_region.current.name}",
"delivery_stream": "${aws_kinesis_firehose_delivery_stream.firelenstest.name}"
}
}
},
{
"name" : "log_router",
"image": "906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latest",
"firelensConfiguration": {
"type": "fluentbit"
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${data.aws_cloudwatch_log_group.firelenstest.name}",
"awslogs-region": "${data.aws_region.current.name}",
"awslogs-stream-prefix": "ecs"
}
}
}
]
EOF
}
resource "aws_ecs_service" "firelenstest" {
name = "${local.ecs_service_name}"
cluster = "${aws_ecs_cluster.firelenstest.id}"
launch_type = "FARGATE"
task_definition = "${aws_ecs_task_definition.firelenstest.arn}"
desired_count = 1
load_balancer {
target_group_arn = "${data.aws_alb_target_group.firelenstest.arn}"
container_name = "${var.prefix}-Container"
container_port = 9000
}
deployment_controller {
type = "CODE_DEPLOY"
}
network_configuration {
subnets = flatten(["${data.aws_subnet_ids.my_vpc.ids}",])
security_groups = [
"${data.aws_security_group.firelenstest.id}",
]
assign_public_ip = "true"
}
}
出力の確認
①…時間単位でディレクトリが作られる
②…kinesis.tfでs3_configuration
のデフォルト設定を適用すると、5MiBまたは5分単位で出力される。
ログ分割に関する時間やバッファリングのチューニングは↑この設定で行える。
ログ出力そのものの設定は、Fluent Bit側の設定になる。詳細はユーザーガイドの『カスタム設定ファイルの指定』を参照。