この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ECSのタスク(コンテナ)のログ出力先を変更できるFireLens機能があります。 AWS FargateでFireLensの動作検証のため、最小限の設定でFireLensを起動させて動作確認する機会がありました。また使う場面がありそうなのでCloudFormationのテンプレートにまとめました。
-
Icons made by Freepik from www.flaticon.com
Fluent Bitでのカスタムログルーティング設定は行いません。FireLensにはオリジナルのaws-for-fluent-bit
イメージをそのまま起動します。FireLens自身のログはlog driverをawslogsに設定し、CloudWatch Logsへ直接送ることができます。FireLensをデバッグするには必要ですね。
-
Icons made by Freepik from www.flaticon.com
もしカスタムログルーティングの設定を行いたい場合は以下のブログポストを参照ください。
本当に最小限の設定にしたいとなった結果、アプリケーションコンテナのログはFireLensコンテナの標準出力へ返すことになりました。本来FireLensコンテナのログ確認用にlog driverをawslogs設定でCloudWatch Logsへ送るところへ、アプリケーションログも載せてしまいます。 この設定はFluent Bitのカスタムログルーティングと一般的に呼ばれているものとは異なります。FireLensの動作検証の参考にしてください。
-
Icons made by Freepik from www.flaticon.com
CloudFormation
テンプレートを実行すると約5分で以下の動作確認環境を構築できます。
ポイント
- NginxコンテナのパブリックIPにアクセスすることで、アクセスログがFireLensコンテナ経由でCloudWatch Logsへ保存
- ELBは作成しません
- 動作検証用にECS Exec用のタスクロール設定済み
- パブリックのコンテナイメージをそのまま利用するため、自前でECRにイメージの準備不要
- セキュリティグループが
0.0.0.0/0
で解放されています、必要に応じてアクセス元を制限してください
前提
VPCは準備済みのものを間借りします。
- VPC作成済み
- パブリックサブネットが2個あること
テンプレート
折り畳み
fargateWithFirelens.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Fargate*1, Firelens*1
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Common Settings
Parameters:
- ProjectName
- Environment
- Label:
default: ECS VPC Settings
Parameters:
- VPCID
- PublicSubnet1
- PublicSubnet2
Parameters:
ProjectName:
Description: Project Name
Type: String
Default: unnamed
Environment:
Description: Environment
Type: String
Default: dev
AllowedValues:
- prod
- dev
- stg
- test
VPCID:
Type: AWS::EC2::VPC::Id
PublicSubnet1:
Description: "Web App Subnet 1st"
Type: AWS::EC2::Subnet::Id
PublicSubnet2:
Description: "Web App Subnet 2nd"
Type: AWS::EC2::Subnet::Id
DesiredCount:
Type: Number
Default: 1
ClusterName:
Type: String
Default: cluster
AppName:
Type: String
Default: webapp
ServiceName:
Type: String
Default: service
TaskDefinitionName:
Type: String
Default: taskdef
ImageNameWebApp:
Description: "Web Application Repository Name also Need to TagName"
Type: String
Default: "public.ecr.aws/nginx/nginx:latest"
ImageNameFirelens:
Description: "Firelens Repository Name also Need to TagName"
Type: String
Default: "public.ecr.aws/aws-observability/aws-for-fluent-bit:latest"
Resources:
# --------------------------------------------
# ECS Fargate
# --------------------------------------------
# Cluster
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "${ProjectName}-${Environment}-${ClusterName}"
CapacityProviders:
- "FARGATE_SPOT"
- "FARGATE"
ECSLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/ecs/logs/${ProjectName}-${Environment}-${TaskDefinitionName}"
# Service
ECSService:
Type: "AWS::ECS::Service"
Properties:
ServiceName: !Sub ${ProjectName}-${Environment}-${ServiceName}
Cluster: !Ref ECSCluster
LaunchType: "FARGATE"
PlatformVersion: "1.4.0"
DesiredCount: !Ref DesiredCount
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 100
DeploymentCircuitBreaker:
Enable: false
Rollback: false
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: "ENABLED"
SecurityGroups:
- !Ref SecurityGroup1
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
TaskDefinition: !Ref ECSTaskDefinition
# ECS TaskDefinition
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Family: !Sub "${ProjectName}-${Environment}-${AppName}-${TaskDefinitionName}"
TaskRoleArn: !GetAtt ECSTaskRole1.Arn
ExecutionRoleArn: !GetAtt ECSTaskExecutionRole1.Arn
NetworkMode: "awsvpc"
RequiresCompatibilities:
- "FARGATE"
Cpu: "256"
Memory: "512"
ContainerDefinitions:
- Essential: true
Image: !Ref ImageNameWebApp
LogConfiguration:
LogDriver: "awsfirelens"
Options:
Name: "stdout"
Name: !Ref AppName
PortMappings:
- ContainerPort: 80
HostPort: 80
Protocol: "tcp"
- Essential: true
FirelensConfiguration:
Type: "fluentbit"
Image: !Ref ImageNameFirelens
LogConfiguration:
LogDriver: "awslogs"
Options:
awslogs-group: !Ref ECSLogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: "firelens"
Name: "log_router"
User: "0"
# --------------------------------------------
# Security Group
# --------------------------------------------
# Security Group for WebApp
SecurityGroup1:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${ProjectName}-${Environment}-${AppName}-sg
GroupDescription: Web App Security Group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: "0.0.0.0/0"
Description: "Access from Public"
VpcId: !Ref VPCID
Tags:
- Key: Name
Value: !Sub ${ProjectName}-${Environment}-${AppName}-sg
# --------------------------------------------
# IAM Role
# --------------------------------------------
# ECS Task Execution Role
ECSTaskExecutionRole1:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub ${ProjectName}-${Environment}-${AppName}-ECSTaskExecutionRole
Path: "/"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# ECS Task Role
ECSTaskRole1:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
RoleName: !Sub ${ProjectName}-${Environment}-${AppName}-ECSTaskRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Ref ECSExecPolicy
# --------------------------------------------
# IAM Policy
# --------------------------------------------
# Allowed ECS Exec
ECSExecPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub "${ProjectName}-${Environment}-ECSExecPolicy"
Path: "/"
PolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
],
"Resource": "*"
}
]
}
検証環境
項目 | バージョン |
---|---|
aws-for-fluent-bit | 2.17.0 |
Fluent Bit | 1.8.1 |
Fargate platform | 1.4.0 |
Rain | 1.2.0 |
rain deploy時のパラメータ指定。VPC ID、サブネットIDはご自身の環境に合わせ変更してください。
rain deploy ./fargateWithFirelens.yml fargateWithFirelens-stack --params \
AppName=webapp,\
ClusterName=cluster,\
DesiredCount=1,\
Environment=test,\
ImageNameFirelens=public.ecr.aws/aws-observability/aws-for-fluent-bit:latest,\
ImageNameWebApp=public.ecr.aws/nginx/nginx:latest,\
ProjectName=minimum,\
PublicSubnet1=subnet-043566448c316b46a,\
PublicSubnet2=subnet-00a24cb4e0d180ffe,\
ServiceName=service,\
TaskDefinitionName=taskdef,\
VPCID=vpc-05d62f9c3d68253a2
FireLens動作確認
最小構成でFireLensが確実に起動できることを目的とした環境です。
タスク定義
CloudFormationで生成されたタスク定義を確認します。設定ポイントを抜粋して説明します。
アプリケーションコンテナの設定
アプリコンテナのlogConfigurationのlog driverはawsfirelensを指定し、optionで最もシンプルな設定だと思われるNameキーにstdoutの値を指定しています。これによりアプリコンテナのログがFireLensコンテナの標準出力へ送られます。
アプリケーションコンテナの設定抜粋
"executionRoleArn": "arn:aws:iam::123456789012:role/minimum-test-webapp-ECSTaskExecutionRole",
"containerDefinitions": [
{
"dnsSearchDomains": [],
"environmentFiles": [],
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": [],
"options": {
"Name": "stdout"
}
},
FireLensコンテナの設定
FireLensコンテナのlogConfigurationのlog dirverはawslogsのままで、FireLensコンテナの標準出力内容をCloudWatch Logsへ送ります。前述のアプリコンテナの設定により、FireLensの標準出力内容にアプリコンテナのログが載かってくるわけです。
firelensConfigurationの設定値はfluentbitを指定し、オプションは未指定です。
FireLensの設定抜粋
"logConfiguration": {
"logDriver": "awslogs",
"secretOptions": [],
"options": {
"awslogs-group": "/ecs/logs/minimum-test-taskdef",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "firelens"
}
},
...snip...
"image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:latest",
"startTimeout": null,
"firelensConfiguration": {
"type": "fluentbit",
"options": {}
},
タスク定義全文
折り畳み
{
"ipcMode": null,
"executionRoleArn": "arn:aws:iam::123456789012:role/minimum-test-webapp-ECSTaskExecutionRole",
"containerDefinitions": [
{
"dnsSearchDomains": [],
"environmentFiles": [],
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": [],
"options": {
"Name": "stdout"
}
},
"entryPoint": [],
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
],
"command": [],
"linuxParameters": null,
"cpu": 0,
"environment": [],
"resourceRequirements": null,
"ulimits": [],
"dnsServers": [],
"mountPoints": [],
"workingDirectory": null,
"secrets": [],
"dockerSecurityOptions": [],
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"stopTimeout": null,
"image": "public.ecr.aws/nginx/nginx:latest",
"startTimeout": null,
"firelensConfiguration": null,
"dependsOn": null,
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": [],
"hostname": null,
"extraHosts": [],
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": {},
"systemControls": [],
"privileged": null,
"name": "webapp"
},
{
"dnsSearchDomains": [],
"environmentFiles": [],
"logConfiguration": {
"logDriver": "awslogs",
"secretOptions": [],
"options": {
"awslogs-group": "/ecs/logs/minimum-test-taskdef",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "firelens"
}
},
"entryPoint": [],
"portMappings": [],
"command": [],
"linuxParameters": null,
"cpu": 0,
"environment": [],
"resourceRequirements": null,
"ulimits": [],
"dnsServers": [],
"mountPoints": [],
"workingDirectory": null,
"secrets": [],
"dockerSecurityOptions": [],
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"stopTimeout": null,
"image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:latest",
"startTimeout": null,
"firelensConfiguration": {
"type": "fluentbit",
"options": {}
},
"dependsOn": null,
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": [],
"hostname": null,
"extraHosts": [],
"pseudoTerminal": null,
"user": "0",
"readonlyRootFilesystem": null,
"dockerLabels": {},
"systemControls": [],
"privileged": null,
"name": "log_router"
}
],
"placementConstraints": [],
"memory": "512",
"taskRoleArn": "arn:aws:iam::123456789012:role/minimum-test-webapp-ECSTaskRole",
"compatibilities": [
"EC2",
"FARGATE"
],
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/minimum-test-webapp-taskdef:1",
"family": "minimum-test-webapp-taskdef",
"requiresAttributes": [
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.execution-role-awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.firelens.fluentbit"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.17"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.logging-driver.awsfirelens"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.task-eni"
}
],
"pidMode": null,
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"revision": 1,
"status": "ACTIVE",
"inferenceAccelerators": null,
"proxyConfiguration": null,
"volumes": []
}
ログ出力確認
クラスターが作成されています。
FireLens(log_router)が起動しています。
タスクからパブリックIPを確認します。
WebブラウザからパブリックIPへアクセスすると、Nginxのデフォルトページを確認できます。
Nginxコンテナ(webapp)のログを確認します。log driverはFireLensを指定しているため、ここからはなにも確認できません。
FireLensコンテナ(log_router)のログを確認します。Nginxのアクセスログを確認できます。FireLensのログと同じCloudWatch Logsのロググループにログが保存されています。アクセス元のIPは伏せていますが、Webブラウザでアクセスした時間のログが確認できます。
CloudWatch Logsからも確認してみます。FireLensの起動ログが記録されています。FireLensのデバッグ目的のため、FireLensが起動しない、すぐ落ちるなどあればCloudWatch Logsから確認できます。
起動ログの後半には、さきほどのNginxコンテナのアクセスログも同様に確認できます。
FireLensへECS Exec
ECS ExecのおかげでFargateでも障害調査がはかどるようになりました。ECS Execに必要な権限はCloudFormationで作成済みです。だけど、現状AWS CLIからの操作が必須な箇所あります。詳細は以下のブログポストを参照ください。
やってみる
やや手間な作業ですがIAMロールが正しいか動作確認ためやります。
ECS Exec機能を有効化します。
aws ecs update-service \
--cluster minimum-test-cluster \
--service minimum-test-service \
--enable-execute-command
enableExecuteCommand
がtrueであることを確認します。
実行結果
"rolloutState": "COMPLETED",
...skipping...
"enableECSManagedTags": false,
"propagateTags": "NONE",
"enableExecuteCommand": true
}
}
ECS Exec機能を有効化した後に起動したタスクではじめてECS Execが利用できます。つまり、今起動しているタスク(コンテナ)ではECS Execが使えないです。
さきほどECS Exec機能を有効化しましたが、タスク定義のリビジョンに変更はありません。タスクの新しいデプロイの強制し、ECSのサービス(minimum-test-service)を更新かけます。
aws ecs update-service \
--cluster minimum-test-cluster \
--service minimum-test-service \
--task-definition minimum-test-webapp-taskdef \
--force-new-deployment
新しいタスクがデプロイ中です。
現在起動しているタスクIDを確認します。タスクは1個しか起動していないため、1つだけ結果が返ってきました。
aws ecs list-tasks \
--cluster minimum-test-cluster
実行結果
{
"taskArns": [
"arn:aws:ecs:ap-northeast-1:123456789012:task/minimum-test-cluster/0575613b8a7a4d0a8845ccd70cef8e7a"
]
}
新しいタスクでenableExecuteCommand
がtrueであることを念のため確認します。
aws ecs describe-tasks \
--cluster minimum-test-cluster \
--tasks 0575613b8a7a4d0a8845ccd70cef8e7a
実行結果
"availabilityZone": "ap-northeast-1a",
...skipping...
"enableExecuteCommand": true,
"group": "service:minimum-test-service",
"healthStatus": "UNKNOWN",
aws-for-fluent-bit
のイメージを利用したFireLensです。ベースイメージを確認したところamazonlinuxでした。
- aws-for-fluent-bit/Dockerfile at mainline · aws/aws-for-fluent-bit
- ECR Public Gallery - aws-for-fluentbit
Amazon LinuxとのことでBashでログインしてみます。
aws ecs execute-command \
--cluster minimum-test-cluster \
--task 0575613b8a7a4d0a8845ccd70cef8e7a \
--container log_router \
--interactive \
--command "/bin/bash"
FIreLens(log_router)コンテナ内
FireLensコンテナに入れました。
Fluent Bitの設定ファイルを直接確認できました。
aws-for-fluent-bit
イメージをそのまま起動しているため、カスタムコンフィグの指定がない状態です。
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 minimum-test-cluster
Record ecs_task_arn arn:aws:ecs:ap-northeast-1:123456789012:task/minimum-test-cluster/0575613b8a7a4d0a8845ccd70cef8e7a
Record ecs_task_definition minimum-test-webapp-taskdef:1
[OUTPUT]
Name null
Match firelens-healthcheck
[OUTPUT]
Name stdout
Match webapp-firelens*
おわりに
FireLensコンテナへECS Exceすることはなかったので良い機会でした。FireLens起動だけのために必要なタスクロール、タスク実行ロールはないことを確認できました。カスタムログルーティングのためのタスクロール設定は頭にあったのですが、起動にあたり何か必要だったかは意識していなかったので勉強になりました。