ECS ON EC2에 데이터독 셋업 (4 Pillar)

Datadog 설정 가이드

ECS EC2 Launch Type + Node.js (NestJS)


개요

이 가이드는 두 단계로 구성됩니다.

  1. Datadog Agent 설치 — ECS 클러스터에 DAEMON 서비스로 배포
  2. 애플리케이션 계측 — Node.js 앱에 dd-trace 추가 및 Task Definition 수정

완료 후 얻을 수 있는 것:


사전 조건


Step 1. Datadog Agent 배포

install/datadog-agent.yml 파일을 CloudFormation으로 배포합니다.

1-1. 전체 템플릿

Resources:
  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: datadog-agent
      NetworkMode: bridge
      RequiresCompatibilities:
        - EC2
      Cpu: '256'
      Memory: '512'
      ContainerDefinitions:
        - Name: datadog-agent
          Image: public.ecr.aws/datadog/agent:latest
          Memory: 512
          Cpu: 100
          Essential: true
          PortMappings:
            - ContainerPort: 8126
              HostPort: 8126
              Protocol: tcp
          Environment:
            - Name: DD_API_KEY
              Value: YOUR_DATADOG_API_KEY        # ← 교체 필수
            - Name: DD_SITE
              Value: datadoghq.com               # ← 사용 중인 Datadog 사이트로 교체
            - Name: DD_APM_ENABLED
              Value: 'true'
            - Name: DD_APM_NON_LOCAL_TRAFFIC
              Value: 'true'
            - Name: DD_LOGS_ENABLED
              Value: 'true'
            - Name: DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL
              Value: 'true'
            - Name: DD_LOGS_CONFIG_AUTO_MULTI_LINE_DETECTION
              Value: 'true'
          MountPoints:
            - ContainerPath: /var/run/docker.sock
              SourceVolume: docker_sock
            - ContainerPath: /host/sys/fs/cgroup
              SourceVolume: cgroup
            - ContainerPath: /host/proc
              SourceVolume: proc
            - ContainerPath: /opt/datadog-agent/run
              SourceVolume: pointdir
            - ContainerPath: /var/lib/docker/containers
              SourceVolume: containers_root
      Volumes:
        - Name: docker_sock
          Host:
            SourcePath: /var/run/docker.sock
        - Name: proc
          Host:
            SourcePath: /proc/
        - Name: cgroup
          Host:
            SourcePath: /sys/fs/cgroup/
        - Name: pointdir
          Host:
            SourcePath: /opt/datadog-agent/run
        - Name: containers_root
          Host:
            SourcePath: /var/lib/docker/containers/

  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: YOUR_ECS_CLUSTER_NAME             # ← 교체 필수
      TaskDefinition: !Ref ECSTaskDefinition
      SchedulingStrategy: DAEMON
      LaunchType: EC2

1-2. 수정해야 할 항목

항목 위치 설명
DD_API_KEY Environment Datadog 콘솔 → Organization Settings → API Keys
DD_SITE Environment 아래 표 참고
Cluster ECSService.Properties ECS 클러스터 이름

DD_SITE 값 (가입한 Datadog 사이트에 따라 결정)

사이트 DD_SITE
US1 (기본, app.datadoghq.com) datadoghq.com
US3 (us3.datadoghq.com) us3.datadoghq.com
EU (app.datadoghq.eu) datadoghq.eu
AP1 (ap1.datadoghq.com) ap1.datadoghq.com

1-3. 주요 설정 설명

Task Definition 레벨

설정 설명
NetworkMode bridge DAEMON 서비스는 bridge 모드 필수. bridge 모드에서만 HostPort를 고정 바인딩할 수 있어, 같은 호스트의 컨테이너들이 에이전트에 접근 가능
RequiresCompatibilities EC2 EC2 launch type 전용
SchedulingStrategy DAEMON EC2 호스트 1대당 에이전트 1개를 자동으로 배포. 인스턴스가 추가되면 에이전트도 자동 추가

PortMappings

설정 설명
ContainerPort 8126 에이전트가 트레이스를 수신하는 포트
HostPort 8126 호스트(EC2)에 고정 바인딩. 앱 컨테이너가 호스트IP:8126으로 트레이스를 전송

Environment

변수 설명
DD_API_KEY Datadog 인증 키
DD_SITE 트레이스/로그를 전송할 Datadog 사이트 엔드포인트
DD_APM_ENABLED APM 트레이스 수집 활성화
DD_APM_NON_LOCAL_TRAFFIC awsvpc 모드 앱 컨테이너는 호스트와 별도 ENI를 가지므로, 에이전트가 0.0.0.0:8126을 리스닝하도록 설정. 이 옵션이 없으면 앱 컨테이너에서 보낸 트레이스가 거부됨
DD_LOGS_ENABLED 로그 수집 활성화
DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL 클러스터 내 모든 컨테이너의 로그를 자동으로 Datadog에 전송. CloudWatch 없이도 로그 수집 가능
DD_LOGS_CONFIG_AUTO_MULTI_LINE_DETECTION 스택 트레이스처럼 여러 줄에 걸친 로그를 하나의 로그 이벤트로 자동 합산

MountPoints / Volumes

마운트 경로 용도
/var/run/docker.sock Docker 데몬과 통신. 실행 중인 컨테이너 목록 조회, 메타데이터 수집
/var/lib/docker/containers 컨테이너 로그 파일(*-json.log) 직접 읽기. 로그를 Datadog으로 전송하는 핵심 마운트
/host/proc 호스트 프로세스 정보 수집 (CPU, 메모리, 네트워크 메트릭)
/host/sys/fs/cgroup 컨테이너별 리소스 사용량(CPU, 메모리) 수집
/opt/datadog-agent/run 에이전트 재시작 시 로그 수집 위치(offset) 유지. 중복 수집 방지

1-4. CloudFormation 배포

aws cloudformation deploy \
  --template-file install/datadog-agent.yml \
  --stack-name datadog-agent \
  --region ap-northeast-2

배포 후 ECS 콘솔에서 datadog-agent 서비스가 DAEMON 전략으로 각 EC2 인스턴스에 1개씩 실행 중인지 확인합니다.


Step 2. Security Group 설정

앱 컨테이너(awsvpc)에서 EC2 호스트의 Datadog Agent(포트 8126)로 트래픽이 흐를 수 있도록 인바운드 룰을 추가합니다.

앱 컨테이너 SG  →  8126/tcp  →  EC2 인스턴스 SG
aws ec2 authorize-security-group-ingress \
  --group-id <EC2_INSTANCES_SG_ID> \
  --protocol tcp \
  --port 8126 \
  --source-group <APP_TASKS_SG_ID> \
  --region ap-northeast-2

Security Group ID는 ECS 콘솔 → 클러스터 → 서비스 → 네트워킹 탭에서 확인할 수 있습니다.


Step 3. 애플리케이션 계측

3-1. 패키지 설치

npm install dd-trace nestjs-pino pino-http

3-2. tracer.ts 생성

프로젝트 src/ 아래에 파일을 생성합니다.

// src/tracer.ts
import tracer from 'dd-trace';

tracer.init({
  service: process.env.DD_SERVICE ?? 'my-service',
  env: process.env.DD_ENV ?? 'production',
  version: process.env.DD_VERSION ?? '1.0.0',
  logInjection: true,  // 로그에 trace_id 자동 삽입
});

export default tracer;

3-3. main.ts 수정 — tracer를 가장 먼저 임포트

// src/main.ts
import './tracer';         // ← 반드시 첫 번째 줄
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from 'nestjs-pino';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  app.useLogger(app.get(Logger));
  await app.listen(8000);
}
bootstrap();

dd-trace는 다른 모듈보다 먼저 로드해야 HTTP 요청 등의 자동 계측이 동작합니다.

3-4. app.module.ts — nestjs-pino 등록

// src/app.module.ts
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: {
        autoLogging: false,
        formatters: {
          level: (label) => ({ level: label }),
        },
      },
    }),
  ],
})
export class AppModule {}

3-5. 컨트롤러 — 로거 주입

import { Controller, Get } from '@nestjs/common';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';

@Controller('products')
export class ProductsController {
  constructor(
    @InjectPinoLogger(ProductsController.name)
    private readonly logger: PinoLogger,
  ) {}

  @Get()
  findAll() {
    this.logger.info('상품 목록 조회');
    return [];
  }
}

@InjectPinoLogger()로 받은 PinoLogger는 현재 HTTP 요청 컨텍스트를 가지므로, 로그에 trace_idspan_id가 자동으로 포함됩니다.


Step 4. Task Definition 수정

앱 Task Definition에 다음 두 가지를 추가합니다.

추가할 내용

① entryPoint — DD_AGENT_HOST를 런타임에 동적으로 설정

awsvpc 모드 태스크는 EC2 호스트 IP를 미리 알 수 없으므로, 시작 시점에 EC2 메타데이터에서 조회합니다.

"entryPoint": [
  "sh", "-c",
  "export DD_AGENT_HOST=$(wget -qO- http://169.254.169.254/latest/meta-data/local-ipv4) && node dist/main"
]

node:alpine 이미지에는 curl이 없습니다. wget -qO-를 사용합니다.

② environment — Datadog 서비스 식별 정보

"environment": [
  { "name": "DD_SERVICE",          "value": "my-service-name" },
  { "name": "DD_ENV",              "value": "production" },
  { "name": "DD_VERSION",          "value": "1.0.0" },
  { "name": "DD_TRACE_AGENT_PORT", "value": "8126" }
]

수정된 Task Definition 예시 (JSON)

{
  "family": "my-shop-api",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["EC2"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "shop-api",
      "image": "ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/shop-api:latest",
      "essential": true,
      "portMappings": [
        { "containerPort": 8000, "protocol": "tcp" }
      ],
      "entryPoint": [
        "sh", "-c",
        "export DD_AGENT_HOST=$(wget -qO- http://169.254.169.254/latest/meta-data/local-ipv4) && node dist/main"
      ],
      "environment": [
        { "name": "DD_SERVICE",          "value": "my-shop-api" },
        { "name": "DD_ENV",              "value": "production" },
        { "name": "DD_VERSION",          "value": "1.0.0" },
        { "name": "DD_TRACE_AGENT_PORT", "value": "8126" }
      ]
    }
  ]
}

logConfiguration(CloudWatch)은 선택 사항입니다. Step 1에서 설정한 DD Agent가 컨테이너 로그를 Datadog으로 직접 수집하므로, CloudWatch 없이도 로그가 수집됩니다.


Step 5. 배포 및 검증

배포

# Task Definition 등록
aws ecs register-task-definition \
  --cli-input-json file://task-definition.json \
  --region ap-northeast-2

# 서비스 재배포
aws ecs update-service \
  --cluster YOUR_CLUSTER \
  --service YOUR_SERVICE \
  --task-definition my-shop-api \
  --force-new-deployment \
  --region ap-northeast-2

검증

로그에 trace_id 포함 여부 확인 (CloudWatch 사용 시)

aws logs tail /ecs/my-shop-api --region ap-northeast-2 --since 5m

정상이면 로그 항목에 dd 필드가 포함됩니다:

{
  "level": "info",
  "msg": "상품 목록 조회",
  "dd": {
    "trace_id": "3a747f6edba68cc4",
    "span_id":  "8602362596511696430",
    "service":  "my-shop-api",
    "env":      "production"
  }
}

Datadog 콘솔 확인

메뉴 확인 항목
APM → Services 서비스명이 목록에 표시되는지
APM → Traces 트레이스가 수신되는지
Logs → Search @dd.trace_id:* 필터로 연동 로그 확인

트러블슈팅

APM 서비스가 보이지 않을 때

로그에 dd.trace_id가 없을 때

컨트롤러에서 로거를 @InjectPinoLogger()로 DI 받지 않은 경우입니다.

// ❌ trace_id 없음
private readonly logger = new Logger(MyController.name);

// ✅ trace_id 자동 포함
constructor(
  @InjectPinoLogger(MyController.name)
  private readonly logger: PinoLogger,
) {}