Expert OpenTelemetry guidance for collector configuration, pipeline design, and production telemetry instrumentation across Kubernetes, ECS, serverless, and standalone deployments. Use when configuring collectors, designing pipelines, instrumenting applications, implementing sampling, managing cardinality, securing telemetry, writing OTTL transformations, or setting up AI coding agent observability (Claude Code, Codex, Gemini CLI, GitHub Copilot).
94
98%
Does it follow best practices?
Impact
94%
1.34xAverage score across 18 eval scenarios
Passed
No known issues
AWS ECS (Elastic Container Service) supports two compute models: EC2 and Fargate. This guide covers OpenTelemetry Collector deployment patterns for both, with emphasis on AWS-specific networking, IAM roles, and task metadata integration.
Unlike Kubernetes, ECS lacks native DaemonSet support on EC2, so collectors must be deployed as replica services running on each instance via ASG-level scaling or explicit daemon scheduling.
EC2 Cluster Model:
┌──────────────────────────────────┐
│ Auto Scaling Group │
│ ┌────────────────────────────┐ │
│ │ EC2 Instance │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ App Task + Collector │ │ │
│ │ └──────────────────────┘ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ Collector (daemon) │ │ │
│ │ │ • host metrics │ │ │
│ │ │ • local logs │ │ │
│ │ └──────────────────────┘ │ │
│ └────────────────────────────┘ │
│ ┌────────────────────────────┐ │
│ │ EC2 Instance │ │
│ │ (same layout) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
↓ (OTLP:4317)
Backend/Gateway
Fargate Cluster Model:
┌──────────────────────────────────┐
│ Fargate (serverless) │
│ ┌────────────────────────────┐ │
│ │ Task (similar to pod) │ │
│ │ ├─ App Container │ │
│ │ ├─ Collector Sidecar │ │
│ │ └─ CloudWatch Agent │ │
│ │ (optional) │ │
│ └────────────────────────────┘ │
│ ┌────────────────────────────┐ │
│ │ Task (another deployment) │ │
│ │ (same layout) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
↓ (via NAT/IGW)
Backend/Gateway| Aspect | EC2 | Fargate |
|---|---|---|
| Cost Model | Pay per instance (shared resources) | Pay per task (isolated) |
| Host Metrics | ✓ Available (via host bind mount or IAM) | ✗ Not available (no host access) |
| Collector Placement | DaemonSet-style service or per-task | Sidecar only (per-task) |
| Networking | Flexible (host port, dynamic mapping) | Only awsvpc mode, no port mapping |
| Node Management | Required (patching, scaling) | Managed by AWS |
| Cold Starts | Faster (warm instances) | Slower (on-demand provisioning) |
{
"family": "otel-collector-daemon",
"networkMode": "host",
"requiresCompatibilities": ["EC2"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "otel-collector",
"image": "otel/opentelemetry-collector-contrib:latest",
"cpu": 256,
"memory": 512,
"essential": true,
"portMappings": [
{
"containerPort": 4317,
"hostPort": 4317,
"protocol": "tcp"
},
{
"containerPort": 4318,
"hostPort": 4318,
"protocol": "tcp"
}
],
"environment": [
{
"name": "GOGC",
"value": "80"
},
{
"name": "OTEL_EXPORTER_OTLP_ENDPOINT",
"value": "http://10.0.0.10:4317"
}
],
"mountPoints": [
{
"sourceVolume": "logs",
"containerPath": "/var/log",
"readOnly": true
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/otel-collector",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
],
"volumes": [
{
"name": "logs",
"host": {
"sourcePath": "/var/log"
}
}
]
}{
"cluster": "my-cluster",
"serviceName": "otel-collector-daemon",
"taskDefinition": "otel-collector-daemon:1",
"launchType": "EC2",
"schedulingStrategy": "DAEMON",
"deploymentConfiguration": {
"maximumPercent": 200,
"minimumHealthyPercent": 100
}
}Key gotchas (EC2):
localhost resolves to 127.0.0.1 inside the container, not the host IP; use host networking or IMDSv2 to discover host IP/var/log (may need --user root or uid adjustment){
"family": "app-with-collector",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::ACCOUNT:role/app-task-role",
"containerDefinitions": [
{
"name": "my-app",
"image": "myapp:latest",
"cpu": 256,
"memory": 512,
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{
"name": "OTEL_EXPORTER_OTLP_ENDPOINT",
"value": "http://localhost:4317"
},
{
"name": "OTEL_SERVICE_NAME",
"value": "my-app"
}
],
"dependsOn": [
{
"containerName": "otel-collector",
"condition": "START"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
},
{
"name": "otel-collector",
"image": "otel/opentelemetry-collector-contrib:latest",
"cpu": 256,
"memory": 512,
"essential": false,
"portMappings": [
{
"containerPort": 4317,
"protocol": "tcp"
},
{
"containerPort": 4318,
"protocol": "tcp"
}
],
"environment": [
{
"name": "GOGC",
"value": "80"
},
{
"name": "OTEL_EXPORTER_OTLP_ENDPOINT",
"value": "https://backend.example.com:4317"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:13133/ || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/otel-collector",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}Key gotchas (Fargate):
http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_FULL_URI} to access task metadataawsvpc mode supported; no hostPort mappingsecrets array backed by Secrets Manager; don't hardcode credentials in env"dependsOn": [
{
"containerName": "otel-collector",
"condition": "HEALTHY"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:13133/ || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
}"networkMode": "awsvpc",
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": ["subnet-xxx", "subnet-yyy"],
"securityGroups": ["sg-collector"],
"assignPublicIp": "DISABLED"
}
}Service Discovery (AWS CloudMap):
{
"serviceName": "otel-gateway",
"cluster": "my-cluster",
"taskDefinition": "otel-gateway",
"desiredCount": 2,
"launchType": "FARGATE",
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": ["subnet-xxx"],
"securityGroups": ["sg-gateway"],
"assignPublicIp": "DISABLED"
}
},
"serviceRegistries": [
{
"registryArn": "arn:aws:servicediscovery:us-east-1:ACCOUNT:service/sd/otel-gateway"
}
]
}In collector config, use otel-gateway.sd:4317 as endpoint.
exporters:
otlphttp:
endpoint: https://api.example.com:4318
headers:
Authorization: "Bearer ${env:BACKEND_API_KEY}"
X-Trace-Source: "ecs-fargate"Inject the secret through the container definition's secrets array:
"secrets": [
{
"name": "BACKEND_API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:ACCOUNT:secret:backend-key:token::"
}
]exporters:
awsemf:
namespace: "OpenTelemetry"
region: "us-east-1"
metric_declaration:
- dimensions: ["Service"]
metric_name_pattern: ".*"Diagnostic steps:
# Check task networking
aws ecs describe-tasks --cluster my-cluster --tasks arn:... \
--query 'tasks[0].attachments'
# Verify Security Group allows egress
aws ec2 describe-security-groups --group-ids sg-xxx
# Test connectivity from container
aws ecs execute-command --cluster my-cluster --task <task-id> \
--container otel-collector \
--command "curl -v https://backend.example.com:4317"Common causes:
HTTPS 443 to 0.0.0.0/0Diagnostic steps:
# Check memory allocation
aws ecs describe-task-definition --task-definition app-with-collector:1 \
--query 'taskDefinition.containerDefinitions[?name==`otel-collector`].memory'
# View CloudWatch logs for OOM kill
aws logs tail /ecs/otel-collector --follow
# Check actual memory usage in task stats
aws ecs describe-tasks --cluster my-cluster --tasks arn:... \
--query 'tasks[0].containers[?name==`otel-collector`].lastStatus'Common causes:
memory_limiter processorsend_batch_sizeDiagnostic steps:
# Verify Prometheus receiver is enabled in config
aws ecs describe-task-definition --task-definition otel-collector-daemon:1 \
--query 'taskDefinition.containerDefinitions[0].environment'
# Check IAM role permissions for EC2 scraping
aws iam get-role-policy --role-name collector-task-role --policy-name ...
# Test scrape endpoint from task
aws ecs execute-command --cluster my-cluster --task <task-id> \
--container otel-collector \
--command "curl -s http://localhost:8888/metrics | head -20"Common causes:
receivers: sectionec2:DescribeInstances to policy.claude-plugin
.codex-plugin
.cursor-plugin
.github
scripts
docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
references