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_id와 span_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,
) {}