EC2 Dev 서버 운영기 — PM2에서 시작해서 ECS Production까지
배경
Node.js 마이크로서비스 4개를 운영하고 있다. API Gateway 1개와 백엔드 서비스 3개. Production은 AWS ECS Fargate로 돌리고, Dev 서버는 EC2 한 대에 PM2로 올려놓고 쓰고 있다.
어느 날 Dev 서버 SSH 접속이 느려졌다. 들어가보니 PM2 로그가 수 GB, Docker dangling 이미지가 디스크를 채우고 있었다. 그때부터 정리한 운영 스크립트와, EC2와 ECS를 동시에 운영하면서 느낀 점을 정리한다.
Dev 서버 (EC2) 구조
EC2 (Ubuntu, t3.medium)
├── PM2 (Fork mode)
│ ├── gateway 512MB
│ ├── service-1 512MB
│ ├── service-2 512MB
│ ├── service-3-a 1GB
│ ├── service-3-b 1GB
│ ├── admin-dashboard 256MB
│ └── nginx (sticky session)
│
├── Docker
│ ├── PostgreSQL 15 (port 5432)
│ └── Redis 7.4 (port 6379)
│
└── Nginx (ip_hash로 SSE sticky session)
PM2를 fork 모드로 쓰는 이유가 있다. SSE(Server-Sent Events) 연결이 cluster 모드에서 끊기기 때문이다. service-3은 2개 인스턴스로 띄우고, Nginx ip_hash로 sticky session을 건다.
Production (ECS Fargate) 구조
ALB (단일, 멀티 포트)
├── :443 → Gateway (Blue/Green, 2~10 tasks)
├── :444 → Service-3 (Blue/Green, Sticky, 1~6 tasks)
├── :446 → Service-2 (Blue/Green, Sticky, 1~6 tasks)
├── :448 → Service-1 (Rolling, Sticky, 1~6 tasks)
└── :3333 → Admin Dashboard (Rolling, 1 task)
Service Discovery (Cloud Map)
├── gateway.internal:4001
├── service-1.internal:4001
├── service-2.internal:4002
└── service-3.internal:4003
각 서비스가 독립적인 Fargate Task로 돌아간다. TCP 마이크로서비스 간 통신은 AWS Cloud Map으로 서비스 디스커버리를 하고, ALB에서 SSE가 필요한 서비스에는 sticky session 쿠키를 붙인다.
배포 전략도 서비스마다 다르다
| 서비스 | 배포 방식 | 이유 |
|---|---|---|
| Gateway | CodeDeploy Blue/Green | 진입점이라 무중단 필수 |
| Service-3 | CodeDeploy Blue/Green | SSE 연결 graceful draining 120초 |
| Service-1 | ECS Rolling | 상태 없음, 빠른 배포 우선 |
| Service-2 | ECS Rolling | 상태 없음 |
| Admin Dashboard | ECS Rolling | 내부 도구, 중단 허용 |
두 환경의 현실적인 차이
EC2가 Dev에서 좋은 이유
# 코드 수정 → 반영: 30초
git pull && pnpm build && pm2 restart all
# 디버깅: SSH 한 방
ssh dev-server
pm2 logs service-1 --lines 100
docker exec -it postgres psql
ECS에서 같은 걸 하려면:
# 코드 수정 → 반영: 3~5분
# Docker 빌드 → ECR 푸시 → Task 재배포
# 디버깅: 의식의 흐름이 필요
aws ecs execute-command \
--cluster my-cluster \
--task arn:aws:ecs:... \
--container my-service \
--interactive --command "/bin/sh"
# + Session Manager Plugin, IAM 권한, ECS Exec 활성화 필요
.env 하나 바꾸는 것도 EC2는 vim으로 수정 후 재시작이면 끝이지만, ECS는 Secrets Manager 수정 → 태스크 재배포다.
단, ALB가 붙으면 EC2 장점이 사라진다
EC2의 가장 큰 장점은 Stop하면 과금이 멈추는 것이다. 그런데 ALB가 붙어있으면:
- ALB 고정비: ~$16/월
- NAT Gateway: ~$32/월 + 트래픽
- Stop해도 $48/월은 계속 나간다
이 정도면 ECS Fargate로 desired_count: 0으로 내려놓는 것과 비용 차이가 없다. ALB가 필요한 Dev 서버라면 차라리 ECS로 통일하는 게 인프라 관리 포인트가 줄어든다.
EC2 Dev 서버가 의미있으려면 ALB 없이 IP 직접 접근으로 써야 한다.
EC2 Dev 서버 운영 스크립트
ALB 없이 EC2를 Dev 서버로 쓰고 있다면, 이 자동화 정도는 깔아둬야 한다.
Cron 자동 정리
crontab -e
# [매시] Docker 미사용 리소스 + systemd 로그 정리
0 * * * * docker system prune -f && sudo journalctl --vacuum-size=500M && sudo find /var/log -name "*.gz" -delete && sudo find /tmp -atime +1 -delete 2>/dev/null
# [매일 03시] PM2 로그 비우기 — GB 단위로 쌓인다
0 3 * * * pm2 flush && find ~/.pm2/logs -name "*.log" -size +500M -exec truncate -s 0 {} \;
# [매일 04시] 72시간 지난 Docker 이미지 삭제
0 4 * * * docker image prune -a -f --filter "until=72h" && docker volume prune -f
# [매일 05시] 7일 넘은 로그 파일 삭제
0 5 * * * sudo find /var/log -name "*.log.*" -mtime +7 -delete 2>/dev/null
# [매주 일요일] apt 캐시 정리
0 4 * * 0 sudo apt clean && sudo apt autoremove -y -q
30초 진단
서버가 느릴 때 이것부터:
uptime && free -h && df -h /
세 가지를 동시에 본다:
- load average — 코어 수 이상이면 CPU 과부하
- available — 전체 메모리의 10% 이하면 위험
- Use% — 디스크 90% 이상이면 긴급
더 파고 싶으면:
# CPU/메모리 Top 5
ps aux --sort=-%cpu | head -5
ps aux --sort=-%mem | head -5
# CPU steal — 높으면 t3 같은 버스터블 인스턴스 한계
top -bn1 | grep "%Cpu" | awk '{print "steal:", $16"%"}'
# OOM Killer 발생 여부
sudo dmesg | grep -i "oom\|killed" | tail -5
Docker 인프라 모니터링
# 컨테이너 리소스
docker stats --no-stream
# PostgreSQL idle 연결 정리 (10분 이상)
docker exec $(docker ps -qf "name=postgres") psql -U postgres -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND query_start < now() - interval '10 minutes';"
# Redis BullMQ 잔여 데이터 정리
docker exec $(docker ps -qf "name=redis") \
redis-cli --scan --pattern "bull:*:completed" | \
xargs -I{} docker exec $(docker ps -qf "name=redis") redis-cli DEL {}
긴급 대응
# 디스크 100%: 범인 찾고 즉시 확보
sudo du -sh /var/log /tmp ~/.pm2/logs /var/lib/docker 2>/dev/null | sort -rh
pm2 flush && docker system prune -a -f --volumes && sudo journalctl --vacuum-size=100M
# 메모리 부족: 캐시 해제 + PM2 재시작
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null
pm2 restart all
ECS Production에서는 이게 다 필요 없다
위 스크립트들이 EC2에서 필요한 이유는 서버가 stateful하기 때문이다. ECS Fargate는:
| EC2에서 수동으로 하는 것 | ECS에서 자동으로 되는 것 |
|---|---|
| PM2 로그 정리 Cron | CloudWatch Logs (30일 retention 자동) |
| Docker prune Cron | 컨테이너 재생성 시 자동 정리 |
| PM2 헬스체크 | ECS 헬스체크 + 자동 재시작 |
ps aux 수동 모니터링 |
CloudWatch 메트릭 + 알림 |
| fail2ban SSH 방어 | SSH 자체가 없음 |
| Swap 설정 | Fargate가 메모리 관리 |
| 디스크 용량 관리 | Ephemeral 스토리지, 상태 없음 |
ECS에서 대신 필요한 것
# 서비스 상태 확인
aws ecs describe-services --cluster my-cluster \
--services gateway --query 'services[0].{running:runningCount,desired:desiredCount,deployments:deployments[*].status}'
# 실시간 로그
aws logs tail /ecs/my-service --follow
# 스케일링
aws ecs update-service --cluster my-cluster --service gateway --desired-count 4
# 롤백
aws ecs update-service --cluster my-cluster --service gateway \
--task-definition my-service:$(( $(aws ecs describe-services --cluster my-cluster --services gateway --query 'services[0].taskDefinition' --output text | grep -o '[0-9]*$') - 1 ))
개선 로드맵
현재 구조에서 아직 남은 과제들:
1. Dev 환경도 ECS로 통합
EC2 Dev 서버에 ALB가 붙어있다면, 비용 이점이 없다. Terraform 모듈을 환경별로 분리하면 된다:
terraform/environments/
├── prod/ # 현재 구성
├── staging/ # desired_count: 1, 작은 리소스
└── dev/ # desired_count: 1, FARGATE_SPOT 100%
2. Auto Scaling 정책 추가
현재 ECS에 min/max만 정의되어 있고 실제 스케일링 정책이 없다. Target Tracking 추가가 필요하다:
resource "aws_appautoscaling_target" "gateway" {
max_capacity = 10
min_capacity = 2
resource_id = "service/my-cluster/gateway"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "gateway_cpu" {
name = "gateway-cpu-scaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.gateway.resource_id
scalable_dimension = aws_appautoscaling_target.gateway.scalable_dimension
service_namespace = aws_appautoscaling_target.gateway.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 70.0
}
}
3. FARGATE_SPOT 활용
상태 없는 서비스(Service-1, Service-2)는 FARGATE_SPOT으로 전환하면 최대 70% 비용 절감이 가능하다. 중단 시 ECS가 자동으로 다른 인스턴스를 띄운다.
4. Docker 로그 제한 (EC2 운영 시)
정리보다 애초에 안 쌓이게 하는 게 낫다:
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
AI 활용 포인트
이 글의 시작은 "서버가 느린데 어떡하지"였다. Claude Code에게 물어보니 진단 → cron 자동화 → 문서화까지 한 세션에서 끝났다.
특히 도움된 부분:
- 코드베이스의
ecosystem.config.js,docker-compose.yml, Terraform 설정을 자동으로 분석해서 프로젝트 스택에 맞는 스크립트 생성 - cron 표현식,
docker exec파이프라인처럼 문법 실수하기 쉬운 명령어를 바로 사용 가능한 형태로 제공 - EC2 vs ECS 비교를 현재 인프라 비용 기준으로 구체적으로 계산
Top comments (0)