ECS ON EC2에 데이터독 셋업 (4 Pillar)
Datadog 설정 가이드
ECS EC2 Launch Type + Node.js (NestJS)
개요
이 가이드는 두 단계로 구성됩니다.
- Datadog Agent 설치 — ECS 클러스터에 DAEMON 서비스로 배포
- 애플리케이션 계측 — Node.js 앱에
dd-trace추가 및 Task Definition 수정
완료 후 얻을 수 있는 것:
- Datadog APM에서 서비스별 트레이스 확인
- 로그에
trace_id자동 삽입 → 로그와 트레이스 연동 (Log-Trace Correlation) - 컨테이너 로그가 Datadog으로 직접 전송
사전 조건
- ECS 클러스터가 EC2 launch type으로 구성되어 있을 것
- Datadog API Key 준비
- AWS CLI 설치 및 인증 완료
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,
) {}