OpenTelemetry Collector Contribをサイドカーコンテナとしてデプロイすることで、EC2インスタンス上で実行されているAmazon ECSタスクをモニターします。この包括的なガイドでは、ECS on EC2ワークロード用のタスク定義の作成、コレクターの設定、および監視の設定について説明します。
インストレーション手順
ECS on EC2タスクの監視を設定するには、次の手順に従います。
あなたが始める前に
環境が次の要件を満たしていることを確認してください。
New Relicライセンスキーを保存する
OpenTelemetry Collectorの認証情報を安全に保存するために、ライセンスキーをSystems Manager(SSM)パラメーターとして保存します:
$aws ssm put-parameter \> --name "/newrelic-infra/ecs/license-key" \> --type SecureString \> --description 'New Relic license key for ECS monitoring' \> --value "YOUR_NEW_RELIC_LICENSE_KEY"IAMポリシーと実行ロールの作成
ECSコンテナがNew Relicライセンスキーを安全に取得できるように、IAMポリシーを作成します:
bash$aws iam create-policy \>--policy-name "NewRelicSSMLicenseKeyReadAccess" \>--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ssm:GetParameters"],"Resource":["arn:aws:ssm:*:*:parameter/newrelic-infra/ecs/license-key"]}]}' \>--description "Provides read access to the New Relic SSM license key parameter"タスク実行ロールとして使用するIAMロールを作成します。
bash$aws iam create-role \>--role-name "NewRelicECSTaskExecutionRole" \>--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \>--description "ECS task execution role for New Relic infrastructure"必要なマネージドポリシーをロールにアタッチします:
bash$# Attach the standard ECS task execution policy$aws iam attach-role-policy \>--role-name "NewRelicECSTaskExecutionRole" \>--policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"$$# Attach the New Relic SSM license key read access policy$aws iam attach-role-policy \>--role-name "NewRelicECSTaskExecutionRole" \>--policy-arn "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/NewRelicSSMLicenseKeyReadAccess"
コレクターの設定を保存する
コンテナイメージを再ビルドせずに設定を管理および更新できるように、OpenTelemetry Collector の設定を AWS Systems Manager Parameter Store に保存します:
$aws ssm put-parameter \> --name "/ecs/otel-collector/ec2-config" \> --type "String" \> --value "$(cat <<EOF$receivers:$ awsecscontainermetrics:$ collection_interval: <COLLECTION_INTERVAL>$ hostmetrics:$ collection_interval: <COLLECTION_INTERVAL>$ scrapers:$ cpu:$ metrics:$ system.cpu.time:$ enabled: false$ system.cpu.utilization:$ enabled: true$ load:$ memory:$ metrics:$ system.memory.utilization:$ enabled: true$ paging:$ metrics:$ system.paging.utilization:$ enabled: false$ system.paging.faults:$ enabled: false$ filesystem:$ metrics:$ system.filesystem.utilization:$ enabled: true$ disk:$ metrics:$ system.disk.merged:$ enabled: false$ system.disk.pending_operations:$ enabled: false$ system.disk.weighted_io_time:$ enabled: false$ network:$ metrics:$ system.network.connections:$ enabled: false$
$processors:$ metricstransform:$ transforms:$ - include: system.cpu.utilization$ action: update$ operations:$ - action: aggregate_labels$ label_set: [ state ]$ aggregation_type: mean$ - include: system.paging.operations$ action: update$ operations:$ - action: aggregate_labels$ label_set: [ direction ]$ aggregation_type: sum$ filter/exclude_cpu_utilization:$ metrics:$ datapoint:$ - 'metric.name == "system.cpu.utilization" and attributes["state"] == "interrupt"'$ - 'metric.name == "system.cpu.utilization" and attributes["state"] == "nice"'$ - 'metric.name == "system.cpu.utilization" and attributes["state"] == "softirq"'$ filter/exclude_memory_utilization:$ metrics:$ datapoint:$ - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_unreclaimable"'$ - 'metric.name == "system.memory.utilization" and attributes["state"] == "inactive"'$ - 'metric.name == "system.memory.utilization" and attributes["state"] == "cached"'$ - 'metric.name == "system.memory.utilization" and attributes["state"] == "buffered"'$ - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_reclaimable"'$ filter/exclude_memory_usage:$ metrics:$ datapoint:$ - 'metric.name == "system.memory.usage" and attributes["state"] == "slab_unreclaimable"'$ - 'metric.name == "system.memory.usage" and attributes["state"] == "inactive"'$ filter/exclude_filesystem_utilization:$ metrics:$ datapoint:$ - 'metric.name == "system.filesystem.utilization" and attributes["type"] == "squashfs"'$ filter/exclude_filesystem_usage:$ metrics:$ datapoint:$ - 'metric.name == "system.filesystem.usage" and attributes["type"] == "squashfs"'$ - 'metric.name == "system.filesystem.usage" and attributes["state"] == "reserved"'$ filter/exclude_filesystem_inodes_usage:$ metrics:$ datapoint:$ - 'metric.name == "system.filesystem.inodes.usage" and attributes["type"] == "squashfs"'$ - 'metric.name == "system.filesystem.inodes.usage" and attributes["state"] == "reserved"'$ filter/exclude_system_disk:$ metrics:$ datapoint:$ - 'metric.name == "system.disk.operations" and IsMatch(attributes["device"], "^loop.*") == true'$ - 'metric.name == "system.disk.merged" and IsMatch(attributes["device"], "^loop.*") == true'$ - 'metric.name == "system.disk.io" and IsMatch(attributes["device"], "^loop.*") == true'$ - 'metric.name == "system.disk.io_time" and IsMatch(attributes["device"], "^loop.*") == true'$ - 'metric.name == "system.disk.operation_time" and IsMatch(attributes["device"], "^loop.*") == true'$ filter/exclude_system_paging:$ metrics:$ datapoint:$ - 'metric.name == "system.paging.usage" and attributes["state"] == "cached"'$ - 'metric.name == "system.paging.operations" and attributes["type"] == "cached"'$ filter/exclude_network:$ metrics:$ datapoint:$ - 'IsMatch(metric.name, "^system.network.*") == true and attributes["device"] == "lo"'$
$ attributes/exclude_system_paging:$ include:$ match_type: strict$ metric_names:$ - system.paging.operations$ actions:$ - key: type$ action: delete$
$ cumulativetodelta:$
$ transform/host:$ metric_statements:$ - context: metric$ statements:$ - set(metric.description, "")$ - set(metric.unit, "")$
$ transform:$ trace_statements:$ - context: span$ statements:$ - truncate_all(span.attributes, <ATTRIBUTE_TRUNCATION_LIMIT>)$ - truncate_all(resource.attributes, <RESOURCE_ATTRIBUTE_TRUNCATION_LIMIT>)$ log_statements:$ - context: log$ statements:$ - truncate_all(log.attributes, <ATTRIBUTE_TRUNCATION_LIMIT>)$ - truncate_all(resource.attributes, <RESOURCE_ATTRIBUTE_TRUNCATION_LIMIT>)$
$ memory_limiter:$ check_interval: <MEMORY_LIMITER_CHECK_INTERVAL>$ limit_mib: \${env:NEW_RELIC_MEMORY_LIMIT_MIB:-<MEMORY_LIMIT_MIB>}$ metricstransform/base:$ transforms:$ - include: container.cpu.utilized$ action: insert$ new_name: container.cpu.utilization$ - include: container.memory.usage$ action: insert$ new_name: container.memory.usage.total$ - include: container.storage.read_bytes$ action: insert$ new_name: container.blockio.io_service_bytes_recursive$ operations:$ - action: add_label$ new_label: operation$ new_value: read$ - include: container.storage.write_bytes$ action: insert$ new_name: container.blockio.io_service_bytes_recursive$ operations:$ - action: add_label$ new_label: operation$ new_value: write$ transform/promote_cluster_keys:$ metric_statements:$ - context: datapoint$ statements:$ - set(attributes["aws.ecs.cluster.name"], resource.attributes["aws.ecs.cluster.name"]) where resource.attributes["aws.ecs.cluster.name"] != nil$ - set(attributes["cloud.account.id"], resource.attributes["cloud.account.id"]) where resource.attributes["cloud.account.id"] != nil$ - set(attributes["cloud.region"], resource.attributes["cloud.region"]) where resource.attributes["cloud.region"] != nil$ - set(attributes["aws.ecs.task.arn"], resource.attributes["aws.ecs.task.arn"]) where resource.attributes["aws.ecs.task.arn"] != nil$ metricstransform/cluster_aggregation:$ transforms:$ - include: container.cpu.utilization$ action: insert$ new_name: ecs.cluster.cpu.utilization$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: mean$ - include: container.memory.usage.total$ action: insert$ new_name: ecs.cluster.memory.usage$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: sum$ - include: container.network.io.usage.rx_bytes$ action: insert$ new_name: ecs.cluster.network.rx_bytes$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: sum$ - include: container.network.io.usage.tx_bytes$ action: insert$ new_name: ecs.cluster.network.tx_bytes$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: sum$ - include: container.cpu.utilization$ action: insert$ new_name: ecs.cluster.running_task_count$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region, aws.ecs.task.arn]$ aggregation_type: mean$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: count$ - include: container.memory.usage.limit$ action: insert$ new_name: ecs.cluster.memory.limit$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: sum$ transform/running_task_count_one:$ metric_statements:$ - context: datapoint$ statements:$ - set(value_double, 1.0) where metric.name == "ecs.cluster.running_task_count"$ metricstransform/running_task_count_sum:$ transforms:$ - include: ecs.cluster.running_task_count$ action: update$ operations:$ - action: aggregate_labels$ label_set: [aws.ecs.cluster.name, cloud.account.id, cloud.region]$ aggregation_type: sum$ filter/cluster_metrics:$ metrics:$ include:$ match_type: regexp$ metric_names:$ - ^ecs\.cluster\..*$ metricstransform/task_metrics:$ transforms:$ - include: ecs.task.cpu.utilized$ action: insert$ new_name: ecs.task.cpu.utilization$ - include: container.memory.usage.total$ action: insert$ new_name: ecs.task.memory.usage$ filter/task_metrics:$ metrics:$ include:$ match_type: strict$ metric_names: $ - "ecs.task.cpu.utilization" $ - "ecs.task.cpu.utilized" $ - "ecs.task.memory.utilized" $ - "ecs.task.memory.usage" $ - "ecs.task.memory.usage.limit" $ - "ecs.task.network.io.usage.rx_bytes" $ - "ecs.task.network.io.usage.tx_bytes"$ filter/container_metrics:$ metrics:$ include:$ match_type: regexp$ metric_names:$ - ^container\..*$ transform/cluster_identifier:$ metric_statements:$ - context: resource$ statements:$ - set(attributes["aws.arn"], resource.attributes["aws.ecs.task.arn"]) where resource.attributes["aws.ecs.task.arn"] != nil and resource.attributes["aws.ecs.cluster.name"] != nil$ - replace_pattern(attributes["aws.arn"], ":task/.*", "") where attributes["aws.arn"] != nil$ - set(attributes["aws.arn"], Concat([attributes["aws.arn"], ":cluster/", resource.attributes["aws.ecs.cluster.name"]], "")) where attributes["aws.arn"] != nil and resource.attributes["aws.ecs.cluster.name"] != nil$ transform/task_identifier:$ metric_statements:$ - context: resource$ statements:$ - set(attributes["TaskArn"], resource.attributes["aws.ecs.task.arn"]) where resource.attributes["aws.ecs.task.arn"] != nil$ batch:$ send_batch_size: <SEND_BATCH_SIZE>$ timeout: <BATCH_TIMEOUT>$ resource:$ attributes:$ - key: ClusterName$ from_attribute: aws.ecs.cluster.name$ action: insert$ - key: ServiceName$ from_attribute: aws.ecs.service.name$ action: insert$ - key: TaskId$ from_attribute: aws.ecs.task.id$ action: insert$ - key: TaskDefinitionFamily$ from_attribute: aws.ecs.task.family$ action: insert$ - key: LaunchType$ from_attribute: aws.ecs.launch_type$ action: insert$ resource/cluster:$ attributes:$ - key: ClusterName$ from_attribute: aws.ecs.cluster.name$ action: insert$ - key: cloud.platform$ value: "aws_ecs"$ action: upsert$
$ resource/task:$ attributes:$ - key: ClusterName$ from_attribute: aws.ecs.cluster.name$ action: insert$ - key: ServiceName$ from_attribute: aws.ecs.service.name$ action: insert$ - key: TaskId$ from_attribute: aws.ecs.task.id$ action: insert$ - key: TaskArn$ from_attribute: aws.ecs.task.arn$ action: insert$ - key: TaskDefinitionFamily$ from_attribute: aws.ecs.task.family$ action: insert$ - key: LaunchType$ from_attribute: aws.ecs.launch_type$ action: insert$ - key: cloud.platform$ value: "aws_ecs"$ action: upsert$ - key: docker.host$ from_attribute: aws.ecs.task.id$ action: insert$ - key: docker.imageName$ from_attribute: container.image.name$ action: insert$ - key: docker.containerId$ from_attribute: container.id$ action: insert$ - key: docker.state$ from_attribute: aws.ecs.container.know_status$ action: insert$ resourcedetection:$ detectors:$ - env$ - ecs$ - ec2$ - system$ timeout: <RESOURCE_DETECTION_TIMEOUT>$ override: false$
$exporters:$ otlphttp:$ endpoint: https://otlp.nr-data.net:443$ headers:$ api-key: \${NEW_RELIC_LICENSE_KEY}$
$ debug:$ verbosity: basic$
$service:$pipelines:$ metrics/clusters:$ receivers: [awsecscontainermetrics]$ processors: [$ metricstransform/base,$ transform/promote_cluster_keys, $ metricstransform/cluster_aggregation,$ transform/running_task_count_one,$ metricstransform/running_task_count_sum, $ filter/cluster_metrics,$ transform/cluster_identifier,$ resource/cluster,$ batch$ ]$ exporters: [otlphttp, debug]$
$ metrics/containers:$ receivers: [awsecscontainermetrics]$ processors: [$ metricstransform/base,$ filter/container_metrics,$ resource/task,$ batch$ ]$ exporters: [otlphttp, debug]$
$ metrics/tasks:$ receivers: [awsecscontainermetrics]$ processors: [$ metricstransform/base,$ metricstransform/task_metrics,$ filter/task_metrics,$ transform/task_identifier,$ resource/task,$ batch$ ]$ exporters: [otlphttp, debug]$
$ metrics/host:$ receivers: [hostmetrics]$ processors:$ - memory_limiter$ - metricstransform$ - filter/exclude_cpu_utilization$ - filter/exclude_memory_utilization$ - filter/exclude_memory_usage$ - filter/exclude_filesystem_utilization$ - filter/exclude_filesystem_usage$ - filter/exclude_filesystem_inodes_usage$ - filter/exclude_system_disk$ - filter/exclude_network$ - attributes/exclude_system_paging$ - transform/host$ - resourcedetection$ - cumulativetodelta$ - batch$ exporters: [otlphttp, debug]$EOF$)"設定パラメーター
OpenTelemetry Collectorの設定では、以下のパラメーターをカスタマイズできます:
パラメータ | 説明 |
|---|---|
| ECSコンテナおよびホストのメトリクスエンドポイントからメトリクスを収集する間隔。 |
| MiB単位のOpenTelemetry Collectorのメモリ制限 |
| メモリリミッターが現在のメモリ使用量を確認する間隔 |
| New Relicに送信する前にバッチ処理するメトリクスの数 |
| バッチを送信するまでの最大待ち時間 |
| リソース検出プロセッサのタイムアウト |
| 切り捨てられる前のスパンおよびログ属性値の最大長。デフォルト:4095 |
| 切り詰められる前のリソース属性値の最大長。デフォルト:4095 |
タスク定義を作成
Create a new ECS task definition that includes the collector sidecar container. Choose between NRDOT Collector (New Relic's distribution) or OpenTelemetry Collector, then select the task definition for your container platform:
ヒント
NRDOT Collector is New Relic's distribution of the OpenTelemetry Collector with New Relic support for assistance. It bundles the awsecscontainermetrics and hostmetrics receivers used by this configuration, so it runs with the same collector config you stored in the previous step.
重要
NRDOT Collector publishes Linux container images only. To monitor Windows containers, use the OpenTelemetry Collector tab.
{ "family": "otel-ecs-ec2-sidecar-metrics", "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT:role/NewRelicECSTaskExecutionRole", "networkMode": "host", "requiresCompatibilities": ["EC2"], "cpu": "<TASK_CPU>", "memory": "<TASK_MEMORY>", "containerDefinitions": [ { "name": "your-application", "image": "your-app:latest", "cpu": <APP_CPU>, "memory": <APP_MEMORY>, "essential": true, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "<APP_LOG_GROUP>", "awslogs-create-group": "true", "awslogs-region": "<AWS_REGION>", "awslogs-stream-prefix": "<APP_LOG_STREAM_PREFIX>" } } }, { "name": "otel-collector", "image": "newrelic/nrdot-collector:latest", "cpu": <COLLECTOR_CPU>, "memory": <COLLECTOR_MEMORY>, "essential": true, "command": ["--config=env:OTEL_CONFIG"], "mountPoints": [ { "sourceVolume": "proc", "containerPath": "/proc", "readOnly": true }, { "sourceVolume": "sys", "containerPath": "/sys", "readOnly": true } ], "secrets": [ { "name": "OTEL_CONFIG", "valueFrom": "arn:aws:ssm:us-east-1:YOUR_ACCOUNT:parameter/ecs/otel-collector/ec2-config" }, { "name": "NEW_RELIC_LICENSE_KEY", "valueFrom": "arn:aws:ssm:us-east-1:YOUR_ACCOUNT:parameter/newrelic-infra/ecs/license-key" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "<COLLECTOR_LOG_GROUP>", "awslogs-create-group": "true", "awslogs-region": "<AWS_REGION>", "awslogs-stream-prefix": "<COLLECTOR_LOG_STREAM_PREFIX>" } } } ], "volumes": [ { "name": "proc", "host": { "sourcePath": "/proc" } }, { "name": "sys", "host": { "sourcePath": "/sys" } } ]}タスク定義パラメーター
ECSタスク定義では、以下のパラメーターをカスタマイズできます:
パラメータ | 説明 |
|---|---|
| EC2タスクの合計CPUユニット |
| EC2タスクの合計メモリ(MiB単位) |
| アプリケーションコンテナに割り当てられたCPUユニット |
| アプリケーションコンテナに割り当てられたメモリ(MiB単位) |
| OpenTelemetry Collectorに割り当てられたCPUユニット |
| OpenTelemetry Collectorに割り当てられたメモリ(MiB) |
| アプリケーションコンテナのCloudWatchロググループ名 |
| OpenTelemetry Collector 用の CloudWatch ロググループ名 |
| CloudWatchログ用のAWSリージョン |
| アプリケーションコンテナのログストリームプレフィックス |
| OpenTelemetry Collector 用のログストリームプレフィックス |
ヒント
networkModeはLinuxコンテナの場合は"host"に設定されており、Windowsコンテナの場合は"default"にする必要があります。ホストモードにより、EC2インスタンス上のシステムメトリクスへのアクセスが向上します。
重要
YOUR_ACCOUNTとリージョンの値を、実際のAWSアカウントIDとAWSリージョンに置き換えてください。
タスクをデプロイして実行する
タスク定義をECSクラスタにデプロイします:
タスク定義を登録します。
bash$aws ecs register-task-definition --cli-input-json file://task-definition.jsonデーモンスケジューリング戦略を使用してサービスを作成します:
bash$aws ecs create-service \>--cluster your-cluster-name \>--service-name otel-monitoring-service \>--task-definition otel-ecs-ec2-sidecar-metrics:1 \>--scheduling-strategy DAEMON \>--launch-type EC2ヒント
デーモンスケジューリング戦略は、クラスタ内のすべてのEC2インスタンスで1つの監視タスクが実行されることを保証し、包括的なinfrastructureモニタリングのカバレッジを提供します。
データ収集を確認する
データがNew Relicに取り込まれていることを確認します:
OpenTelemetry Collectorのステータスを確認する:コンテナのログを確認して、コレクターがエラーなしで実行され、New Relicに正常に接続されていることを確認します:
bash$aws logs get-log-events \>--log-group-name "/ecs/otel-collector-ec2" \>--log-stream-name "otel/otel-collector/TASK_ID"New Relic UIでデータを確認する:one.newrelic.com > All Capabilities > Infrastructureに移動して、ECSホストとコンテナがメトリクスとともに表示されることを確認します。データの探索に関する詳細なガイダンスについては、ECS監視データを検索してクエリを実行するを参照してください。
次のステップ
監視を設定した後、次のことができます:
- ECSメトリクスに対してカスタムダッシュボードを作成します
- コンテナおよびホストレベルの問題に対するアラートの設定
- ECSメトリクスをアプリケーショントレースおよびログと関連付ける