

# 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 Construct Library](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 에이전트로 애플리케이션을 계측합니다. 다음은 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. CloudWatch Agent를 `HOST` 네트워크 모드의 대몬 서비스로 실행합니다.

     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 Agent 서비스를 실행하려면 다른 서비스와 통신할 수 있는 특정 보안 그룹 구성이 필요합니다. Application Signals 기능의 경우 최소 인바운드 규칙인 포트 2000(HTTP) 및 포트 4316(HTTP)으로 보안 그룹을 구성합니다. 이 구성에서는 CloudWatch 에이전트와 종속 서비스 간의 적절한 연결이 확보됩니다.

     1. 서비스 연결을 통해 CloudWatch Agent를 복제본 서비스로 실행합니다.

     1. `instrumentation`를 구성하여 ADOT Python 에이전트로 애플리케이션을 계측합니다.

     1. Service Connect 연결 엔드포인트를 사용하여 CloudWatch 에이전트 서버와 통신하도록 `overrideEnvironments`를 구성하여 환경 변수를 재정의합니다.

        ```
        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` 컨테이너를 사용한 Application Signals 활성화를 통한 Node.js 계측 SDK 주입에는 해당되지 않습니다. 이 절차의 2단계를 건너뛰고 다음을 대신 수행합니다.
   + 관련 종속성을 자동 계측용 Node.js 애플리케이션에 설치합니다.

     ```
     npm install @aws/aws-distro-opentelemetry-node-autoinstrumentation
     npm install @opentelemetry/instrumentation@0.54.
     ```
   +  TaskDefinition을 업데이트합니다.

     1. 애플리케이션 컨테이너에 추가 구성을 추가합니다.

     1. `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`- 설명서의 예제 애플리케이션 컨테이너입니다. 실제 워크로드에서는 이 특정 컨테이너가 존재하지 않거나 자체 서비스 컨테이너로 대체될 수 있습니다.