From e80aad4f83768ce3cca9b7d881d2055aad09ce3a Mon Sep 17 00:00:00 2001 From: Zhou Date: Tue, 21 Jan 2025 23:43:51 -0800 Subject: [PATCH] Address additional comments --- .../aws-applicationsignals-alpha/README.md | 293 ++++++++---------- .../lib/enablement/ecs.ts | 52 ++-- 2 files changed, 160 insertions(+), 185 deletions(-) diff --git a/packages/@aws-cdk/aws-applicationsignals-alpha/README.md b/packages/@aws-cdk/aws-applicationsignals-alpha/README.md index a33bd95a290ba..96cbe9b09e00e 100644 --- a/packages/@aws-cdk/aws-applicationsignals-alpha/README.md +++ b/packages/@aws-cdk/aws-applicationsignals-alpha/README.md @@ -15,196 +15,173 @@ - - ---- - -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. - ---- - -## Application Signals - CloudWatch Application Signals is an auto-instrumentation solution built on OpenTelemetry that enables zero-code collection of monitoring data, such -as traces and metrics, from applications running across multiple platforms. It also supports topology auto-discovery based on collected monitoring -data and includes a new feature for managing service-level objectives (SLOs). +as traces and metrics, from applications running across multiple platforms. It also supports topology auto-discovery based on collected monitoring data +and includes a new feature for managing service-level objectives (SLOs). It supports Java, Python, .NET, and Node.js on platforms including EKS (and native Kubernetes), Lambda, ECS, and EC2. For more details, visit [Application Signals](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Monitoring-Sections.html) on the AWS public website. -## Application Signals L2 CDK for enablement +## Application Signals Enablement L2 Constructs -This module is a collection of L2 constructs which leverages native L1 CFN resources, simplifying the enablement steps and the creation of Application +A collection of L2 constructs which leverages native L1 CFN resources, simplifying the enablement steps and the creation of Application Signals resources. -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. - -```ts nofixture -import * as appsignals from '@aws-cdk/aws-applicationsignals-alpha'; -``` - ### ApplicationSignalsIntegration `ApplicationSignalsIntegration` aims to address key challenges in the current CDK enablement process, which requires complex manual configurations for ECS customers. Application Signals is designed to be flexible and is supported for other platforms as well. However, the initial focus is on supporting ECS, with plans to potentially extend support to other platforms in the future. -#### 1. Enable Application Signals on ECS with sidecar mode - -##### 1.1. Create a TaskDefinition for application - -```ts -import * as ecs from 'aws-cdk-lib/aws-ecs'; - -const fargateTaskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDefinition', { - taskRole: taskRole, - cpu: 2048, - memoryLimitMiB: 4096 -}); - -fargateTaskDefinition.addContainer('app', { - image: ..., - ... -}); -``` - -##### 1.2. Configure ApplicationSignalsIntegration - -Key steps: +#### Enable Application Signals on ECS with sidecar mode 1. Configure `instrumentation` to instrument the application with the ADOT Java Agent. 2. Setting `enableSidecar` to true to add the CloudWatch Agent as a sidecar container. ```ts -new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', { - taskDefinition, - instrumentation: { - language: appsignals.InstrumentationLanguage.JAVA, - sdkVersion: appsignals.JavaInstrumentationVersion.V1_32_6 - }, - serviceName: 'sample-app', - cloudWatchAgent: { - enableSidecar: true, - } -}); +import { Construct } from 'constructs'; +import * as appsignals from '@aws-cdk/aws-applicationsignals-alpha'; +import * as cdk from 'aws-cdk-lib'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; + +class MyStack extends cdk.Stack { + public constructor(scope?: Construct, id?: string, props: cdk.StackProps = {}) { + super(); + const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'FargateTaskDefinition', { + cpu: 2048, + memoryLimitMiB: 4096 + }); + + fargateTaskDefinition.addContainer('app', { + image: ecs.ContainerImage.fromRegistry('test/sample-app'), + }); + + new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', { + taskDefinition: fargateTaskDefinition, + instrumentation: { + sdkVersion: appsignals.JavaInstrumentationVersion.V1_32_6 + }, + serviceName: 'sample-app', + cloudWatchAgent: { + enableSidecar: true, + } + }); + } +} ``` -#### 2. Enable Application Signals on ECS with daemon mode +#### Enable Application Signals on ECS with daemon mode Note: Since the daemon deployment strategy is not supported on ECS Fargate, this mode is only supported on ECS on EC2. -##### 2.1. Run CloudWatch Agent as a daemon service +1. Run CloudWatch Agent as a daemon service with service connect. +1. Configure `instrumentation` to instrument the application with the ADOT Python Agent. +1. Set `enableSidecar` to false to disable running CloudWatch agent as a sidecar. +1. Override environment variables by configuring `overrideEnvironments` to use service connect endpoints to communicate to the CloudWatch agent server ```ts +import { Construct } from 'constructs'; +import * as appsignals from '@aws-cdk/aws-applicationsignals-alpha'; +import * as cdk from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as logs from 'aws-cdk-lib/aws-logs'; -// Create a task definition for CloudWatch agent -const cwAgentTaskDefinition = new ecs.Ec2TaskDefinition(this, 'CloudWatchAgentTaskDefinition'); - -// Add CloudWatch agent container -const cwAgentContainer = cwAgentTaskDefinition.addContainer('ecs-cwagent', { - image: ecs.ContainerImage.fromRegistry("public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest"), - cpu: 128, - memoryLimitMiB: 64, - portMappings: [ - { - name: 'cwagent-4316', - containerPort: 4316, - hostPort: 4316, - }, - { - name: 'cwagent-2000', - containerPort: 2000, - hostPort: 2000, - }, - ], - logging: new ecs.AwsLogDriver({ +class MyStack extends cdk.Stack { + public constructor(scope?: Construct, id?: string, props: cdk.StackProps = {}) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'TestVpc', {}); + const cluster = new ecs.Cluster(this, 'TestCluster', { vpc }); + // Create a task definition for CloudWatch agent + const cwAgentTaskDefinition = new ecs.Ec2TaskDefinition(this, 'CloudWatchAgentTaskDefinition'); + + // Add CloudWatch agent container + const cwAgentContainer = cwAgentTaskDefinition.addContainer('ecs-cwagent', { + image: ecs.ContainerImage.fromRegistry("public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest"), + cpu: 128, + memoryLimitMiB: 64, + portMappings: [ + { + name: 'cwagent-4316', + containerPort: 4316, + hostPort: 4316, + }, + { + name: 'cwagent-2000', + containerPort: 2000, + hostPort: 2000, + }, + ], + logging: new ecs.AwsLogDriver({ streamPrefix: 'ecs', logGroup: new logs.LogGroup(this, 'CwAgentLogGroup', { - logGroupName: '/ecs/ecs-cwagent-cdk-daemon', - removalPolicy: cdk.RemovalPolicy.DESTROY, + logGroupName: '/ecs/ecs-cwagent-cdk-daemon', + removalPolicy: cdk.RemovalPolicy.DESTROY, }), - }), - environment: { - CW_CONFIG_CONTENT: '{"traces": {"traces_collected": {"application_signals": {}}}, "logs": {"metrics_collected":{"application_signals": {}}}}' - } -}); - -// Create the CloudWatch agent daemon service -new ecs.Ec2Service(this, 'CloudWatchAgentDaemon', { - cluster, - taskDefinition: cwAgentTaskDefinition, - daemon: true, // Runs one container per EC2 instance - serviceConnectConfiguration: { // Create service connect - namespace: namespace.namespaceArn, - services: [{ - portMappingName: 'cwagent-4316', - dnsName: 'cwagent-4316-http', - port: 4316 - }, { - portMappingName: 'cwagent-2000', - dnsName: 'cwagent-2000-http', - port: 2000 - }] - } -}); -``` - -##### 2.2. Create a TaskDefinition for application - -```ts -import * as ecs from 'aws-cdk-lib/aws-ecs'; - -const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'EC2TaskDefinition', { - taskRole: taskRole, - ... -}); - -ec2TaskDefinition.addContainer('app', { - image: ..., - ... -}); -``` - -##### 2.3. Configure ApplicationSignalsIntegration - -Key steps: - -1. Configure `instrumentation` to instrument the application with the ADOT Python Agent. -2. Set `enableSidecar` to false to disable running CloudWatch agent as a sidecar. -3. Override environment variables by configuring `overrideEnvironments` to use Service Connect to communicate to the CloudWatch agent server - -```ts -new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', ec2TaskDefinition, { - instrumentation: { - language: appsignals.InstrumentationLanguage.PYTHON, - sdkVersion: appsignals.PythonInstrumentationVersion.V0_8_0 - }, - overrideEnvironments: [{ - name: appsignals.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT, - // overwrite the endpoint to point to the created Service Connect - value: "http://cwagent-4316-http:4316/v1/metrics" - }, { - name: appsignals.TraceExporting.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, - // overwrite the endpoint to point to the created Service Connect - value: "http://cwagent-4316-http:4316/v1/traces" - }, { - name: appsignals.TraceExporting.OTEL_TRACES_SAMPLER_ARG, - // overwrite the endpoint to point to the created Service Connect - value: "endpoint=http://cwagent-2000-http:2000" - }], - serviceName: 'sample-app', - cloudWatchAgent: { - // disable sidecar mode - enableSidecar: false, + }), + environment: { + CW_CONFIG_CONTENT: '{"traces":{"traces_collected":{"application_signals":{}}},"logs":{"metrics_collected":{"application_signals":{}}}}' + } + }); + + // Create the CloudWatch agent daemon service + new ecs.Ec2Service(this, 'CloudWatchAgentDaemon', { + cluster, + taskDefinition: cwAgentTaskDefinition, + daemon: true, // Runs one container per EC2 instance + serviceConnectConfiguration: { + namespace: 'namespace-arn', + services: [ + { + portMappingName: 'cwagent-4316', + dnsName: 'cwagent-4316-http', + port: 4316 + }, + { + portMappingName: 'cwagent-2000', + dnsName: 'cwagent-2000-http', + port: 2000 + } + ] + } + }); + + const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'Ec2TaskDefinition', { + networkMode: ecs.NetworkMode.HOST, + }); + + ec2TaskDefinition.addContainer('app', { + image: ecs.ContainerImage.fromRegistry('test/sample-app'), + cpu: 0, + memoryLimitMiB: 512, + }); + + new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', { + taskDefinition: ec2TaskDefinition, + instrumentation: { + sdkVersion: appsignals.PythonInstrumentationVersion.V0_8_0 + }, + overrideEnvironments: [{ + name: appsignals.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT, + // overwrite the endpoint to point to the created Service Connect + value: "http://cwagent-4316-http:4316/v1/metrics" + }, { + name: appsignals.TraceExporting.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, + // overwrite the endpoint to point to the created Service Connect + value: "http://cwagent-4316-http:4316/v1/traces" + }, { + name: appsignals.TraceExporting.OTEL_TRACES_SAMPLER_ARG, + // overwrite the endpoint to point to the created Service Connect + value: "endpoint=http://cwagent-2000-http:2000" + }], + serviceName: 'sample-app', + cloudWatchAgent: { + // disable sidecar mode + enableSidecar: false, + } + }); } -}); +} ``` diff --git a/packages/@aws-cdk/aws-applicationsignals-alpha/lib/enablement/ecs.ts b/packages/@aws-cdk/aws-applicationsignals-alpha/lib/enablement/ecs.ts index a6e82cc9eb1db..02fdbe6cdd0e7 100644 --- a/packages/@aws-cdk/aws-applicationsignals-alpha/lib/enablement/ecs.ts +++ b/packages/@aws-cdk/aws-applicationsignals-alpha/lib/enablement/ecs.ts @@ -271,13 +271,11 @@ abstract class SDKInject { */ protected abstract get containerPath(): string; /** - * Inject additional environment variables to the application container - * other than the DEFAULT_ENVS. + * Inject additional environment variables to the application container other than the DEFAULT_ENVS. */ protected abstract injectAdditionalEnvironments(envsToInject: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void; /** - * Override additional environment variables to the application container - * other than the DEFAULT_ENVS. + * Override environment variables in the application container. */ protected abstract overrideAdditionalEnvironments(envsToOverride: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void; @@ -605,30 +603,30 @@ class NodeSDKInject extends SDKInject { } } -const CLOUDWATCH_AGENT_IMAGE = 'amazon/cloudwatch-agent:latest'; -const CLOUDWATCH_AGENT_IMAGE_WIN2019 = 'public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest-windowsservercore2019'; -const CLOUDWATCH_AGENT_IMAGE_WIN2022 = 'public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest-windowsservercore2022'; -const CW_CONFIG_CONTENT = { - logs: { - metrics_collected: { - application_signals: { - enabled: true, +/** + * Class for integrating Application Signals into an ECS task definition. + */ +export class ApplicationSignalsIntegration extends Construct { + private static readonly CLOUDWATCH_AGENT_IMAGE = 'amazon/cloudwatch-agent:latest'; + private static readonly CLOUDWATCH_AGENT_IMAGE_WIN2019 = 'public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest-windowsservercore2019'; + private static readonly CLOUDWATCH_AGENT_IMAGE_WIN2022 = 'public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest-windowsservercore2022'; + private static readonly CW_CONFIG_CONTENT = { + logs: { + metrics_collected: { + application_signals: { + enabled: true, + }, }, }, - }, - traces: { - traces_collected: { - application_signals: { - enabled: true, + traces: { + traces_collected: { + application_signals: { + enabled: true, + }, }, }, - }, -}; + }; -/** - * Class for integrating Application Signals into an ECS task definition. - */ -export class ApplicationSignalsIntegration extends Construct { private sdkInject?: SDKInject; private mountVolumeName: string = 'opentelemetry-auto-instrumentation'; @@ -648,7 +646,7 @@ export class ApplicationSignalsIntegration extends Construct { }]; let runtimePlatformObj = props.instrumentation.runtimePlatform ?? (props.taskDefinition as any).runtimePlatform; - let cloudWatchAgentImage = CLOUDWATCH_AGENT_IMAGE; + let cloudWatchAgentImage = ApplicationSignalsIntegration.CLOUDWATCH_AGENT_IMAGE; let cpuArch = ecs.CpuArchitecture.X86_64; let isWindows = false; @@ -660,11 +658,11 @@ export class ApplicationSignalsIntegration extends Construct { switch (runtimePlatform.operatingSystemFamily) { case ecs.OperatingSystemFamily.WINDOWS_SERVER_2019_CORE: case ecs.OperatingSystemFamily.WINDOWS_SERVER_2019_FULL: - cloudWatchAgentImage = CLOUDWATCH_AGENT_IMAGE_WIN2019; + cloudWatchAgentImage = ApplicationSignalsIntegration.CLOUDWATCH_AGENT_IMAGE_WIN2019; break; case ecs.OperatingSystemFamily.WINDOWS_SERVER_2022_CORE: case ecs.OperatingSystemFamily.WINDOWS_SERVER_2022_FULL: - cloudWatchAgentImage = CLOUDWATCH_AGENT_IMAGE_WIN2022; + cloudWatchAgentImage = ApplicationSignalsIntegration.CLOUDWATCH_AGENT_IMAGE_WIN2022; break; } @@ -761,7 +759,7 @@ export class ApplicationSignalsIntegration extends Construct { return { image: ecs.ContainerImage.fromRegistry(image), environment: { - CW_CONFIG_CONTENT: JSON.stringify(CW_CONFIG_CONTENT), + CW_CONFIG_CONTENT: JSON.stringify(ApplicationSignalsIntegration.CW_CONFIG_CONTENT), }, logging: new ecs.AwsLogDriver({ streamPrefix: name }), user: '0:1338',