

# 使用 AWS CDK 在 Amazon ECS 上启用 Application Signals
<a name="CloudWatch-Application-Signals-EKS-CDK"></a>

要使用 AWS CDK 在 Amazon ECS 上启用 Application Signals，请执行以下操作。

1. 为应用程序启用 Application Signals – 如果您尚未在此账户中启用 Application Signals，则必须向 Application Signals 授予发现您的服务所需的权限。

   ```
   import { aws_applicationsignals as applicationsignals } from 'aws-cdk-lib';
   
   const cfnDiscovery = new applicationsignals.CfnDiscovery(this,
     'ApplicationSignalsServiceRole', { }
   );
   ```

   Discovery CloudFormation 资源授予 Application Signals 下列权限：
   + `xray:GetServiceGraph`
   + `logs:StartQuery`
   + `logs:GetQueryResults`
   + `cloudwatch:GetMetricData`
   + `cloudwatch:ListMetrics`
   + `tag:GetResources`

   有关该角色的更多信息，请参阅[CloudWatch Application Signals 的服务相关角色权限](using-service-linked-roles.md#service-linked-role-signals)。

1. 使用 AWS CDK 中的 [AWS::ApplicationSignals 构造库](https://www.npmjs.com/package/@aws-cdk/aws-applicationsignals-alpha)来检测您的应用程序。本文档中的代码片段为 *TypeScript* 格式。有关其他特定语言的备选方案，请参阅 [AWS CDK 支持的编程语言](https://docs.aws.amazon.com/cdk/v2/guide/languages.html)。
   + **使用附加模式在 Amazon ECS 上启用 Application Signals**

     1. 配置 `instrumentation`，以使用 AWS Distro for OpenTelemetry（ADOT）SDK Agent 检测应用程序。以下是检测 Java 应用程序的示例。请参阅 [InstrumentationVersion](https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_aws-applicationsignals-alpha.InstrumentationVersion.html)，了解所有可支持的语言版本。

     1. 指定 `cloudWatchAgentSidecar` 以将 CloudWatch 代理配置为附加容器。

        ```
        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';
        
        class MyStack extends cdk.Stack {
          public constructor(scope?: Construct, id?: string, props: cdk.StackProps = {}) {
            super();
            const vpc = new ec2.Vpc(this, 'TestVpc', {});
            const cluster = new ecs.Cluster(this, 'TestCluster', { vpc });
        
            const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'SampleAppTaskDefinition', {
              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.V2_10_0,
              },
              serviceName: 'sample-app',
              cloudWatchAgentSidecar: {
                containerName: 'ecs-cwagent',
                enableLogging: true,
                cpu: 256,
                memoryLimitMiB: 512,
              }
            });
        
            new ecs.FargateService(this, 'MySampleApp', {
              cluster: cluster,
              taskDefinition: fargateTaskDefinition,
              desiredCount: 1,
            });
          }
        }
        ```
   + **使用进程守护程序模式在 Amazon ECS 上启用 Application Signals**
**注意**  
进程守护程序部署策略在 Amazon ECS Fargate 上不受支持，仅在 Amazon EC2 的 Amazon ECS 上受支持。

     1. 以 `HOST` 网络模式将 CloudWatch 代理作为守护进程服务运行。

     1. 配置 `instrumentation`，以使用 ADOT Python 代理检测应用程序。

        ```
        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';
        
        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 });
        
            // Define Task Definition for CloudWatch agent (Daemon)
            const cwAgentTaskDefinition = new ecs.Ec2TaskDefinition(this, 'CloudWatchAgentTaskDefinition', {
              networkMode: ecs.NetworkMode.HOST,
            });
        
            new appsignals.CloudWatchAgentIntegration(this, 'CloudWatchAgentIntegration', {
              taskDefinition: cwAgentTaskDefinition,
              containerName: 'ecs-cwagent',
              enableLogging: false,
              cpu: 128,
              memoryLimitMiB: 64,
              portMappings: [
                {
                  containerPort: 4316,
                  hostPort: 4316,
                },
                {
                  containerPort: 2000,
                  hostPort: 2000,
                },
              ],
            });
        
            // Create the CloudWatch Agent daemon service
            new ecs.Ec2Service(this, 'CloudWatchAgentDaemon', {
              cluster,
              taskDefinition: cwAgentTaskDefinition,
              daemon: true,  // Runs one container per EC2 instance
            });
        
            // Define Task Definition for user application
            const sampleAppTaskDefinition = new ecs.Ec2TaskDefinition(this, 'SampleAppTaskDefinition', {
              networkMode: ecs.NetworkMode.HOST,
            });
        
            sampleAppTaskDefinition.addContainer('app', {
              image: ecs.ContainerImage.fromRegistry('test/sample-app'),
              cpu: 0,
              memoryLimitMiB: 512,
            });
        
            // No CloudWatch Agent sidecar is needed as application container communicates to CloudWatch Agent daemon through host network
            new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', {
              taskDefinition: sampleAppTaskDefinition,
              instrumentation: {
                sdkVersion: appsignals.PythonInstrumentationVersion.V0_8_0
              },
              serviceName: 'sample-app'
            });
        
            new ecs.Ec2Service(this, 'MySampleApp', {
              cluster,
              taskDefinition: sampleAppTaskDefinition,
              desiredCount: 1,
            });
          }
        }
        ```
   + **使用副本模式在 Amazon ECS 上启用 Application Signals**
**注意**  
使用副本模式运行 CloudWatch 代理服务需要特定的安全组配置，才能与其他服务进行通信。要使用 Application Signals 功能，请使用最低入站规则配置安全组：端口 2000（HTTP）和端口 4316（HTTP）。此配置可确保 CloudWatch 代理与相关服务之间的连接正确。

     1. 使用服务连接将 CloudWatch 代理作为副本服务运行。

     1. 配置 `instrumentation`，以使用 ADOT Python 代理检测应用程序。

     1. 配置 `overrideEnvironments` 来使用服务连接端点与 CloudWatch 代理服务器通信，以覆盖环境变量。

        ```
        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 { PrivateDnsNamespace } from 'aws-cdk-lib/aws-servicediscovery';
        
        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 });
            const dnsNamespace = new PrivateDnsNamespace(this, 'Namespace', {
              vpc,
              name: 'local',
            });
            const securityGroup = new ec2.SecurityGroup(this, 'ECSSG', { vpc });
            securityGroup.addIngressRule(securityGroup, ec2.Port.tcpRange(0, 65535));
        
            // Define Task Definition for CloudWatch agent (Replica)
            const cwAgentTaskDefinition = new ecs.FargateTaskDefinition(this, 'CloudWatchAgentTaskDefinition', {});
        
            new appsignals.CloudWatchAgentIntegration(this, 'CloudWatchAgentIntegration', {
              taskDefinition: cwAgentTaskDefinition,
              containerName: 'ecs-cwagent',
              enableLogging: false,
              cpu: 128,
              memoryLimitMiB: 64,
              portMappings: [
                {
                  name: 'cwagent-4316',
                  containerPort: 4316,
                  hostPort: 4316,
                },
                {
                  name: 'cwagent-2000',
                  containerPort: 2000,
                  hostPort: 2000,
                },
              ],
            });
        
            // Create the CloudWatch Agent replica service with service connect
            new ecs.FargateService(this, 'CloudWatchAgentService', {
              cluster: cluster,
              taskDefinition: cwAgentTaskDefinition,
              securityGroups: [securityGroup],
              serviceConnectConfiguration: {
                namespace: dnsNamespace.namespaceArn,
                services: [
                  {
                    portMappingName: 'cwagent-4316',
                    dnsName: 'cwagent-4316-http',
                    port: 4316,
                  },
                  {
                    portMappingName: 'cwagent-2000',
                    dnsName: 'cwagent-2000-http',
                    port: 2000,
                  },
                ],
              },
              desiredCount: 1,
            });
        
            // Define Task Definition for user application
            const sampleAppTaskDefinition = new ecs.FargateTaskDefinition(this, 'SampleAppTaskDefinition', {});
        
            sampleAppTaskDefinition.addContainer('app', {
              image: ecs.ContainerImage.fromRegistry('test/sample-app'),
              cpu: 0,
              memoryLimitMiB: 512,
            });
        
            // Overwrite environment variables to connect to the CloudWatch Agent service just created
            new appsignals.ApplicationSignalsIntegration(this, 'ApplicationSignalsIntegration', {
              taskDefinition: sampleAppTaskDefinition,
              instrumentation: {
                sdkVersion: appsignals.PythonInstrumentationVersion.V0_8_0,
              },
              serviceName: 'sample-app',
              overrideEnvironments: [
                {
                  name: appsignals.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT,
                  value: 'http://cwagent-4316-http:4316/v1/metrics',
                },
                {
                  name: appsignals.TraceExporting.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
                  value: 'http://cwagent-4316-http:4316/v1/traces',
                },
                {
                  name: appsignals.TraceExporting.OTEL_TRACES_SAMPLER_ARG,
                  value: 'endpoint=http://cwagent-2000-http:2000',
                },
              ],
            });
        
            // Create ECS Service with service connect configuration
            new ecs.FargateService(this, 'MySampleApp', {
              cluster: cluster,
              taskDefinition: sampleAppTaskDefinition,
              serviceConnectConfiguration: {
                namespace: dnsNamespace.namespaceArn,
              },
              desiredCount: 1,
            });
          }
        }
        ```

1. 使用 ESM 模块格式设置 Node.js 应用程序。我们对采用 ESM 模块格式的 Node.js 应用程序提供有限的支持。有关更多信息，请参阅[使用 ESM 的 Node.js 的已知限制](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-supportmatrix.html#ESM-limitations)。

   对于 ESM 模块格式，通过使用 `init` 容器注入 Node.js 检测 SDK 启用 Application Signals 不适用。跳过此程序的第 2 步，改为执行以下操作。
   + 将相关依赖项安装到您的 Node.js 应用程序中以进行自动检测。

     ```
     npm install @aws/aws-distro-opentelemetry-node-autoinstrumentation
     npm install @opentelemetry/instrumentation@0.54.
     ```
   +  更新任务定义。

     1. 将其他配置添加到您的应用程序容器。

     1. Configure `NODE_OPTIONS`。

     1. （可选）如果选择附加模式，则添加 CloudWatch 代理。

        ```
        import { Construct } from 'constructs';
        import * as appsignals from '@aws-cdk/aws-applicationsignals-alpha';
        import * as ecs from 'aws-cdk-lib/aws-ecs';
        
        class MyStack extends cdk.Stack {
          public constructor(scope?: Construct, id?: string, props: cdk.StackProps = {}) {
            super(scope, id, props);
            const fargateTaskDefinition = new ecs.FargateTaskDefinition(stack, 'TestTaskDefinition', {
              cpu: 256,
              memoryLimitMiB: 512,
            });
            const appContainer = fargateTaskDefinition.addContainer('app', {
              image: ecs.ContainerImage.fromRegistry('docker/cdk-test'),
            });
        
            const volumeName = 'opentelemetry-auto-instrumentation'
            fargateTaskDefinition.addVolume({name: volumeName});
        
            // Inject additional configurations
            const injector = new appsignals.NodeInjector(volumeName, appsignals.NodeInstrumentationVersion.V0_5_0);
            injector.renderDefaultContainer(fargateTaskDefinition);
            // Configure NODE_OPTIONS
            appContainer.addEnvironment('NODE_OPTIONS', '--import @aws/aws-distro-opentelemetry-node-autoinstrumentation/register --experimental-loader=@opentelemetry/instrumentation/hook.mjs')
        
            // Optional: add CloudWatch agent
            const cwAgent = new appsignals.CloudWatchAgentIntegration(stack, 'AddCloudWatchAgent', {
              containerName: 'ecs-cwagent',
              taskDefinition: fargateTaskDefinition,
              memoryReservationMiB: 50,
            });
            appContainer.addContainerDependencies({
              container: cwAgent.agentContainer,
              condition: ecs.ContainerDependencyCondition.START,
            });
        }
        ```

1. 部署更新后的堆栈 – 在应用程序的主目录中运行 `cdk synth` 命令。要在您的 AWS 账户中部署服务，请在应用程序的主目录中运行 `cdk deploy` 命令。

   如果您使用的是挎斗策略，则会看到创建了一个服务：
   + *APPLICATION\$1SERVICE *是您应用程序的服务。该服务包含以下三个容器：
     + `init` – Application Signals 初始化所必需的容器。
     + `ecs-cwagent` – 运行 CloudWatch 代理的容器
     + `my-app` – 这是我们文档中的示例应用程序容器。在实际工作负载中，此特定的容器可能不存在，或者可能被您自己的服务容器所取代。

   如果您使用的是进程守护程序策略，则会看到创建了两个服务：
   + *CloudWatchAgentDaemon* 是 CloudWatch 代理进程守护程序服务。
   + *APPLICATION\$1SERVICE *是您应用程序的服务。该服务包含以下两个容器：
     + `init` – Application Signals 初始化所必需的容器。
     + `my-app` – 这是我们文档中的示例应用程序容器。在实际工作负载中，此特定的容器可能不存在，或者可能被您自己的服务容器所取代。

   如果您使用的是进程守护程序策略，则会看到创建了两个服务：
   + *CloudWatchAgentService* 是 CloudWatch 代理进程守护程序服务。
   + *APPLICATION\$1SERVICE *是您应用程序的服务。该服务包含以下两个容器：
     + `init` – Application Signals 初始化所必需的容器。
     + `my-app` – 这是我们文档中的示例应用程序容器。在实际工作负载中，此特定的容器可能不存在，或者可能被您自己的服务容器所取代。