Subchapter 37.19
references/cloudwatch/appsignals-guides/ecs-nodejs.mdMarkdown8 KBView on GitHub
This guide provides complete steps to enable AWS Application Signals for ECS services (both EC2 and Fargate launch types), including distributed tracing, performance monitoring, and service mapping.
Constraints: You must strictly follow the steps in the order below, do not skip or combine steps.
const taskRole = new iam.Role(this, 'EcsTaskRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'),
iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'),
],
});const cwAgentLogGroup = new logs.LogGroup(this, 'CwAgentLogGroup', {
logGroupName: '/ecs/ecs-cwagent',
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.ONE_MONTH,
});const cwAgentContainer = taskDefinition.addContainer('ecs-cwagent-{{SERVICE_NAME}}', {
image: ecs.ContainerImage.fromRegistry('public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest'), // Use latest. ServiceEvents requires 1.300070.0+ (or 1.300069.0+).
essential: false,
memoryReservationMiB: 128,
cpu: 64,
environment: {
CW_CONFIG_CONTENT: JSON.stringify({
"traces": {
"traces_collected": {
"application_signals": {}
}
},
"logs": {
"metrics_collected": {
"application_signals": {}
}
}
}),
},
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'ecs',
logGroup: cwAgentLogGroup,
}),
});const taskDefinition = new ecs.FargateTaskDefinition(this, '{{SERVICE_NAME}}TaskDefinition', {
// Existing configuration...
volumes: [
{
name: "opentelemetry-auto-instrumentation-node"
}
],
});const initContainer = taskDefinition.addContainer('init', {
image: ecs.ContainerImage.fromRegistry('public.ecr.aws/aws-observability/adot-autoinstrumentation-node:v0.12.0'), // Minimum version for ServiceEvents. Check ../application-signals-onboarding.md for how to query the latest version.
essential: false,
memoryReservationMiB: 64,
cpu: 32,
command: ['cp', '-a', '/autoinstrumentation/.', '/otel-auto-instrumentation-node'],
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'init-{{SERVICE_NAME}}',
logGroup: serviceLogGroup,
}),
});
initContainer.addMountPoints({
sourceVolume: 'opentelemetry-auto-instrumentation-node',
containerPath: '/otel-auto-instrumentation-node',
readOnly: false,
});const mainContainer = taskDefinition.addContainer('{{SERVICE_NAME}}-container', {
// Existing configuration...
environment: {
// Existing environment variables...
// ADOT Configuration for Application Signals - Node.js
OTEL_RESOURCE_ATTRIBUTES: 'service.name={{SERVICE_NAME}}',
OTEL_METRICS_EXPORTER: 'none',
OTEL_LOGS_EXPORTER: 'none',
NODE_OPTIONS: '--require /otel-auto-instrumentation-node/autoinstrumentation.js', // CommonJS
OTEL_TRACES_EXPORTER: 'otlp',
OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: 'http://localhost:4316/v1/traces',
OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT: 'http://localhost:4316/v1/metrics',
OTEL_AWS_APPLICATION_SIGNALS_ENABLED: 'true',
},
});Module format note:
NODE_OPTIONS: '--require /otel-auto-instrumentation-node/autoinstrumentation.js'NODE_OPTIONS: '--import /otel-auto-instrumentation-node/autoinstrumentation.js --experimental-loader=/otel-auto-instrumentation-node/node_modules/@opentelemetry/instrumentation/instrumentation/hook.mjs'mainContainer.addMountPoints({
sourceVolume: 'opentelemetry-auto-instrumentation-node',
containerPath: '/otel-auto-instrumentation-node',
readOnly: false,
});mainContainer.addContainerDependencies({
container: initContainer,
condition: ecs.ContainerDependencyCondition.SUCCESS,
});
mainContainer.addContainerDependencies({
container: cwAgentContainer,
condition: ecs.ContainerDependencyCondition.START,
});Before reciting the summary below (guidance for you, not for the user): report both managed policies — the task role now carries two, and earlier versions of this summary named only
CloudWatchAgentServerPolicy. CurrentCloudWatchAgentServerPolicyalready grants thexray:Put*and sampling actions, soAWSXRayDaemonWriteAccessis largely redundant here. If the user asks to trim to least privilege, verify against the live policy documents (aws iam get-policy-version) rather than hand-rolling an inline policy from this guide. Resource scoping is the more valuable axis than pruning actions:CloudWatchAgentServerPolicygrants everything onResource: "*", so dropping actions still leaveslogs:PutLogEventson every log group in the account. And do not assume step 1.2 covers the agent’s log needs —/ecs/ecs-cwagentis the awslogs destination for the agent container’s stdout, written under the task execution role. The Application Signals data the agent itself publishes goes to/aws/application-signals/data, which this guide does not pre-create, sologs:CreateLogGroupandlogs:CreateLogStreammust survive any trim. A denial does not surface in the console — check the agent’s own log — and the symptom is that telemetry never starts arriving.
Tell the user:
“I’ve completed the Application Signals enablement for your application. Here’s what I modified:
Files Changed:
Next Steps:
git diffcdk deployterraform applyVerification: Once deployed, you can verify Application Signals is working by:
Monitor Application Health: After enablement, you can monitor your application’s operational health using Application Signals dashboards. For more information, see Monitor the operational health of your applications with Application Signals (opens in a new tab).
Troubleshooting If you encounter any other issues, refer to the CloudWatch APM troubleshooting guide (opens in a new tab).
Let me know if you’d like me to make any adjustments before you deploy!”