背景
ECS Fargate で稼働するアプリケーションのログファイル(非標準出力)の内容をCloudWatch Logsに出力する要望があります。
しかし、AWS Fargate でホストされるタスクは、file 設定ファイルタイプのみをサポートします。1
メンテナンス性、利用易さ、コスト、またシステムCI/CDを複雑化したくない等の理由で、FireLensとその設定ファイルはカスタムイメージを利用したくないです。
FireLens設定ファイルは EFS に格納し、その EFS を ECS にマウントする案を考えました。
- システムは、Dev環境、STG環境、本番環境で構成されている
- 環境ごとにAWSアカウントが異なって、アカウント間にTransit Gatewayで繋がっている
- EFS は各環境で作成する
- 運用管理サーバ(踏み台)は、Dev環境で構築されている
EFS構築
-
EFS File system を作成する
- VPC は、運用サーバ(EC2)また ECS が稼働する VPC を選択する
-
AccessPoint を作成する
- File system は、上記 1 で作成した File system を選択する
- Root directory path は
/
を設定する
-
各環境で上記1,2の手順を実施する
EC2にEFSをマウントする
注意: クロスアカウントシナリオでは通常の NFS コマンドを使用できないため、botocore と Amazon EFS クライアントが必要になります。
-
セキュリティグループ
EC2 用のセキュリティグループに、NAS用2049ポートを許可する。 -
Amazon EFS クライアントをインストールする
$ sudo yum install -y amazon-efs-utils
-
クロスアカウント EFS ファイルシステムにアクセスしてマウントするには、以下のようなポリシーステートメントを IAM ポリシーに追加します。
-
運用サーバ IAM Profile 用 Policy
aws-ec2-profile-policy{ "Sid": "EfsPermissions", "Effect": "Allow", "Action": [ "elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite", "elasticfilesystem:ClientRootAccess" ], "Resource": [ "arn:aws:elasticfilesystem:region:accountA-id:file-system/file-system-id", "arn:aws:elasticfilesystem:region:accountB-id:file-system/file-system-id", "arn:aws:elasticfilesystem:region:accountB-id:file-system/file-system-id" ] }
-
STG環境、本番環境 EFS ポリシーに下記内容を追記
{ "Sid": "access-point-statement-example03", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::{dev_account_id}:root" }, "Action": "*", "Resource": "arn:aws:elasticfilesystem:ap-northeast-1:{efs_account_id}:file-system/{file_system_id}" }
-
-
Amazon EFS コンソールにログインして、[File Systems] (ファイルシステム) を選択します。[EFS-File-System-ID] を選択してから、[Network] (ネットワーク) にあるアベイラビリティーゾーンの IP アドレスをメモします。
-
取得した IP アドレスを使用して、NFS クライアントの /etc/hosts ファイルに hosts エントリを追加します。DNS 名の形式は、
mount-target-IP-Address file-system-ID.efs.region.amazonaws.com
です。以下のコマンド例を参照してください。
$ echo "10.0.8.117 fs-087e048b084872a24.efs.ap-northeast-1.amazonaws.com" | sudo tee -a /etc/hosts
-
マウントヘルパーを使用して EFS ファイルシステムをマウントします。
-
ファイルシステムDNS名を使用してマウントするには:
$ sudo mount -t efs -o iam,tls file-system-dns-name efs-mount-point/
$ sudo mount -t efs -o iam,tls fs-087e048b084872a24.efs.ap-northeast-1.amazonaws.com efs/
- または -
-
マウントターゲット IP アドレスを使用してマウントするには:
$ sudo mount -t efs -o iam,tls,mounttargetip=mount-target-ip file-system-id efs-mount-point/
$ sudo mount -t efs -o iam,tls,mounttargetip=10.0.8.117 fs-087e048b084872a24 efs/
-
-
再起動しても自動マウントできるため、
/etc/fstab
を修正する。2
ECSにEFSをマウントする
-
ECS 用のセキュリティグループに、NAS用2049ポートを許可する。
-
ECS Task実行ロールに EFS関する権限、例えば
AmazonElasticFileSystemClientFullAccess
ロールをアタッチする -
各環境の ECS タスク定義ファイルに、各環境のEFS情報を書き換えた内容を追記する。
"volumes": [ { "name": "firelens-config", "efsVolumeConfiguration": { "fileSystemId": "{file-system-id}", "rootDirectory": "/", "transitEncryption": "ENABLED", "authorizationConfig": { "accessPointId": "{access-point-id}", "iam": "ENABLED" } } } ]
タスク定義にFireLensを設定する
-
ECS タスク定義に、FireLens用サイドカーを追加
firelens-side-car{ "name": "log_router", "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable", "cpu": 0, "memory": 256, "memoryReservation": 128, "portMappings": [], "essential": true, "environment": [], "mountPoints": [], "volumesFrom": [], "user": "0", "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/Cluster/Service", "awslogs-create-group": "true", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs" } }, "systemControls": [], }
-
各環境 FireLens 用の Container 定義に、下記マウント定義を追記する
"mountPoints": [ { "sourceVolume": "firelens-config", "containerPath": "/configs/{env}" } ],
-
Multiline PARSER 用
parser_springboot.conf
ファイルを作成以下の設定は、ログのタイムスタンプを見て、次のタイムスタンプまでログを合併する。タイムスタンプが付いていないログは、前のログに統合する。
[MULTILINE_PARSER] name springboot-multiline type regex flush_timeout 1000 # # Regex rules for multiline parsing # --------------------------------- # # configuration hints: # # - first state always has the name: start_state # - every field in the rule must be inside double quotes # # rules | state name | regex pattern | next state # ------|---------------|-------------------------------------------- rule "start_state" "/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}/" "cont" rule "cont" "/^(?!\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3})/" "cont"
-
extra.conf
ファイルを作成する。Parsers_File
のパスは、上記ステップ2のsourceVolume
パスとなります。注意: [OUTPUT]セクションのプラグインNameについて、
cloudwatch
と最新のcloudwatch_logs
がありますが、現時点ではcloudwatch_logs
は、log_group_name
またlog_stream_name
での$(variable)
テンプレートが利用できないため、今回cloudwatch
を利用する。extra.conf# グローバルな設定 [SERVICE] Flush 1 Grace 30 Parsers_File /firelens-configs/parser_springboot.conf [FILTER] Name multiline Match * multiline.key_content log multiline.parser springboot-multiline [FILTER] Name grep Match * regex log ^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3} [OUTPUT] Name cloudwatch Match app-log region ap-northeast-1 log_group_name /ecs/Cluster/Service log_stream_name app/$(ecs_task_id) auto_create_group true retry_limit 2
-
EC2 踏み台から、修正した
extra.conf
ファイルを各環境用のフォルダにコピーする。- /configs/dev/extra.conf
- /configs/stg/extra.conf
- /configs/prd/extra.conf
-
各環境 FireLens 用のサイドカー定義に、FireLens 設定ファイルを指定する。
"firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/configs/{env}/extra.conf", "enable-ecs-log-metadata": "false" } }
-
ECS タスク定義のアプリケーション用のコンテナ定義部分の
logConfiguration
内容を修正する。"logConfiguration": { "logDriver": "awsfirelens", "options": { "Name": "cloudwatch", "log_group_name": "/ecs/Cluster/Service", "log_stream_name": "app/$(ecs_task_id)", "auto_create_group": "true", "log_key": "log", "region": "ap-northeast-1", "retry_limit": "2" } },
-
修正後のタスク定義は、下記のようになります。
ECS タスク定義(taskdef.json)
taskdef.json{ "containerDefinitions": [ { "name": "app", "image": "<IMAGE_URI>", "cpu": 0, "memory": 512, "memoryReservation": 256, "portMappings": [ { "name": "app-port", "containerPort": 8080, "hostPort": 8080, "protocol": "tcp", "appProtocol": "http" } ], "essential": true, "environment": [], "mountPoints": [], "volumesFrom": [], "logConfiguration": { "logDriver": "awsfirelens", "options": { "Name": "cloudwatch", "log_group_name": "/ecs/Cluster/Service", "log_stream_name": "app/$(ecs_task_id)", "auto_create_group": "true", "log_key": "log", "region": "ap-northeast-1", "retry_limit": "2" } }, "systemControls": [] }, { "name": "log_router", "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable", "cpu": 0, "memory": 256, "memoryReservation": 128, "portMappings": [], "essential": true, "environment": [], "mountPoints": [ { "sourceVolume": "firelens-configs", "containerPath": "/firelens-configs" } ], "volumesFrom": [], "user": "0", "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/Cluster/Service", "awslogs-create-group": "true", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs" } }, "systemControls": [], "firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/firelens-configs/extra.conf", "enable-ecs-log-metadata": "false" } } } ], "family": "ecs-taskdef-app", "taskRoleArn": "arn:aws:iam::{account_id}:role/role-ecs-task-exec", "executionRoleArn": "arn:aws:iam::{account_id}:role/role-ecs-task-exec", "networkMode": "awsvpc", "placementConstraints": [], "compatibilities": [ "EC2", "FARGATE" ], "requiresCompatibilities": [ "FARGATE" ], "cpu": "256", "memory": "512", "ephemeralStorage": { "sizeInGiB": 100 }, "volumes": [ { "name": "firelens-configs", "efsVolumeConfiguration": { "fileSystemId": "{file-system-id}", "rootDirectory": "/", "transitEncryption": "ENABLED", "authorizationConfig": { "accessPointId": "{access-point-id}", "iam": "ENABLED" } } } ], "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" } }