AWS FargateでFireLens(Fluent Bit)を最小構成で起動し、ECS ExecでFireLensコンテナに入ってみる
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個あること
テンプレート
折り畳み
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を指定し、オプションは未指定です。
"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起動だけのために必要なタスクロール、タスク実行ロールはないことを確認できました。カスタムログルーティングのためのタスクロール設定は頭にあったのですが、起動にあたり何か必要だったかは意識していなかったので勉強になりました。