티스토리 뷰

반응형

✏️  목차

  1. 배경 및 목표
  2. Prometheus + Grafana 직접 구축을 포기한 이유
  3. 최종 아키텍처
  4. 구축 단계
    • 1단계 - Actuator 보안 강화
    • 2단계 - Management 포트 분리
    • 3단계 - Docker 포트 Publish
    • 4단계 - Grafana Alloy EC2 설치
    • 5단계 - Alloy 설정
  5. 트러블슈팅
  6. 결과
  7. 정리 요약

 

✏️ 배경 및 목표

EAT-SSU 서버는 EC2 t3.micro (1GB RAM) 위에서 Docker로 운영 중이다.

운영 관측성(observability)을 높이기 위해 아래 지표들을 실시간으로 시각화하고 싶었다.

  • JVM 힙 메모리 사용량
  • API 응답 시간 (HTTP Statistics)
  • DB 커넥션풀 (HikariCP)
  • CPU 사용률 / Load Average
  • JVM GC 현황

이미 아래 의존성은 추가되어 있었고, /actuator/prometheus 엔드포인트에서 메트릭을 노출하고 있었다.
문제는 이 메트릭을 수집하고 시각화할 스택이 없다는 것이었다.

spring-boot-starter-actuator
micrometer-registry-prometheus

 

✏️ Prometheus + Grafana 직접 구축을 포기한 이유

처음에는 EC2에 Prometheus + Grafana 컨테이너를 직접 올리려 했는데 EC2 스펙이슈로 메모리 부족한 상태

Prometheus + Grafana를 추가하면 OOM(Out of Memory) 이 날 게 뻔한 상황.
결론: EC2 직접 구축 포기 → Grafana Cloud 무료 플랜을 사용하자

항목 상태
가용 메모리 (available) 369MB
이미 사용 중인 Swap 501MB
Prometheus 예상 사용량 ~150MB
Grafana 예상 사용량 ~200MB

 

 

✏️ 최종 아키텍처

Grafana Alloy를 선택하였다. Alloy는 Go 바이너리 단일 실행 파일이다.
컨테이너 없이 systemd 서비스로 실행하면 메모리 사용량이 약 72MB에 불과해 t3.micro에서도 충분히 감당할 수 있다.

Spring App (port 9000 / 9001)
        │
        │  scrape (localhost:9001/actuator/prometheus)
        ▼
Grafana Alloy  ← EC2 설치, systemd 서비스, ~72MB
        │
        │  remote_write (HTTPS)
        ▼
Grafana Cloud Mimir  ← 무료 플랜, 외부 저장소
        │
        │  query
        ▼
Grafana Dashboard  ← 실시간 시각화

 

✏️ 구축 단계

1단계 - Actuator 보안 강화

기존 SecurityConfig의 AUTH_WHITELIST를 확인해 보니 /actuator/**가 통째로 열려 있었다. 이는 /actuator/env, /actuator/metrics 같은 민감한 정보까지 외부에 노출되는 문제가 있다.

# Before
"/actuator/**"

# After
# 불필요한 Actuator 엔드포인트는 인증 없이 외부 접근이 불가하도록 화이트리스트를 최소화했다.
"/actuator/health",
"/actuator/prometheus"

 2단계 - Management 포트 분리

Actuator 엔드포인트를 앱 포트(9000)와 분리하여 외부에서 직접 접근할 수 없도록 별도 포트(9001)에 바인딩했다.

address: 127.0.0.1로 설정하면 컨테이너 내부 루프백에만 바인딩된다.
EC2 호스트에서 포트 포워딩(-p 127.0.0.1:9001:9001)을 받으려면 컨테이너가 0.0.0.0 에 바인딩되어 있어야 한다.
이를 127.0.0.1로 놓으면 Spring Boot 로그에는 정상 기동으로 찍혀도, 호스트에서 접속이 안 된다.

# application-dev.yml
management:
  server:
    port: 9001
    address: 0.0.0.0   # ⚠️ Docker 컨테이너 특성상 반드시 0.0.0.0
  metrics:
    tags:
      application: eatssu-dev   # Grafana 대시보드 필터용 레이블
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus

3단계 - Docker 포트 Publish

deploy.yml의 docker run 명령에 9001 포트를 추가했다.

127.0.0.1:9001:9001로 바인딩하면 EC2 내부(Alloy)에서만 접근 가능하고 외부 인터넷에서는 접근이 차단된다.

# .github/workflows/deploy.yml
sudo docker run -d --name eatssu-dev \
  --restart unless-stopped \
  -p 9000:9000 \
  -p 127.0.0.1:9001:9001 \    # ← 추가: EC2 내부에서만 접근 가능
  ...
포트 바인딩 역할
9000 0.0.0.0:9000 앱 API (외부 공개)
9001 127.0.0.1:9001 Actuator / Prometheus (EC2 내부 전용)

4단계 - Grafana Alloy EC2 설치

Docker 컨테이너 대신 systemd 서비스로 설치하여 메모리 오버헤드를 최소화했다.

# 1. Grafana APT 저장소 추가
sudo apt-get install -y apt-transport-https software-properties-common wget

sudo mkdir -p /etc/apt/keyrings/
wget -q -O - <https://apt.grafana.com/gpg.key> \
  | gpg --dearmor \
  | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null

echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] <https://apt.grafana.com> stable main" \
  | sudo tee /etc/apt/sources.list.d/grafana.list

# 2. Alloy 설치
sudo apt-get update
sudo apt-get install alloy -y

5단계 - Alloy 설정

Alloy 설정 파일(/etc/alloy/config.alloy)을 작성한다.

# /etc/alloy/config.alloy

# 1. Spring App의 /actuator/prometheus 스크랩
prometheus.scrape "spring_app" {
  targets = [{
    __address__    = "localhost:9001",
    __metrics_path__ = "/actuator/prometheus",
  }]
  forward_to = [prometheus.remote_write.metrics_hosted_prometheus.receiver]
}

# 2. Grafana Cloud Mimir로 remote_write
prometheus.remote_write "metrics_hosted_prometheus" {
  endpoint {
    name = "hosted-prometheus"
    url  = "<https://prometheus-prod-49-prod-ap-northeast-0.grafana.net/api/prom/push>"

    basic_auth {
      username = "YOUR_USERNAME"     # Grafana Cloud에서 발급
      password = "YOUR_API_TOKEN"    # Grafana Cloud API Token
    }
  }
}

설정 완료 후 서비스 활성화 및 시작:

sudo systemctl enable alloy
sudo systemctl start alloy

# 상태 확인
sudo systemctl status alloy

 

✏️ 트러블슈팅

문제: 9001 포트에 접속이 안 된다

배포 후 Alloy가 메트릭을 수집하지 못했다. 직접 확인해 보니 Spring Boot 로그에는 아무런 이상이 없었다.

Tomcat started on port(s): 9001 (http) with context path ''

원인 분석

Docker 컨테이너 네트워크는 격리된 네임스페이스를 사용하기 때문에, 컨테이너 내부의 127.0.0.1 은 호스트의 127.0.0.1과 다르다. EC2 호스트에서 포트 포워딩이 동작하려면 컨테이너가 0.0.0.0에 바인딩되어 있어야 한다.

설정 동작
management.server.address: 127.0.0.1 컨테이너 내부 루프백(127.0.0.1)에만 바인딩
-p 127.0.0.1:9001:9001 (호스트) EC2 호스트 → 컨테이너로 포워딩 시도
결과 컨테이너가 0.0.0.0에 안 열려 있으므로 포워딩 불가

 

해결

이렇게 하면 컨테이너는 모든 인터페이스에서 수신하되,
EC2 호스트 측에서는 루프백으로만 노출되어 보안을 유지하면서 Alloy가 접근할 수 있다.

# application-dev.yml
management:
  server:
    address: 0.0.0.0   # 127.0.0.1 → 0.0.0.0 으로 변경
# deploy.yml
-p 127.0.0.1:9001:9001   # EC2 호스트 측은 127.0.0.1 유지 (외부 차단)

 

✏️ 결과

Grafana Cloud 대시보드(Dashboard ID: 19004 - Spring Boot 3.x Statistics)에서 아래 지표들이 실시간으로 수집되고 있다.

 

 

✏️ 정리 요약

항목 내용
수집 에이전트 Grafana Alloy (systemd 서비스, ~72MB)
메트릭 저장소 Grafana Cloud Mimir (무료 플랜)
시각화 Grafana Cloud Dashboard (ID: 19004)
Actuator 보안 화이트리스트 최소화 (/health, /prometheus 만 공개)
포트 보안 management port 9001, EC2 내부(127.0.0.1)에서만 접근 가능
비용 무료

 

✏️ 알게된 점

  1. t3.micro에서 Prometheus + Grafana 직접 운영은 무리다. Grafana Cloud 무료 플랜을 활용하자.
  2. Grafana Alloy는 경량 Go 바이너리다. Docker 대신 systemd 서비스로 실행하면 메모리 부담이 크게 줄어든다.
  3. Docker 컨테이너에서 127.0.0.1 바인딩은 함정이다. 호스트 포트 포워딩을 쓰려면 컨테이너는 0.0.0.0, 호스트는 127.0.0.1로 노출하는 패턴을 써야 한다.
  4. t3.micro처럼 메모리가 빡빡한 환경에서도 Grafana Cloud + Alloy 조합으로 충분히 프로덕션 수준의 운영 모니터링을 구축할 수 있다.
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함