Ctrl + Shift + ESC

EC2 로그 운영 개선기: CloudWatch Logs, S3 아카이빙, logrotate 본문

Cloud/AWS

EC2 로그 운영 개선기: CloudWatch Logs, S3 아카이빙, logrotate

단축키실행해보세요 2026. 4. 9. 18:44

서론

요즘은 모니터링을 도입할 때 Prometheus를 거의 기본 선택지처럼 고려하는 경우가 많다. 다만 Prometheus는 생각보다 설정과 운영이 복잡한 편이고, AWS CloudWatch 같은 AWS 네이티브 서비스와의 장단점을 함께 비교해보는 과정이 필요하다고 느꼈다.

이 지점에서 금방 분명해진 점이 하나 있었다. 메트릭 수집과 로그 수집은 비슷해 보이지만, 실제 운영 방식은 꽤 다르다는 것이다.

 

정확히 말하면 Prometheus는 메트릭 수집에 강점이 있는 도구이고, 애플리케이션 로그를 다루려면 별도의 로그 수집기와 저장소가 함께 필요하다. 결국 로그까지 직접 운영하려면 Prometheus만으로는 부족했고, Promtail이나 Grafana Loki 같은 컴포넌트를 추가로 구성해 로그를 수집하고 저장하는 구조를 고민해야 했다.

문제는 이 방식이 생각보다 가볍지 않다는 점이었다. 로그 수집을 위한 별도 인프라를 띄워야 하고, 경우에 따라 Amazon EC2 인스턴스를 추가로 운영해야 할 수도 있었다. 여기에 폴링 비용, 저장 비용, 그리고 운영 복잡도까지 함께 고려해야 했다.

 

반면 AWS CloudWatch Logs는 접근 방식이 훨씬 단순했다. EC2 인스턴스에 CloudWatch Agent만 설치하면 로컬 로그 파일을 바로 CloudWatch Logs로 전송할 수 있었고, 별도의 로그 수집 서버를 직접 운영하지 않아도 되었다. 즉, 로그 수집을 위한 추가 인프라를 고민할 필요가 줄어들고, 관리형 서비스 위에서 수집과 조회를 빠르게 시작할 수 있었다. 개인적으로는 이 점이 꽤 크게 다가왔다. 로그 수집 자체보다 서비스를 운영하는 데 더 집중하고 싶었기 때문이다.

 

이러한 이유로 초기 프로젝트에서는 CloudWatch Logs를 중심으로 로그를 수집하고, 이후 필요에 따라 S3 아카이빙과 logrotate를 덧붙이는 방식을 선택했다. 이후 필요성이 생기면서 Prometheus + Grafana 구조를 별도로 구축하게 되었지만(참고: https://ctrl-shit-esc.tistory.com/218), 처음부터 모든 것을 직접 운영하기보다는 단계적으로 확장하는 접근이 더 현실적이라고 판단했다.

 

CloudWatch Logs

애플리케이션은 /home/ubuntu/app/application.log에 로그를 기록하고 있었고, CodeDeploy 로그 역시 인스턴스 내부 파일로만 존재했다. 이 구조는 단순하지만 아래와 같은 문제들이 있었다.

  • 서버에 직접 접속해야 로그를 확인할 수 있다
  • 인스턴스가 여러 대일 경우 로그를 한 번에 보기 어렵다
  • 인스턴스가 교체되거나 장애가 발생하면 로그 추적이 번거롭다
  • 운영 중 빠르게 검색하거나 알람을 설정하기 어렵다

로그는 쌓여가지만, 이 로그는 활용하기 어렵다는 문제가 발생했다.

그래서 가장 먼저 도입한 것이 Amazon CloudWatch Logs 기반의 구조였다.

 

CloudWatch Logs 도입 후 로그 흐름은 다음과 같다.

 

1. Spring Boot 애플리케이션이 /home/ubuntu/app/application.log에 로그를 기록

2. EC2에 설치된 Amazon CloudWatch Agent가 해당 파일을 읽음

3. Agent가 AWS Logs API를 통해 CloudWatch Logs로 전송

 

CloudWatch Agent는 로컬 로그 파일을 읽어 전송하는 수집기 역할을 담당했다. 이는 애플리케이션을 수정하지 않고도 로그 수집 구조를 붙일 수 있다는 점에서, 도입 난이도가 낮았다.

 

이렇게 구성한 이후 더 이상 인스턴스에 직접 SSH로 접속하지 않아도, 중앙에서 로그를 조회하고 검색할 수 있게 되었다.

 

S3 아카이빙

CloudWatch Logs는 실시간 조회와 검색에는 좋지만, 모든 로그를 오랫동안 보관하기에는 비용 부담이 있다. 

그래서 CloudWatch Logs에 들어온 로그를 Lambda를 통해 S3로 아카이빙하는 구조를 선택했다.

 

동작 순서는 다음과 같다.

  1. EC2의 CloudWatch Agent가 로그를 CloudWatch Logs로 보낸다.
  2. CloudWatch Logs 로그 그룹에 Subscription Filter를 설정한다.
  3. 로그 그룹에 새 이벤트가 들어오면 Lambda가 호출된다.
  4. Lambda가 전달받은 로그 이벤트를 gzip 압축해 S3에 저장한다.

여기서 각 구성요소의 역할은 다음과 같다.

  • CloudWatch Logs: 운영 중 조회용
  • Lambda: 로그 이벤트를 받아 중간 처리
  • S3: 장기 보관 저장소

CloudWatch Logs Subscription 이벤트 base64 + gzip 형태로 들어오므로 그대로 S3에 저장하기엔 약간 가공이 필요해 Lambda를 이용했다.

이렇게 바꾸고 나니 S3에 쓸데없는 빈 객체가 생기지 않았고, 로그 발생 날짜 기준으로 아카이브 경로를 더 정확하게 나눌 수 있었다.

 

하지만 EC2 내부에 application.log가 계속 쌓이는 문제가 해결되지 않았다.

애플리케이션은 여전히 로컬 파일에 로그를 먼저 쓰고 있었고, CloudWatch Agent는 그 파일을 읽어서 전송할 뿐이었다.

그렇기 때문에 CloudWatch Logs로 보냈다고 해서 EC2 내부 파일이 자동으로 줄어들지는 않았다.

 

logrotate

 

실제로 디스크 사용량을 점검했을 때 /home/ubuntu/app/application.log 의 파일 크기가 약 2GB 가까이 커져 있었다.

CloudWatch와 S3가 있어도, application.log 자체가 EC2 안에서 무한히 커지면 결국 디스크 부족이 발생한다.

그래서 application.log 를 logrotate 관리 대상에 넣었다.

 

/home/ubuntu/app/application.log {
    daily
    rotate 7
    maxsize 100M
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
    create 664 ubuntu ubuntu
}

logrotate는 위와 같이 설정하여 하루 단위로 rotate하되, 100MB가 넘을 때 rotate 되도록 설정했다.

 

현재 애플리케이션은 nohup java ... > application.log 2> error.log & 형태로 표준출력을 파일에 직접 리다이렉트하고 있다.

따라서 프로세스가 application.log 파일을 계속 가리키고 있기 때문에 단순히 rename 기반 rotate만 실행하면 기대와 다르게 동작할 수 있다. 그래서 copytruncate를 사용해 이 문제를 해결했다.

 

마치며

이번 글에서 사용한 3개 도구의 사용 목적은 다음과 같다.

 

CloudWatch Logs

  • 의도: 서버에 직접 접속하지 않고 로그를 중앙에서 확인하기 위해
  • 동작: EC2에 설치된 CloudWatch Agent가 로컬 로그 파일을 읽어 CloudWatch Logs로 전송
  • 목적: 여러 인스턴스의 로그를 한 곳에서 검색하고 운영 가시성을 확보하기 위해

S3 아카이빙

  • 의도: CloudWatch Logs에 장기간 보관할 때의 비용 부담을 줄이기 위해
  • 동작: CloudWatch Logs Subscription Filter가 Lambda를 호출하고, Lambda가 로그 이벤트를 S3에 압축 저장
  • 목적: 장기 보관과 비용 절감, 감사/분석용 아카이브 확보를 위해

logrotate

  • 의도: EC2 내부 application.log 가 계속 커져서 디스크 부족 위험이 있었기 때문에
  • 동작: 로컬 로그 파일을 주기적으로 회전하고 압축하며 오래된 파일을 삭제
  • 목적: CloudWatch와 S3는 외부 보관일 뿐, 로컬 파일 크기 자체는 줄여주지 않기 때문에

 

이렇게도 구성해보고 저렇게도 구성해보면서, 각 기술이 어디에 강점이 있고 어디까지는 커버하지 못하는지 비교해볼 수 있었다.

그리고 하나 사소하지만 중요한 점이 있는데 만약 logrotate를 사용하게 된다면 해당 설정을 인스턴스 생성 시점에 적용되도록 user-data에 포함시키는 것을 잊지 않기를 바란다.