13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Observability

  • [인프라] Sentry 비용 최적화, 1년 운영 회고로 정리한 현실 전략

    [인프라] Sentry 비용 최적화, 1년 운영 회고로 정리한 현실 전략

    [인프라] Sentry 비용 최적화, 1년 운영 회고로 정리한 현실 전략

    Sentry 비용 최적화 이야기는 막상 운영을 시작하고 나서야 절실해지더라고요. 처음엔 에러 모니터링만 잘 되면 된다고 생각했는데, 실제로 1년 정도 굴려보니 비용 구조와 운영 습관이 생각보다 강하게 연결돼 있었습니다. 저도 처음엔 “에러를 잘 모으는 게 무조건 좋은 거 아닌가?” 싶었는데요. 실제로 써보니까 많이 모으는 것보다 의미 있게 남기는 것이 훨씬 중요했습니다. 이번 글에서는 제가 겪은 Sentry 사용 후기와 함께, 예상치 못한 비용 포인트, 클라우드 비용 관리 관점에서의 판단 기준, 그리고 에러 모니터링 비용을 줄이면서도 운영 효율을 유지한 방법을 정리해보겠습니다.

    특히 팀에서 이미 Sentry를 쓰고 있는데 월말마다 이벤트가 급증하거나, 알림이 너무 많아서 정작 중요한 장애를 놓치고 있다면 이 글이 꽤 도움이 될 겁니다. 혹시 이런 경험 있으신가요? 대시보드는 꽉 차 있는데, 남는 건 피로감뿐인 상황이요. 저도 딱 그랬습니다 ㅎㅎ

    Sentry 비용 최적화 관점의 이벤트 수집 아키텍처 다이어그램

    Sentry 비용 최적화 관점에서 보면, 애플리케이션에서 발생한 이벤트가 SDK, 릴레이, 프로젝트, 알림, 대시보드로 이어지는 흐름 전체를 한 번에 보는 게 중요합니다.

    Sentry 비용 최적화가 중요한 이유

    쉽게 말해 Sentry는 문제를 빨리 찾게 해주는 도구입니다. 그런데 운영이 길어질수록 “문제를 빨리 찾는 비용”도 같이 봐야 하거든요. 에러 모니터링 비용은 단순히 청구서 숫자만의 문제가 아닙니다. 너무 많은 이벤트는 분석 시간을 늘리고, 너무 많은 알림은 팀의 집중력을 깎습니다. 그래서 비용 최적화는 돈 절약이 아니라 운영 신호 대 잡음비를 올리는 작업에 가깝습니다.

    제가 1년 동안 운영하면서 가장 크게 느낀 점은 이거였어요. 비용이 늘어나는 순간은 장애가 커질 때보다, 쓸데없는 이벤트가 조용히 쌓일 때가 더 많았습니다. 배포 직후 반복되는 프런트엔드 에러, 이미 알고 있는 봇 요청, 의미 없는 health check 실패 로그, 중복 알림. 이런 것들이 눈에 안 띄게 쌓이다가 어느 순간 “왜 이렇게 많이 쓰고 있지?”가 되더라고요.

    Sentry에서 비용에 직접 영향을 주는 요소

    • 이벤트 볼륨: 동일 원인의 반복 에러도 건수는 그대로 쌓입니다.
    • 환경 분리: production, staging, dev를 무분별하게 넣으면 분석 난이도와 노이즈가 같이 증가합니다.
    • 릴리스 태깅: 배포 단위 구분이 안 되면 원인 파악 시간이 길어집니다.
    • 알림 정책: 중요하지 않은 이벤트까지 모두 알리면 대응 체계가 무너집니다.
    • 샘플링: 전부 수집하는 습관은 초반엔 편하지만, 장기 운영에서는 비효율로 돌아오는 경우가 많습니다.

    Sentry 사용 후기: 1년 써보니 드러난 예상치 못한 비용

    제가 처음 놓쳤던 부분은 “에러 건수”와 “운영 가치”가 비례하지 않는다는 점이었습니다. 처음엔 많이 잡히면 좋은 줄 알았거든요. 근데 여기서 문제가 생깁니다. 예를 들어 이미 원인을 알고 있고 우선순위가 낮은 에러가 계속 들어오면, 그건 모니터링 강화가 아니라 비용과 피로도만 늘리는 요소가 돼버립니다.

    실제로 써보니까 예상치 못한 비용 포인트는 대체로 아래 네 가지였어요.

    1. 배포 직후 반복 에러: 특정 버전에서 같은 오류가 급증하면 이벤트가 짧은 시간에 몰립니다.
    2. 봇/크롤러 유입: 정상 사용자가 아닌 요청에서 발생한 예외도 그대로 쌓이더라고요.
    3. 개발/테스트 환경 혼입: staging이나 로컬 테스트 이벤트가 production 분석을 방해했습니다.
    4. 소유자 없는 알림: 누구도 처리하지 않는 알림이 계속 발행되면 시스템만 시끄러워집니다.

    여기서 중요한 포인트! Sentry 효율은 결국 “무엇을 버릴지 결정하는 능력”에서 많이 갈립니다. 모든 걸 저장하는 게 정답은 아니었어요.

    Sentry 비용 최적화를 위한 기본 원칙

    저는 중간에 운영 원칙을 아예 다시 세웠습니다. 기준이 생기니까 그다음부터는 훨씬 편하더라고요.

    항목 처음 운영할 때 실수 지금 적용하는 기준
    이벤트 수집 전부 수집 중요도 기준으로 샘플링
    환경 구분 production/staging/dev 혼합 production 중심, 나머지는 제한 수집
    알림 모든 에러 알림 사용자 영향 큰 이슈만 즉시 알림
    태그 관리 태그 난립 서비스, 릴리스, 환경, 고객군 중심 최소화
    리뷰 주기 문제 생길 때만 확인 주간 리뷰로 노이즈 제거

    쉽게 말해 기준은 단순합니다. 장애 대응에 도움 되는 데이터는 남기고, 나머지는 과감히 줄인다. 이 원칙 하나로 Sentry 비용 최적화 방향이 꽤 명확해졌습니다.

    실전 구현: 제가 적용한 Sentry 효율 개선 단계

    이제부터는 실제로 어떻게 손봤는지 정리해보겠습니다. 팀마다 스택은 다르겠지만, 접근 방식은 꽤 공통적입니다.

    1. 환경별 수집 정책 분리

    가장 먼저 한 일은 production과 staging을 완전히 다르게 다루는 것이었습니다. 운영 환경은 놓치면 안 되니까 보수적으로 가져가고, 테스트 환경은 필요한 범위만 남겼습니다.

    export SENTRY_ENVIRONMENT=production
    export SENTRY_RELEASE=webapp-2026-08-13
    export SENTRY_TRACES_SAMPLE_RATE=0.2

    이 정도만 해도 기준이 생깁니다. 릴리스와 환경을 분리해두면, 나중에 어떤 배포에서 에러가 급증했는지 확인하기 훨씬 쉬워집니다.

    2. 애플리케이션 레벨에서 불필요한 예외 제외

    여기서 삽질을 좀 했습니다. 처음엔 서버에서 발생한 예외를 거의 다 보내도록 해놨었는데요. 실제로 운영하면서 보니 이미 알고 있는 사용자 취소, 봇 요청, 연결 종료 같은 케이스까지 다 들어오고 있더라고요. 이건 운영 정보가 아니라 노이즈에 가깝습니다.

    import sentry_sdk
    
    IGNORED_EXCEPTIONS = {
        "ClientDisconnected",
        "HealthcheckTimeout",
        "BotRequestError",
    }
    
    def before_send(event, hint):
        exc_info = hint.get("exc_info")
        if exc_info:
            exc_type = exc_info[0]
            if exc_type and exc_type.__name__ in IGNORED_EXCEPTIONS:
                return None
    
        request = event.get("request", {})
        headers = request.get("headers", {})
        user_agent = str(headers.get("User-Agent", "")).lower()
        if "bot" in user_agent or "crawler" in user_agent:
            return None
    
        return event
    
    sentry_sdk.init(
        dsn="YOUR_DSN",
        environment="production",
        release="webapp-2026-08-13",
        before_send=before_send,
    )

    이런 식으로 before_send 단계에서 걸러주면 꽤 효과가 있습니다. 물론 너무 공격적으로 제외하면 진짜 문제를 놓칠 수 있으니, 처음에는 로그를 같이 보면서 한두 주 정도 검증하는 게 좋습니다.

    Sentry 비용 최적화를 위한 환경 분리와 필터링 설정 다이어그램

    프로덕션과 스테이징 분리, 봇 요청 제외, 예외 필터링을 하나의 흐름으로 시각화한 이미지가 있으면 팀 내 공유가 훨씬 쉬워집니다.

    3. 샘플링으로 이벤트 볼륨 제어

    샘플링은 처음엔 좀 불안했습니다. “혹시 중요한 걸 놓치면 어떡하지?” 하는 생각이 들거든요. 저도 그랬어요. 근데 실제로는 전부 수집해서 못 보는 것보다 중요한 걸 선별해서 제대로 보는 것이 훨씬 낫더라고요.

    sentry_sdk.init(
        dsn="YOUR_DSN",
        environment="production",
        release="webapp-2026-08-13",
        traces_sample_rate=0.2,
    )

    트래픽이 많은 서비스라면 일괄 비율 샘플링보다, 특정 엔드포인트나 특정 고객군만 더 촘촘히 보는 방식도 고려할 만합니다. 다만 여기서는 수치보다 원칙이 중요해요. 사용자 영향이 큰 경로는 더 자세히, 반복적이고 가치가 낮은 구간은 더 가볍게 가져가시면 됩니다.

    4. 알림 정책 다시 설계

    제가 체감상 가장 크게 개선된 부분은 알림 정책이었습니다. 에러가 생길 때마다 메신저로 다 쏘면 처음엔 든든해 보이는데요. 한 달만 지나면 아무도 안 봅니다. 이건 진짜입니다. 알림은 많을수록 안전한 게 아니라, 신뢰도가 높을수록 안전하더라고요.

    • 새로운 이슈 중 production만 즉시 알림
    • 같은 에러라도 일정 시간 내 반복 급증 시 우선 알림
    • 이미 확인된 낮은 우선순위 이슈는 일일 요약으로 전환
    • 담당 서비스 태그가 없는 이벤트는 우선 분류 작업부터 수행

    이렇게 바꾸고 나서부터는 Sentry 사용 후기가 팀 내에서도 확실히 좋아졌습니다. “시끄러운 도구”에서 “필요할 때 믿고 보는 도구”로 바뀐 느낌이었거든요.

    5. 소스맵과 릴리스 정리

    프런트엔드 운영하시는 분들은 이거 꼭 챙기셔야 합니다. 소스맵이 꼬이면 에러는 들어오는데 분석 시간이 확 늘어납니다. 비용 문제는 단지 이벤트 수만이 아니라, 사람의 조사 시간에서도 생기거든요.

    export SENTRY_RELEASE=webapp-2026-08-13
    sentry-cli releases new "$SENTRY_RELEASE"
    sentry-cli releases finalize "$SENTRY_RELEASE"

    릴리스 정리가 잘 되면 배포 이후 특정 에러가 어느 버전부터 발생했는지 빠르게 추적할 수 있어요. 이건 직접 해보니 진짜 편하더라고요.

    ⚠️ 트러블슈팅: 실제로 겪었던 문제와 해결법

    운영하면서 제일 많이 부딪힌 건 “줄였더니 놓치는 것 아닐까”에 대한 불안이었습니다. 그래서 저는 한 번에 확 줄이지 않고, 단계적으로 조정했습니다.

    1. 문제: 필터를 너무 넓게 잡아 중요한 이벤트가 빠질까 걱정됨
      해결: 먼저 로그와 함께 병행 관찰하고, 제외 규칙은 소규모부터 적용했습니다.
    2. 문제: staging 이벤트가 production 분석을 방해함
      해결: 환경 태그를 강제하고, production 외 환경은 수집량을 제한했습니다.
    3. 문제: 봇 요청이 에러를 과도하게 만듦
      해결: User-Agent 기반 1차 필터링과 라우트 단위 예외 처리를 병행했습니다.
    4. 문제: 알림이 너무 많아 아무도 보지 않음
      해결: 긴급 알림, 일일 요약, 리뷰 대상 이슈를 분리했습니다.

    특히 봇 요청은 진짜 골칫거리였습니다. 처음엔 애플리케이션 문제인 줄 알고 한참 들여다봤는데, 알고 보니 이상한 경로를 두드리는 외부 요청이더라고요. 그때 깨달았어요. 모든 에러가 제품 문제는 아니다. 운영에서는 이 구분이 정말 중요합니다.

    검증과 결과: Sentry 효율이 좋아졌는지 어떻게 확인했나

    최적화는 느낌으로 하면 안 됩니다. 저는 아래 네 가지를 기준으로 봤어요.

    1. 주간 신규 이슈 수가 줄었는가
    2. 반복성 높은 동일 에러 비중이 줄었는가
    3. 알림 확인 후 실제 대응으로 이어지는 비율이 높아졌는가
    4. 배포 후 원인 파악 시간이 짧아졌는가

    정확한 수치를 여기서 단정적으로 적진 않겠습니다. 서비스마다 너무 다르더라고요. 다만 제가 직접 해보니, 이벤트 총량이 줄어든 것보다 분석 시간이 줄어든 효과가 더 크게 체감됐습니다. 월말 비용 체감도 덜했고, 무엇보다 팀이 대시보드를 다시 신뢰하기 시작했어요. 이건 숫자 이상으로 중요하더라고요.

    Sentry 비용 최적화 전후 대시보드 비교 이미지

    최적화 전후 지표를 대시보드로 비교하면 Sentry 효율 개선이 비용 절감뿐 아니라 대응 속도 향상으로 이어졌는지 한눈에 확인할 수 있습니다.

    검증 항목 최적화 전 상태 최적화 후 기대 상태
    이벤트 품질 중복/잡음 많음 핵심 이슈 중심
    알림 신뢰도 무시되는 알림 많음 실제 대응 비율 상승
    원인 분석 속도 버전 추적 어려움 릴리스 기준 추적 가능
    클라우드 비용 관리 월말 체감 증가 예측 가능성 향상

    운영 체크리스트: Sentry 비용 최적화할 때 꼭 보는 것

    • production 외 환경이 과도하게 들어오지 않는가
    • 반복성 높은 저가치 예외를 제외했는가
    • 릴리스와 환경 태그가 일관되게 붙는가
    • 알림 정책이 실제 대응 체계와 맞는가
    • 주간 단위로 노이즈 이벤트를 리뷰하는가

    이 체크리스트만 꾸준히 돌려도 에러 모니터링 비용은 꽤 안정됩니다. 그리고 클라우드 비용 관리 관점에서도 “나중에 한꺼번에 정리”보다 “조금씩 자주 정리”가 훨씬 덜 힘듭니다.

    자주 묻는 질문

    Sentry 비용 최적화는 결국 샘플링만 하면 되나요?

    아닙니다. 샘플링은 한 축일 뿐입니다. 환경 분리, 예외 제외, 릴리스 정리, 알림 정책 재설계가 같이 가야 효과가 나요.

    Sentry 사용 후기에서 가장 체감 큰 개선은 무엇이었나요?

    저는 알림 정책 재설계가 가장 컸습니다. 이벤트 수가 조금 줄어드는 것보다, 정말 중요한 알림만 보이게 만든 효과가 훨씬 크더라고요.

    에러 모니터링 비용을 줄이면 장애 대응력이 떨어지지 않나요?

    무조건 줄이면 그럴 수 있습니다. 그래서 핵심은 삭제가 아니라 선별입니다. 사용자 영향이 큰 이벤트는 더 잘 보고, 가치가 낮은 반복 이벤트는 과감히 줄이는 쪽이 맞아요.

    Sentry 비용 최적화 핵심 체크리스트 인포그래픽

    운영 원칙, 검증 지표, 알림 설계 기준을 한 장으로 정리한 요약 이미지는 팀 온보딩 자료로도 꽤 유용합니다.

    마무리: Sentry 효율은 도구 설정이 아니라 운영 습관에서 결정됩니다

    1년 정도 운영해보니 결론은 분명했어요. Sentry 비용 최적화는 옵션 몇 개 바꾼다고 끝나는 일이 아니었습니다. 이벤트를 어떻게 정의할지, 어떤 알림만 살아남게 할지, 어떤 데이터가 실제로 팀에 도움이 되는지 계속 다듬는 과정이더라고요.

    저도 처음엔 “많이 수집하면 안전하다”고 생각했었는데, 실제로 써보니까 잘 버리는 팀이 결국 더 빨리 대응했습니다. 이거 꽤 역설적이죠. 하지만 운영은 원래 그렇더라고요. 많이 쌓는 것보다, 필요한 걸 바로 찾는 게 더 중요합니다.

    다음 글에서는 이번 내용과 이어서 Sentry 알림 정책 설계를 조금 더 깊게 다뤄볼 예정입니다. 어떤 이슈를 즉시 알림으로 보내고, 어떤 건 요약으로 돌릴지 기준을 잡는 방법이요. 이전 글에서 다룬 로그 집계와 메트릭 분리 전략도 함께 보시면 전체 운영 그림을 잡는 데 도움이 될 겁니다.

    혹시 지금 Sentry를 쓰고 있는데 비용은 늘고 효율은 애매하다고 느끼신다면, 오늘 바로 해볼 수 있는 건 하나입니다. 최근 7일 이슈 중 반복 건수만 많고 조치 가치가 낮은 이벤트를 골라 제외 후보 목록부터 만들어보세요. 여기서부터 진짜 최적화가 시작됩니다. 드디어 감이 잡히실 겁니다 🎉

  • [Cloud] Sentry vs Elastic APM: 클라우드 환경 에러 및 성능 모니터링 솔루션 비교 분석

    [Cloud] Sentry vs Elastic APM: 클라우드 환경 에러 및 성능 모니터링 솔루션 비교 분석

    [인프라] Sentry vs Elastic APM 비교 분석

    Sentry Elastic APM 비교를 고민하시는 분들은 보통 비슷한 시점에 막히더라고요. 서비스는 클라우드에 올렸는데, 장애는 간헐적으로 터지고, CPU나 응답 시간도 오락가락하고, 로그만 봐서는 도대체 어디서부터 추적해야 할지 감이 안 오는 순간이 있습니다. 저도 홈랩이랑 실서비스 비슷한 테스트 환경에서 이것저것 붙여보면서 꽤 삽질했었는데요. 결론부터 말씀드리면 Sentry와 Elastic APM은 겹치는 영역이 있지만 출발점이 꽤 다릅니다. 하나는 에러 트래킹(Error Tracking, 예외 중심 추적)에 강하고, 다른 하나는 APM(Application Performance Monitoring, 애플리케이션 성능 모니터링)과 관측성(Observability, 시스템 상태를 추론하는 능력) 전체 흐름에 더 가깝습니다.

    그래서 오늘은 마케팅 문구 말고, 실제 운영자 시점에서 어떤 팀에 뭐가 더 맞는지 차근차근 정리해보겠습니다. 클라우드 모니터링을 처음 붙이는 분도 이해할 수 있게 풀어볼게요.

    Sentry Elastic APM 비교를 위한 클라우드 모니터링 아키텍처 다이어그램

    애플리케이션, SDK/Agent, 수집 서버, 대시보드까지 이어지는 전체 흐름을 한눈에 보여주는 비교용 다이어그램입니다.

    Sentry와 Elastic APM, 쉽게 말해 뭐가 다른가요?

    쉽게 말해 Sentry는 에러가 났을 때 개발자가 바로 달려가게 만드는 도구에 가깝고, Elastic APM은 서비스가 느려지거나 병목이 생길 때 시스템 전체 문맥을 보게 해주는 도구에 가깝습니다.

    • Sentry: 예외(Exception, 런타임 오류), 스택 트레이스(Stack Trace, 오류 호출 경로), 릴리스 추적(Release Tracking, 배포 버전별 상태) 쪽이 직관적입니다.
    • Elastic APM: 트랜잭션(Transaction, 요청 단위 처리 흐름), 스팬(Span, 세부 작업 구간), 인프라 메트릭(Metrics, CPU/메모리 같은 수치), 로그 연계가 자연스럽습니다.

    처음엔 저도 둘 다 비슷해 보였거든요. 근데 실제로 써보니까 질문 자체가 달라집니다.

    1. Sentry를 볼 때는 “왜 이 에러가 났지?”를 먼저 묻게 됩니다.
    2. Elastic APM을 볼 때는 “왜 이 요청이 느리지?” 또는 “어느 서비스 구간이 병목이지?”를 먼저 보게 됩니다.

    여기서 중요한 포인트! 장애 대응의 시작점이 예외냐, 성능 저하냐에 따라 체감 가치가 꽤 달라집니다.

    클라우드 모니터링 관점에서 보는 핵심 차이

    항목 Sentry Elastic APM
    주력 영역 에러 트래킹, 이슈 그룹화, 릴리스 단위 추적 성능 모니터링, 분산 추적, 로그/메트릭 연계
    초기 진입 난이도 상대적으로 빠름 구성 요소를 이해해야 해서 더 복합적일 수 있음
    개발자 체감 예외 발생 시 원인 파악이 빠름 느린 쿼리, 외부 호출, 서비스 병목 분석에 강함
    운영자 체감 애플리케이션 오류 집중 애플리케이션과 인프라를 함께 보기 좋음
    적합한 팀 작은 팀, 앱/웹 중심 팀, 빠른 에러 대응이 필요한 팀 Elastic Stack을 이미 쓰는 팀, 관측성 통합이 중요한 팀
    확장성 관점 이슈 기반 운영에 편함 로그, 메트릭, 트레이스 통합 설계에 유리

    제가 직접 해보니, 팀에 로그 플랫폼이 이미 있느냐가 생각보다 큽니다. 이미 Elasticsearch(엘라스틱서치, 검색/분석 엔진)와 Kibana(키바나, 시각화 대시보드)를 쓰고 있다면 Elastic APM 쪽이 훨씬 자연스럽게 이어집니다. 반대로 그런 기반이 없고 일단 에러부터 빨리 잡아야 한다면 Sentry가 체감 속도가 빠르더라고요.

    Sentry가 잘 맞는 상황

    Sentry는 특히 이런 상황에서 강합니다.

    • 배포 직후 특정 버전에서 에러가 급증하는지 빠르게 보고 싶을 때
    • 프론트엔드와 백엔드 예외를 한 번에 추적하고 싶을 때
    • 개발자가 스택 트레이스와 컨텍스트만 보고 바로 수정 포인트를 잡아야 할 때
    • 소규모 팀에서 운영 복잡도를 크게 늘리지 않고 에러 트래킹을 붙이고 싶을 때

    실제로 써보니까 Sentry는 “이 에러가 몇 명에게 영향을 줬는가”, “어느 릴리스 이후 시작됐는가” 같은 질문에 답하기 좋았습니다. 특히 이슈 그룹화(Issue Grouping, 같은 유형 오류 묶기)가 잘 되면 알림 피로도도 꽤 줄어듭니다.

    Elastic APM이 빛나는 상황

    Elastic APM은 다음처럼 전체 흐름을 보려는 팀에 잘 맞습니다.

    • 마이크로서비스(Microservices, 기능별로 쪼갠 서비스 구조) 환경에서 요청 경로를 따라가야 할 때
    • 애플리케이션 성능 문제와 인프라 메트릭을 같이 보고 싶을 때
    • 로그, 메트릭, 트레이스를 한 플랫폼에서 묶어 보고 싶을 때
    • 운영팀과 개발팀이 같은 관측성 도구를 공유해야 할 때

    이쪽은 처음엔 좀 묵직합니다. 저도 처음엔 “이걸 다 연결해야 하나?” 싶었는데, 한 번 구조가 잡히면 장점이 확실합니다. 예를 들어 API 응답이 느릴 때, 단순히 느리다는 사실만 보는 게 아니라 DB 쿼리, 외부 HTTP 호출, 특정 서비스 구간까지 내려가서 볼 수 있거든요. 이거 진짜 편하더라고요.

    실전 구현: Python 앱에 둘 다 붙여보기

    비교는 결국 손에 붙여봐야 감이 옵니다. 여기서는 가장 단순한 예제로 Flask(플라스크, Python 웹 프레임워크) 기준으로 보여드릴게요. 실제 서비스에서는 환경 변수로 키를 분리하고, 운영/스테이징 환경을 나눠서 쓰시는 걸 권장합니다.

    1. Sentry SDK 연결

    pip install flask sentry-sdk
    import os
    from flask import Flask
    import sentry_sdk
    from sentry_sdk.integrations.flask import FlaskIntegration
    
    sentry_sdk.init(
        dsn=os.environ.get("SENTRY_DSN"),
        integrations=[FlaskIntegration()],
        traces_sample_rate=0.1,
        send_default_pii=False,
    )
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return {"status": "ok"}
    
    @app.route("/error")
    def error():
        raise RuntimeError("sentry test error")

    여기서 dsn은 Sentry 프로젝트에서 발급받는 연결 정보입니다. traces_sample_rate는 성능 이벤트 샘플링 비율인데요. 무조건 높게 두기보다 트래픽 규모를 보고 잡는 게 좋습니다.

    2. Elastic APM Agent 연결

    pip install flask elastic-apm
    import os
    from flask import Flask
    from elasticapm.contrib.flask import ElasticAPM
    
    app = Flask(__name__)
    app.config["ELASTIC_APM"] = {
        "SERVICE_NAME": "demo-flask-app",
        "SECRET_TOKEN": os.environ.get("ELASTIC_APM_SECRET_TOKEN"),
        "SERVER_URL": os.environ.get("ELASTIC_APM_SERVER_URL"),
        "ENVIRONMENT": os.environ.get("APP_ENV", "production"),
    }
    
    apm = ElasticAPM(app)
    
    @app.route("/")
    def index():
        return {"status": "ok"}
    
    @app.route("/slow")
    def slow():
        import time
        time.sleep(1.2)
        return {"status": "slow"}

    Elastic APM은 보통 APM Server 또는 Elastic Cloud 쪽 엔드포인트로 데이터를 보냅니다. 서비스 이름을 어떻게 나누느냐가 꽤 중요하더라고요. 나중에 대시보드에서 서비스별로 분리해서 보게 되기 때문입니다.

    3. 컨테이너 환경 변수 분리

    services:
      app:
        image: my-flask-app:latest
        environment:
          SENTRY_DSN: "${SENTRY_DSN}"
          ELASTIC_APM_SERVER_URL: "${ELASTIC_APM_SERVER_URL}"
          ELASTIC_APM_SECRET_TOKEN: "${ELASTIC_APM_SECRET_TOKEN}"
          APP_ENV: "production"
        ports:
          - "8000:8000"

    클라우드 환경에서는 Kubernetes(쿠버네티스, 컨테이너 오케스트레이션)나 ECS 같은 플랫폼에서도 같은 원칙입니다. 민감한 값은 Secret으로 빼고, 서비스명과 환경명은 일관되게 가져가세요.

    Sentry Elastic APM 비교용 Flask 연동 구성 다이어그램

    실전 구현 단계에서 앱 코드, 환경 변수, 외부 모니터링 서비스가 어떻게 연결되는지 보여주는 구성 이미지입니다.

    ⚠️ 실제로 자주 겪는 문제와 트러블슈팅

    여기서부터가 진짜 운영 팁입니다. 문서만 보면 금방 붙을 것 같았는데, 저는 여기서 삽질 좀 했습니다 ㅎㅎ

    에러는 들어오는데 성능 데이터가 비어 있는 경우

    • Sentry에서는 트레이싱 샘플링 설정이 너무 낮거나 꺼져 있는지 확인합니다.
    • Elastic APM에서는 Agent 설정은 되었는데 서버 URL 또는 인증 토큰이 맞지 않는 경우가 많습니다.
    • 프록시(Proxy, 중간 전달 장비)나 보안 그룹 때문에 수집 엔드포인트로 아웃바운드가 막힌 경우도 자주 있습니다.

    서비스 이름이 제각각 찍히는 경우

    이건 운영하면서 은근 치명적입니다. 배포마다 이름이 달라지면 대시보드가 찢어집니다. service name 규칙을 미리 정해두세요. 예를 들면 team-service-env 같은 형태로요.

    알림이 너무 많이 오는 경우

    Sentry는 같은 유형의 예외를 얼마나 잘 묶어주느냐가 중요하고, Elastic APM은 임계값(Threshold, 경고 기준치) 설계를 잘해야 합니다. 처음부터 모든 알림을 켜면 금방 무뎌집니다. 저도 초반엔 메신저가 울리기만 하고 아무도 안 보는 상태가 됐었거든요.

    개인정보와 민감정보 유출 위험

    가장 조심해야 할 부분입니다. 에러 이벤트나 트레이스에 요청 본문, 헤더, 사용자 식별값이 섞여 들어갈 수 있습니다. 그래서 기본값을 그대로 믿지 말고 다음 원칙을 꼭 적용하세요.

    1. 민감한 필드는 수집 전에 마스킹(Masking, 값 가리기)합니다.
    2. PII(개인식별정보) 전송 여부를 명시적으로 검토합니다.
    3. 운영 환경과 개발 환경의 수집 범위를 분리합니다.

    검증: 붙인 뒤 무엇을 확인해야 하나요?

    연동이 끝났다고 바로 끝은 아닙니다. 최소한 아래 시나리오는 확인해보셔야 합니다.

    1. Sentry 검증: 테스트 예외를 한 번 발생시켜 이슈가 생성되는지 확인합니다.
    2. Elastic APM 검증: 응답이 느린 엔드포인트를 호출해 트랜잭션과 스팬이 보이는지 확인합니다.
    3. 배포 검증: 새 릴리스 이후 에러 추세가 구분되는지 확인합니다.
    4. 알림 검증: 실제로 필요한 채널로 경고가 가는지 확인합니다.
    curl http://localhost:8000/error
    curl http://localhost:8000/slow

    제가 실제로 써보니까, 검증할 때는 일부러 실패를 만들어보는 게 제일 확실합니다. 정상 상태만 보면 연동이 된 것처럼 보이는데, 막상 장애가 터지면 필요한 필드가 빠져 있는 경우가 있거든요. 드디어 됐다! 싶어서 넘겼다가 나중에 다시 뜯어보는 경우, 정말 많습니다.

    Sentry Elastic APM 비교 결과를 보여주는 에러 및 성능 대시보드 이미지

    하나는 에러 중심, 다른 하나는 성능 흐름 중심이라는 차이를 직관적으로 보여주는 결과 화면 예시입니다.

    어떤 팀이 무엇을 선택하면 좋을까?

    상황 추천 방향
    스타트업/소규모 서비스, 장애 원인 파악이 급함 Sentry 우선
    Elastic Stack을 이미 운영 중 Elastic APM 우선
    프론트엔드 예외와 백엔드 예외를 빠르게 모으고 싶음 Sentry 적합
    로그, 메트릭, 트레이스를 한곳에서 보고 싶음 Elastic APM 적합
    멀티서비스 병목 분석이 중요함 Elastic APM 쪽이 유리
    배포 후 에러 급증 여부를 빠르게 추적해야 함 Sentry 체감이 좋음

    사실 둘 중 하나만 절대 정답이라고 보긴 어렵습니다. 팀의 현재 성숙도와 운영 방식이 더 중요합니다. 로그 플랫폼이 아직 없고 개발자 중심으로 빠르게 대응해야 한다면 Sentry가 훨씬 덜 부담스럽습니다. 반대로 이미 Elastic 기반 운영 체계가 있다면 Elastic APM은 확장성이 좋습니다.

    자주 묻는 질문 정리

    Q. 둘 다 같이 써도 되나요?

    네, 가능합니다. 실제로 에러 트래킹은 Sentry, 통합 관측성은 Elastic APM으로 나눠 가져가는 팀도 있습니다. 다만 데이터 중복과 운영 포인트 증가를 감안하셔야 합니다.

    Q. 클라우드 모니터링 입문자는 무엇부터 시작할까요?

    개인적으로는 장애를 빨리 잡는 경험을 먼저 만드는 걸 추천합니다. 그래서 입문자는 Sentry 같은 에러 중심 도구부터 시작하고, 이후에 성능 모니터링과 분산 추적으로 넓히는 흐름이 덜 헷갈립니다.

    Q. Elastic APM은 언제 더 빛나나요?

    서비스가 여러 개로 나뉘고, 느려지는 구간이 앱 코드인지 DB인지 외부 API인지 구분이 안 될 때요. 그때부터는 트레이스 기반 분석 가치가 확 올라갑니다.

    Sentry Elastic APM 비교와 선택 기준을 정리한 인포그래픽

    팀 규모, 운영 방식, 분석 목적에 따라 어떤 도구가 더 맞는지 빠르게 판단할 수 있도록 요약한 비교 인포그래픽입니다.

    마무리: 결국 질문은 도구가 아니라 운영 방식입니다

    Sentry Elastic APM 비교를 한 줄로 줄이면 이렇습니다. Sentry는 에러 대응의 속도를 높여주고, Elastic APM은 성능과 구조를 읽는 시야를 넓혀줍니다.

    저도 처음엔 “둘 중 뭐가 더 좋지?”만 붙잡고 있었는데, 실제로 써보니까 질문을 바꿔야 하더라고요. 우리 팀은 지금 무엇이 더 아픈가? 에러 원인 파악이 급한지, 아니면 서비스 전체 병목과 클라우드 모니터링 체계를 정리해야 하는지가 먼저입니다.

    혹시 지금 막 APM 솔루션 도입을 검토 중이시라면, 제 추천은 이렇습니다. 작게 붙이고, 일부러 장애를 내보고, 대시보드와 알림이 실제 운영에 도움이 되는지 먼저 확인하세요. 그 다음에 범위를 넓히는 게 훨씬 덜 고생합니다.

    다음 글에서는 OpenTelemetry(OpenTelemetry, 관측성 데이터 수집 표준) 기준으로 Sentry와 Elastic 계열 도구를 어떻게 같이 엮을 수 있는지도 다뤄볼 예정입니다. 이전 글에서 로그 수집 파이프라인 정리한 내용과도 이어보시면 도움이 될 겁니다. 🎉

  • [Cloud] Sentry 메모리 누수 해결: 실전 디버깅 사례 연구

    [Cloud] Sentry 메모리 누수 해결: 실전 디버깅 사례 연구

    Sentry 메모리 누수 해결: 실전 디버깅 사례 연구

    Sentry 메모리 누수 문제는 생각보다 늦게 티가 납니다. 처음에는 응답이 조금씩 느려지는 정도라서 그냥 트래픽이 늘었나 싶거든요. 그런데 어느 순간부터 컨테이너가 재시작되고, OOMKilled(메모리 부족으로 인한 강제 종료)가 찍히고, 에러는 많은데 원인은 안 보이는 상황이 옵니다. 저도 프로덕션 환경에서 딱 그 구간을 겪어봤습니다. 처음엔 애플리케이션 코드보다 인프라 쪽 문제라고 의심했었는데, 실제로 파고 들어가 보니 요청별로 쌓이는 객체 참조가 해제되지 않는 전형적인 메모리 누수였습니다. 이번 글은 Sentry 메모리 누수를 어떻게 추적했고, 어떤 식으로 좁혀 갔는지, 그리고 최종적으로 어떤 기준으로 해결 여부를 검증했는지 정리해 봤습니다.

    특히 Sentry 디버깅, 클라우드 에러 모니터링, 메모리 누수 해결, 성능 최적화가 한 번에 엮여 있는 사례라서, 운영 중인 Python API 서버나 컨테이너 환경을 다루는 분들께 꽤 현실적인 참고가 될 거 같습니다.

    Sentry 메모리 누수 추적을 위한 프로덕션 아키텍처 다이어그램

    프로덕션 API 서버, Sentry, 컨테이너 메모리 사용량 흐름을 한눈에 보여주는 개요 이미지입니다.

    Sentry 메모리 누수, 왜 잡기 어려운가

    쉽게 말해 메모리 누수(memory leak, 더 이상 필요 없는 메모리가 해제되지 않고 계속 남는 현상)는 에러 한 번으로 끝나지 않습니다. 요청은 성공하는데 프로세스 메모리만 조금씩 올라갈 수도 있고, 특정 조건에서만 객체가 쌓일 수도 있죠. 그래서 로그(log, 텍스트 기록)만 봐서는 감이 잘 안 옵니다.

    여기서 주목할 포인트가 있습니다. Sentry는 기본적으로 에러 모니터링(error monitoring)과 이벤트 추적(event tracking)에 강점이 있고, 메모리 누수는 증상과 맥락을 묶어 보는 용도로 정말 유용했습니다. 저는 아래 네 가지를 먼저 봤어요.

    • 언제 메모리 사용량이 급격히 올라가는지
    • 어떤 릴리스(release, 배포 버전) 이후부터 재현되는지
    • 어떤 엔드포인트(endpoint, API 경로)에서 현상이 두드러지는지
    • 재시작 직전 남기는 경고 이벤트와 예외 스택이 무엇인지

    혹시 이런 경험 있으신가요? CPU는 멀쩡한데 메모리만 톱니처럼 올라가고, 재배포하면 잠깐 괜찮아졌다가 다시 터지는 상황이요. 이럴 때는 감으로 고치면 거의 다시 터집니다. 저도 처음엔 캐시(cache) 설정만 바꾸면 되겠지 했는데, 삽질 좀 했습니다 ㅎㅎ

    Sentry 디버깅 관점에서 본 핵심 개념

    제가 실제로 써보니, 메모리 누수는 도구를 나눠서 보는 게 훨씬 편했어요. Sentry는 현상의 타이밍과 요청 맥락을 모으고, Python 내장 도구는 어떤 객체가 쌓이는지를 확인하는 식이었습니다.

    도구 역할 이번 사례에서 한 일
    Sentry 이벤트 추적, 릴리스 비교, 환경 분리 특정 배포 이후 경고 이벤트 증가와 엔드포인트 상관관계 확인
    tracemalloc 메모리 할당 추적 어느 코드 경로에서 객체가 계속 쌓이는지 비교
    gc 가비지 컬렉션 상태 확인 참조가 남아 해제되지 않는 객체 패턴 점검
    ps/top/kubectl 운영 레벨 리소스 확인 프로세스 RSS 증가, 재시작 시점, OOM 패턴 확인

    쉽게 말해, Sentry 메모리 누수 대응은 Sentry 하나만으로 끝내는 작업이 아니라, 관측(observability, 시스템 상태를 관찰하는 능력)의 중심축으로 Sentry를 두고 주변 도구를 붙이는 작업입니다.

    제가 잡았던 실제 패턴

    문제의 원인은 요청 처리 중 생성한 큰 응답 데이터를 전역 리스트(global list)에 디버깅 목적으로 붙여 놓은 코드였습니다. 개발 단계에서는 편했는데, 프로덕션에서 요청이 누적되면서 리스트가 비워지지 않았죠. 더 골치 아픈 건 예외가 바로 나지 않았다는 점입니다. 그래서 클라우드 에러 모니터링만 보고 있으면 늦게 알아차리기 쉬워요.

    실전 구현 1: Sentry에 운영 맥락 심기

    처음 한 일은 Sentry 이벤트에 운영 맥락을 더 많이 넣는 것이었습니다. 그냥 SDK만 붙여 두면 예외는 잘 모이는데, Sentry 메모리 누수처럼 느린 장애는 맥락이 부족할 때가 많거든요.

    1. 릴리스(release)와 환경(environment)을 분리합니다.
    2. 의심되는 엔드포인트에 breadcrumb(브레드크럼, 이벤트 전 단계 기록)를 남깁니다.
    3. 메모리 사용량이 임계치에 가까워지면 warning 이벤트를 직접 보냅니다.
    4. 컨테이너 재시작 전후 로그와 Sentry 타임라인을 맞춰 봅니다.
    import os
    import time
    import resource
    import sentry_sdk
    from sentry_sdk import capture_message, set_tag, add_breadcrumb
    from sentry_sdk.integrations.logging import LoggingIntegration
    
    logging_integration = LoggingIntegration(
        level=None,
        event_level=None,
    )
    
    sentry_sdk.init(
        dsn=os.environ.get("SENTRY_DSN"),
        environment=os.environ.get("APP_ENV", "production"),
        release=os.environ.get("APP_RELEASE", "unknown"),
        integrations=[logging_integration],
        traces_sample_rate=0.1,
    )
    
    MEMORY_WARN_MB = 700
    
    def get_rss_mb():
        rss_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
        if os.uname().sysname == "Darwin":
            return rss_kb / 1024 / 1024
        return rss_kb / 1024
    
    def observe_request(path: str, request_id: str):
        set_tag("endpoint", path)
        set_tag("request_id", request_id)
        add_breadcrumb(
            category="request",
            message=f"handling {path}",
            level="info",
            data={"request_id": request_id, "ts": int(time.time())},
        )
    
        rss_mb = get_rss_mb()
        if rss_mb >= MEMORY_WARN_MB:
            capture_message(
                f"high memory usage detected: {rss_mb:.1f}MB",
                level="warning",
            )

    여기서 중요한 포인트! Sentry에 경고 이벤트를 직접 남기면, 예외가 나기 전 단계의 흐름이 보입니다. 저는 이걸 넣고 나서야 특정 API 요청이 몰린 뒤 10~20분 정도 지나면 경고가 늘어난다는 걸 확인했어요.

    Sentry 메모리 누수 분석에 필요한 release와 environment 태그 구성 이미지

    Sentry에서 release, environment, endpoint 태그를 어떻게 나눠서 보는지 설명하는 구성 이미지입니다.

    실전 구현 2: 재현 스크립트와 누수 코드 분리

    운영에서만 보이는 문제라도, 결국 로컬이나 스테이징(staging, 사전 검증 환경)에서 비슷하게 재현할 수 있어야 고칠 수 있죠. 저는 홈랩에서도 비슷하게 많이 해보는데, 재현이 되는 순간부터 속도가 확 올라갑니다.

    문제를 단순화한 예시는 이런 식이었습니다.

    from fastapi import FastAPI
    
    app = FastAPI()
    DEBUG_CACHE = []
    
    @app.get("/items")
    def items():
        payload = {"rows": ["x" * 10000 for _ in range(500)]}
    
        # 문제 코드: 요청마다 큰 객체를 전역 리스트에 보관
        DEBUG_CACHE.append(payload)
    
        return {"count": len(payload["rows"]), "cache_size": len(DEBUG_CACHE)}

    이런 코드는 테스트 몇 번 할 때는 티가 안 나요. 근데 프로덕션에서 요청이 계속 들어오면 얘기가 달라집니다. 그래서 부하를 살짝 줘서 반복 요청을 만들었습니다.

    for i in $(seq 1 300); do
      curl -s http://127.0.0.1:8000/items > /dev/null
    done
    
    ps -o pid,rss,command -p $(pgrep -f uvicorn)

    이후 tracemalloc으로 전후 차이를 찍었습니다.

    import tracemalloc
    import linecache
    
    tracemalloc.start(25)
    
    # 부하 실행 전 snapshot
    before = tracemalloc.take_snapshot()
    
    # 여기서 반복 호출 수행
    
    after = tracemalloc.take_snapshot()
    stats = after.compare_to(before, "lineno")
    
    for stat in stats[:10]:
        frame = stat.traceback[0]
        print(f"{frame.filename}:{frame.lineno} -> {stat.size_diff / 1024:.1f} KiB")
        print(linecache.getline(frame.filename, frame.lineno).strip())

    실제로 써보니까 이 방식이 좋았던 이유는 명확합니다. Sentry에서 어느 요청 흐름이 이상한지를 찾고, tracemalloc으로 어느 코드 줄이 증가하는지를 고정할 수 있었거든요. 두 도구가 따로 노는 게 아니라 딱 이어졌습니다.

    실전 구현 3: 수정 방향과 배포 전략

    수정 자체는 의외로 단순했습니다. 전역에 보관하던 디버그 데이터를 없애고, 꼭 필요하면 크기를 제한한 ring buffer(링 버퍼, 고정 길이 순환 버퍼)로 바꿨어요. 사실 운영 서버에서는 요청 본문이나 큰 응답 객체를 오래 들고 있는 패턴 자체를 피하는 게 맞습니다.

    from collections import deque
    from fastapi import FastAPI
    
    app = FastAPI()
    DEBUG_CACHE = deque(maxlen=20)
    
    @app.get("/items")
    def items():
        payload = {"rows": ["x" * 10000 for _ in range(500)]}
    
        # 필요한 메타데이터만 제한적으로 저장
        DEBUG_CACHE.append({
            "row_count": len(payload["rows"]),
        })
    
        return {"count": len(payload["rows"]), "cache_size": len(DEBUG_CACHE)}

    배포할 때도 한 번에 전체 트래픽을 넘기지 않았습니다. 제가 운영할 때는 이런 문제는 꼭 점진 배포(rolling deployment, 순차 배포)로 갑니다. 왜냐하면 메모리 누수는 수정했다고 끝이 아니라, 정말 증가 추세가 꺾였는지를 봐야 하거든요.

    1. 수정 버전을 새 릴리스로 배포합니다.
    2. Sentry에서 이전 릴리스와 새 릴리스를 분리해 봅니다.
    3. 동일 엔드포인트 기준 warning 이벤트 발생 빈도를 비교합니다.
    4. 컨테이너 재시작 횟수와 RSS 증가 그래프를 같이 봅니다.

    재현 스크립트, 문제 코드, 수정 후 구조를 비교해서 보여주는 디버깅 흐름 이미지입니다.

    ⚠️ 주의사항: 제가 실제로 겪은 삽질 포인트

    여기서부터가 진짜 중요합니다. 메모리 누수는 원인을 하나만 보면 놓치는 경우가 많아요.

    1. ru_maxrss를 실시간 메모리로 착각

    ru_maxrss는 최대 RSS 값이라서 순간 메모리 스냅샷처럼 쓰면 해석이 꼬일 수 있습니다. 저도 처음엔 현재 메모리라고 생각하고 봤다가 그래프 해석을 잘못했었어요. 그래서 운영에서는 ps, 컨테이너 메트릭, 노드 메모리 지표를 같이 봐야 합니다.

    2. 예외가 없으니 애플리케이션 문제를 늦게 의심

    OOMKilled는 쿠버네티스(Kubernetes)나 런타임 차원의 이벤트로 보이는 경우가 많아서, 애플리케이션 버그와 분리해서 생각하기 쉬워요. 근데 여기서 중요한 건, 예외가 없다고 누수가 없는 게 아니다라는 점입니다.

    3. 디버그 로그가 문제를 키움

    디버깅하려고 요청 본문, 응답 객체, ORM 결과를 통째로 메모리에 들고 있으면 오히려 누수를 악화시킬 수 있습니다. 특히 전역 변수, 캐시, 클로저(closure, 외부 변수를 참조하는 함수), 콜백 등록 구조는 한 번 더 의심해 보셔야 합니다.

    4. Sentry를 만능 분석기로 기대

    Sentry는 정말 강력한 도구지만, heap dump 분석기나 프로파일러(profiler)를 완전히 대체하지는 않습니다. 저는 Sentry를 상황판으로 쓰고, 세부 원인 분석은 tracemalloc과 코드 리뷰로 마무리했어요. 이 조합이 제일 현실적이었습니다.

    • ✅ Sentry: 릴리스, 환경, 요청 흐름, 경고 이벤트 연계
    • ✅ 시스템 도구: RSS, 재시작, 컨테이너 상태 확인
    • ✅ 언어 도구: 객체 증가 경로 추적
    • ⚠️ 단일 지표만 보고 결론 내리기 금지

    검증: 해결됐다고 판단한 기준

    드디어 됐다! 라고 말하기 전에, 저는 꼭 기준을 숫자가 아니라 패턴으로 봅니다. 구체적인 절대 수치는 환경마다 다르니까요. 대신 아래 기준은 어느 환경에서도 꽤 유효합니다.

    1. 동일 트래픽 조건에서 프로세스 메모리가 계속 우상향하지 않는지
    2. 문제 엔드포인트 호출 후에도 일정 시간 뒤 메모리 증가 추세가 평탄해지는지
    3. Sentry warning 이벤트 빈도가 줄었는지
    4. OOM 재시작이 사라졌는지
    5. 새 릴리스 이후 동일 이슈 그룹이 다시 생기지 않는지

    저는 수정 전후를 이런 식으로 운영 점검했습니다.

    kubectl get pods -n prod
    kubectl top pods -n prod
    kubectl describe pod api-server-xxxxx -n prod
    
    # 애플리케이션 로그와 Sentry 이벤트 시점을 함께 대조
    kubectl logs deployment/api-server -n prod --since=30m

    결과적으로 수정 후에는 특정 API 호출이 몰려도 메모리 그래프가 계속 계단식으로 올라가지 않았고, 재시작도 멈췄습니다. Sentry에서도 문제 릴리스 기준으로 쌓이던 경고 이벤트가 눈에 띄게 줄었어요. 이런 순간이 있죠. 아, 이건 증상만 눌러 놓은 게 아니라 원인을 제대로 건드렸구나 하는 느낌이요. 이거 진짜 편하더라고요.

    Sentry 메모리 누수 해결 전후 결과를 보여주는 대시보드 이미지

    수정 전후 메모리 사용량 추세와 Sentry 이벤트 변화를 비교하는 결과 대시보드 이미지입니다.

    정리: Sentry 메모리 누수 대응 체크리스트

    마지막으로, 제가 다시 같은 상황을 만나면 이 순서대로 갑니다. 독자분들도 북마크해 두시면 꽤 쓸 만하실 거 같습니다.

    단계 체크 포인트 목적
    1 Sentry에 release, environment, endpoint 태그 확인 문제 범위 좁히기
    2 경고 이벤트 수동 전송 추가 예외 전 단계 포착
    3 부하 재현과 tracemalloc 비교 코드 라인 단위 확인
    4 전역 객체, 캐시, 콜백 참조 점검 누수 패턴 제거
    5 점진 배포 후 Sentry와 메트릭 비교 해결 여부 검증

    정리해 보면, Sentry 메모리 누수 대응의 핵심은 Sentry를 단순 에러 수집기로 쓰지 않는 데 있습니다. 릴리스 비교, 경고 이벤트, 요청 태그를 잘 심어 두면 메모리 누수처럼 느리고 애매한 장애도 꽤 빨리 좁혀 갈 수 있거든요. 저도 처음엔 헷갈렸는데, 결국 답은 비슷했습니다. 증상을 구조화해서 보고, 재현하고, 작은 단서부터 묶어 가는 것. 운영은 늘 그렇게 풀리더라고요.

    다음 글에서는 OpenTelemetry(오픈텔레메트리, 분산 추적 표준)와 Sentry를 함께 붙여서 요청 지연과 에러를 한 번에 보는 흐름도 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 집계 파이프라인과 같이 보시면 더 이해가 잘 되실 거 같습니다.

    자주 묻는 질문

    Sentry만으로 메모리 누수를 찾을 수 있나요?

    완전히 단독으로 찾기보다는, 어느 흐름에서 문제가 커지는지를 좁히는 데 강합니다. 실제 누수 지점은 tracemalloc, gc, 코드 리뷰 같은 보조 수단이 필요할 때가 많아요.

    메모리 누수 해결 후 무엇을 가장 먼저 봐야 하나요?

    같은 트래픽에서 메모리 사용량이 계속 증가하는지, 컨테이너 재시작이 멈췄는지, 그리고 Sentry 디버깅 기준으로 동일 릴리스 이슈가 줄었는지를 먼저 보시면 됩니다.

    클라우드 에러 모니터링만 잘 되어 있으면 충분한가요?

    충분하지 않을 때가 많습니다. 메모리 누수는 인프라 이벤트처럼 보이지만 실제 원인은 코드 참조 구조인 경우가 많아서, 애플리케이션 계층과 시스템 계층을 같이 보셔야 해요.

    Sentry 메모리 누수 대응 체크리스트 인포그래픽

    Sentry, tracemalloc, 시스템 메트릭을 조합한 대응 순서를 한 장으로 요약한 이미지입니다.

  • [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    Kubernetes 환경을 운영하다 보면 결국 로그 때문에 한 번은 크게 삽질하게 됩니다. Pod(파드)가 재시작되면 이전 로그가 날아가고, 노드가 여러 대로 늘어나면 어디서 무슨 에러가 터졌는지 찾는 데 시간이 꽤 걸리거든요. 저도 홈랩과 실무 환경에서 비슷한 문제를 겪으면서 Loki Kubernetes 로그 구성을 진지하게 붙잡게 됐습니다. 처음엔 “그냥 kubectl logs로 보면 되지 않나?” 싶었는데, 운영 규모가 조금만 커져도 그 방식은 금방 한계가 오더라고요.

    그래서 이번 글에서는 Grafana Loki를 기준으로 Kubernetes 로그 수집과 중앙 집중형 로깅 구성을 어떻게 가져가면 좋은지, 제가 직접 해보면서 정리한 흐름으로 풀어보겠습니다. 너무 이론만 길게 가지 않고, 바로 써먹을 수 있는 형태로 설명드릴게요.

    Loki, Promtail, Grafana, Kubernetes 노드와 애플리케이션 로그 흐름을 한눈에 보여주는 전체 구성도입니다.

    Loki로 Kubernetes 로그를 모아야 하는 이유

    쉽게 말해 Loki는 로그를 저장하고 검색하기 위한 시스템입니다. 로그 전체를 무겁게 색인(indexing)하는 방식보다는, 메타데이터(label) 중심으로 다루는 접근이 특징이죠. 이게 왜 좋냐면, 운영 입장에서 필요한 로그를 비교적 효율적으로 모으고 찾는 데 유리하거든요.

    제가 처음 Loki를 붙여봤을 때 좋았던 점은 딱 세 가지였습니다.

    • Grafana(그라파나)와 궁합이 좋습니다. 로그 조회 화면이 익숙해서 진입 장벽이 낮더라고요.
    • Kubernetes 로그 수집 흐름을 만들기 편합니다. 특히 DaemonSet(데몬셋) 기반 수집기가 노드별 로그를 긁어오는 구조가 이해하기 쉬웠습니다.
    • 메트릭(metrics), 로그(logs), 추적(traces) 관측 체계를 한 방향으로 정리하기 좋습니다.

    물론 무조건 Loki만 정답은 아닙니다. Elasticsearch(엘라스틱서치) 계열이 더 맞는 조직도 있습니다. 다만 “복잡도를 조금 낮추면서 Kubernetes 로그를 중앙에서 보고 싶다”는 목적이라면 Loki는 꽤 현실적인 선택지에요.

    Grafana Loki 핵심 개념부터 먼저 잡아보겠습니다

    Loki, Promtail, Grafana 역할 구분

    구성요소 역할 운영 포인트
    Loki 로그 저장 및 조회 API 제공 라벨 설계가 중요합니다
    Promtail 노드의 로그 파일 수집 후 Loki로 전송 DaemonSet으로 배포하는 경우가 많습니다
    Grafana 로그 검색, 필터링, 시각화 처리 대시보드와 Explore 기능이 편합니다

    여기서 중요한 포인트! Promtail(프롬테일)이 하는 역할은 로그를 읽어 Loki로 보내는 수집기 일이죠. Kubernetes에서는 보통 컨테이너 로그가 노드 파일 시스템에 쌓이기 때문에, 노드마다 하나씩 떠 있는 DaemonSet이 그 로그를 읽는 구조를 많이 씁니다.

    라벨(Label)이 왜 중요할까요?

    Loki는 라벨 기반으로 로그를 찾습니다. 예를 들면 namespace, pod, container, app 같은 값들이죠. 실제로 써보니까 라벨을 너무 많이 붙이면 관리가 복잡해지고, 반대로 너무 적으면 검색이 답답하더라고요. 저도 처음엔 이것저것 다 넣었다가 쿼리가 지저분해져서 다시 정리했었습니다 ㅎㅎ

    • 자주 검색하는 기준만 라벨로 둡니다
    • 변동성이 큰 값은 라벨 남발을 피합니다
    • 운영팀이 실제로 찾는 축을 먼저 정합니다

    Kubernetes 로그 중앙화 구성 흐름

    전체 흐름은 생각보다 단순합니다.

    1. 애플리케이션 컨테이너가 표준 출력(stdout/stderr)으로 로그를 남깁니다.
    2. Kubernetes 노드가 해당 로그를 파일 형태로 보관합니다.
    3. Promtail이 노드에서 로그를 읽고 라벨을 붙입니다.
    4. Loki가 로그를 저장합니다.
    5. Grafana에서 검색하고 필터링합니다.

    이 구조가 좋은 이유는 애플리케이션 코드를 크게 건드리지 않아도 된다는 거거든요. 이미 컨테이너 표준 출력으로 로그를 잘 남기고 있다면, 인프라 쪽에서 수집 체계를 붙이기 좋습니다.

    실전 구현: Helm으로 Loki와 Promtail 배포하기

    이제 본격적으로 들어가 보겠습니다. 여기서는 많이 쓰는 Helm(헬름) 기반 예시로 설명드릴게요. 실제 운영 환경에서는 스토리지, 보존 기간, 인증, 멀티 테넌시 여부 등을 더 따져야 하지만, 처음 시작할 때는 흐름을 먼저 잡는 게 중요합니다.

    1. 네임스페이스 생성

    kubectl create namespace observability

    저는 보통 모니터링/로깅 계열 리소스를 따로 분리하려고 observability 같은 네임스페이스를 씁니다. 꼭 이 이름일 필요는 없어요.

    2. Helm 저장소 추가

    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update

    3. Loki values 파일 작성

    처음엔 기본값으로도 올라가긴 하는데, 최소한 배포 형태와 저장 방식은 눈으로 확인하고 가는 게 좋습니다.

    loki:
      auth_enabled: false
    
    singleBinary:
      replicas: 1
    
    gateway:
      enabled: true
    
    monitoring:
      dashboards:
        enabled: true
      rules:
        enabled: true

    테스트나 홈랩에서는 이렇게 단순하게 시작해도 됩니다. 다만 운영에서는 스토리지 백엔드와 고가용성 구성을 별도로 검토하셔야 합니다. 여기서 무작정 단일 인스턴스로 오래 끌고 가면 나중에 확장 시점에 다시 손이 많이 가더라고요.

    4. Loki 설치

    helm install loki grafana/loki -n observability -f loki-values.yaml

    5. Promtail values 파일 작성

    config:
      clients:
        - url: http://loki-gateway.observability.svc.cluster.local/loki/api/v1/push
    
      snippets:
        pipelineStages:
          - cri: {}
    
      positions:
        filename: /run/promtail/positions.yaml
    
      scrapeConfigs:
        - job_name: kubernetes-pods
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_namespace]
              target_label: namespace
            - source_labels: [__meta_kubernetes_pod_name]
              target_label: pod
            - source_labels: [__meta_kubernetes_pod_container_name]
              target_label: container
            - source_labels: [__meta_kubernetes_pod_label_app]
              target_label: app

    이 부분이 핵심입니다. 어떤 Kubernetes 메타데이터를 라벨로 가져갈지 결정하는 구간이거든요. 제가 실제로 써보니까 namespace, pod, container, app 정도부터 시작하는 게 무난했습니다.

    Kubernetes 로그 수집을 위한 Promtail과 Grafana Loki 구성도

    Promtail DaemonSet이 각 노드의 컨테이너 로그 파일을 읽고 라벨을 붙여 Loki로 보내는 흐름을 설명하는 이미지입니다.

    6. Promtail 설치

    helm install promtail grafana/promtail -n observability -f promtail-values.yaml

    7. Grafana에서 데이터 소스 연결

    Grafana를 이미 쓰고 계신다면 Loki 데이터 소스를 추가하면 됩니다. 같은 클러스터 안에 있다면 서비스 이름으로 연결하는 경우가 많습니다.

    kubectl get svc -n observability

    Grafana Explore 화면에서 Loki를 선택하고 아래처럼 조회해보면 기본 파이프라인이 제대로 도는지 확인할 수 있어요.

    {namespace="default"}

    처음 이 쿼리로 로그가 딱 뜨는 순간, 드디어 됐다! 싶은 느낌이 있습니다. 로그 중앙화는 여기서부터가 시작이거든요.

    운영하면서 유용했던 로그 쿼리 예시

    Loki는 LogQL(로그쿼리언어) 형태로 조회합니다. 익숙해지면 꽤 편합니다.

    {namespace="production", app="nginx"}
    {namespace="production", pod=~"api-.*"}
    {container="backend"} |= "error"
    {app="payments"} |= "timeout"
    • |= 는 특정 문자열 포함 검색에 자주 씁니다
    • 정규식 매칭은 필요한 곳에만 씁니다
    • 라벨 기준 필터를 먼저 좁히고 문자열 검색을 붙이면 보기가 편합니다

    이 부분은 팀 내에서 공통 조회 패턴을 만들어두면 좋습니다. 예를 들어 장애 대응 때 “namespace 먼저, app 다음, error 문자열 마지막” 같은 식으로 말이죠.

    ⚠️ 실제로 많이 겪는 문제와 트러블슈팅

    여기서부터는 제가 꽤 자주 봤던 문제들입니다. 저도 처음엔 헷갈렸는데, 한 번 원리를 이해하고 나면 금방 잡힙니다.

    1. 로그가 아예 안 들어오는 경우

    • Promtail Pod가 정상 실행 중인지 확인합니다
    • Loki push URL이 올바른지 봅니다
    • 네트워크 정책(NetworkPolicy)으로 막히지 않았는지 확인합니다
    • 애플리케이션이 파일이 아니라 표준 출력으로 로그를 남기는지 확인합니다
    kubectl get pods -n observability
    kubectl logs -n observability daemonset/promtail
    kubectl logs -n observability deploy/loki

    2. 라벨이 너무 많아져서 조회가 복잡한 경우

    이건 정말 흔합니다. Kubernetes 메타데이터를 다 가져오고 싶은 마음이 들거든요. 근데 운영해보면 자주 안 쓰는 라벨은 오히려 방해가 됩니다. 중앙 집중형 로깅의 목적은 데이터를 많이 붙이는 게 아니라, 필요한 순간 빨리 찾는 데 있다는 점을 잊지 않는 게 중요합니다.

    3. 특정 Pod 로그만 유독 안 보이는 경우

    보통 라벨 매핑 문제이거나, 애플리케이션 로그 출력 방식 차이인 경우가 많았습니다. 사이드카(sidecar)를 쓰는 구조나 멀티 컨테이너 Pod에서는 컨테이너 이름 기준으로 먼저 좁혀보는 게 좋더라고요.

    4. 과거 로그를 오래 보관하고 싶은 경우

    이건 저장소 설계와 연결됩니다. 홈랩에서는 일단 짧게 가져가도 되는데, 운영에서는 보존 정책(retention policy)을 꼭 먼저 정하셔야 합니다. 로그는 쌓이는 속도가 생각보다 빠릅니다. 처음엔 괜찮다가 어느 날 저장소가 가득 차서 당황하는 경우가 생기거든요.

    검증: Loki Kubernetes 로그가 제대로 모이는지 확인하기

    설치가 끝났다고 바로 안심하면 안 됩니다. 꼭 검증 단계를 거치셔야 합니다. 저는 아래 순서로 확인합니다.

    1. 테스트용 Pod를 띄워 의도적으로 로그를 발생시킵니다.
    2. Promtail 로그에서 수집 관련 에러가 없는지 봅니다.
    3. Grafana Explore에서 namespace와 pod 기준으로 조회합니다.
    4. 문자열 필터로 원하는 로그를 찾을 수 있는지 확인합니다.
    kubectl run log-check --image=busybox --restart=Never -- /bin/sh -c 'i=0; while true; do echo "log-check-$i"; i=$((i+1)); sleep 5; done'

    그 다음 Grafana에서 아래처럼 조회합니다.

    {pod="log-check"}

    이렇게 테스트 로그가 보이면 기본 파이프라인은 정상입니다. 이후엔 에러 패턴, 특정 앱 로그, 운영 네임스페이스 로그를 차례로 점검하면 됩니다.

    Grafana Loki로 Kubernetes 로그를 조회하는 대시보드 이미지

    Grafana Explore 또는 대시보드에서 namespace, pod, app 라벨로 로그를 필터링한 결과 예시 이미지입니다.

    비교: Loki와 다른 로그 수집 접근의 차이

    항목 Loki 중심 구성 전통적 대용량 검색 중심 구성
    초기 진입 난이도 상대적으로 단순한 편 구성 요소가 많은 편
    Grafana 연동 매우 자연스러움 별도 연계 구성이 필요할 수 있음
    라벨 설계 중요도 매우 높음 상대적으로 검색 중심 접근 가능
    홈랩/소규모 시작 부담이 덜함 조금 무거울 수 있음

    이 표를 보면 감이 오실 겁니다. “빠르게 Kubernetes 로그 중앙화를 시작하고 싶다”면 Loki가 꽤 괜찮습니다. 반대로 아주 복잡한 검색 시나리오, 대규모 장기 보관 전략, 조직 표준 스택이 이미 정해져 있다면 다른 선택지가 더 맞을 수도 있어요.

    💡 운영 팁: 제가 나중에 꼭 챙기게 된 것들

    • 라벨 최소화: 처음부터 과하게 넣지 않습니다
    • 보존 정책: 저장소 사용량을 반드시 같이 봅니다
    • 대시보드 표준화: 팀이 자주 보는 쿼리를 정리합니다
    • 애플리케이션 로그 형식: JSON 로그 여부를 팀 기준으로 맞추면 훨씬 편합니다
    • 장애 대응 문서화: “로그가 안 보일 때 체크리스트”를 만들어두면 좋습니다

    특히 JSON 형태 로그를 쓰는 팀이라면 이후 파싱과 필드 추출도 훨씬 수월해집니다. Kubernetes 로그 수집을 이렇게 중앙화하면 메트릭 모니터링과 함께 관측성(Observability, 시스템 상태를 외부 신호로 이해하는 능력)이 한 단계 올라가는 걸 체감하실 겁니다.

    중앙 집중형 로깅 도입 전후 Loki Kubernetes 로그 운영 비교 이미지

    kubectl logs 중심 운영과 Loki 기반 중앙 집중형 로깅 운영의 차이를 한눈에 정리한 비교 이미지입니다.

    자주 묻는 질문

    Q1. Kubernetes 로그 수집은 꼭 Promtail이어야 하나요?

    꼭 그렇진 않습니다. 다만 Loki와 가장 자연스럽게 엮이는 수집기로 많이 사용됩니다. 시작 단계에서는 조합이 단순한 쪽이 유지보수에 유리하더라고요.

    Q2. Grafana Loki만 설치하면 끝인가요?

    아닙니다. 저장소, 보존 정책, 접근 제어, 대시보드 표준화까지 같이 봐야 운영 품질이 올라갑니다. 설치보다 운영 기준을 세우는 게 더 중요합니다.

    Q3. 중앙 집중형 로깅이 왜 필요한가요?

    장애 대응 속도가 달라집니다. 노드와 Pod를 옮겨 다니며 로그를 찾는 시간을 줄일 수 있거든요. 특히 여러 서비스가 동시에 얽히는 환경에서는 체감 차이가 큽니다.

    마무리: Loki Kubernetes 로그 구성, 처음엔 단순하게 시작하세요

    정리해보면 Loki Kubernetes 로그 구성의 핵심은 화려한 기능보다도, 어떤 로그를 어떤 라벨로 모을지를 먼저 정하는 데 있습니다. 저도 처음엔 설정 파일만 붙잡고 씨름했었는데, 결국 답은 단순하더라고요. 필요한 로그를 빨리 찾을 수 있느냐, 장애 때 팀이 같은 화면을 보고 이야기할 수 있느냐, 그게 제일 중요했습니다.

    처음 구축하신다면 작은 범위에서 시작해보세요. 특정 네임스페이스 하나, 서비스 하나부터 붙여보고, 쿼리 패턴을 정리한 다음 확장하는 방식이 훨씬 덜 힘듭니다. 실제로 써보니까 이게 가장 덜 삽질하는 길이었습니다. 혹시 지금 Grafana Loki 도입을 고민 중이시라면, 이번 주말 홈랩에서라도 한 번 꼭 올려보세요. 생각보다 금방 감이 옵니다.

  • [Cloud] New Relic 사용 후기: 1년 APM 운영하며 배운 성능 모니터링 실전 경험

    [Cloud] New Relic 사용 후기: 1년 APM 운영하며 배운 성능 모니터링 실전 경험

    New Relic 사용 후기: 1년 APM 운영하며 배운 성능 모니터링 실전 경험

    New Relic 사용 후기를 찾는 분들은 대개 비슷한 시점에 오시더라고요. 서비스가 어느 정도 커졌는데, 장애는 꼭 새벽에 터지고, CPU나 메모리만 봐서는 원인을 못 찾겠고, 개발팀이랑 인프라팀이 서로 “어디서 느린 거지?” 하고 로그만 뒤집는 상황 말입니다. 저도 13년차 인프라 엔지니어로 일하면서 이런 구간을 여러 번 겪었고, 실제로 지난 1년 동안 New Relic을 운영 환경에 붙여 보면서 APM(Application Performance Monitoring, 애플리케이션 성능 관리)이 왜 필요한지 몸으로 배웠거든요.

    처음엔 솔직히 “모니터링 툴이 다 거기서 거기 아닌가?” 싶었는데요. 실제로 써보니까 New Relic은 단순히 그래프 예쁘게 보여주는 도구라기보다, 문제를 좁혀 가는 순서 자체를 바꿔 주는 도구에 가깝더라고요. 이번 글에서는 화려한 성공담보다는, 제가 직접 해보니 좋았던 점과 불편했던 점, 그리고 삽질했던 포인트까지 같이 정리해보겠습니다.

    New Relic 사용 후기와 함께 보는 전체 서비스 모니터링 아키텍처 이미지

    애플리케이션, 데이터베이스, 외부 API, 사용자 요청 흐름 위에 New Relic이 어떻게 관측 지점을 만드는지 보여주는 개요 이미지입니다.

    1. APM이 필요했던 이유: 성능 모니터링의 빈칸

    예전에는 Node Exporter나 cAdvisor 같은 인프라 지표 위주로 많이 봤습니다. 물론 그것만으로도 기본 체력은 확인할 수 있죠. 그런데 실제 장애 대응에서는 그다음 질문이 꼭 나옵니다. “그래서 어느 요청이 느렸고, 어떤 쿼리가 문제였고, 외부 호출 중 어디서 시간이 새는 건데?” 여기서부터 일반적인 서버 모니터링과 애플리케이션 성능 관리가 갈립니다.

    쉽게 말해 인프라 모니터링은 서버가 아픈지를 보는 거고, APM은 서비스 요청이 어디서 아픈지를 보여주는 쪽입니다. CPU 40%인데 사용자는 느리다고 할 수 있거든요. 저도 처음엔 이 간극이 좀 답답했습니다. 특히 마이크로서비스처럼 서비스가 여러 개로 나뉘면, 한 군데만 봐서는 절대 안 보입니다.

    • 인프라 지표: CPU, 메모리, 디스크, 네트워크 중심
    • APM 및 성능 모니터링: 트랜잭션 응답 시간, 에러율, 외부 호출, DB 쿼리, 분산 추적 중심
    • 운영 관점 핵심: “서버 상태”보다 “사용자 경험”에 더 가깝게 본다는 점

    2. New Relic 사용 후기를 시작하기 전에: 핵심 개념

    New Relic은 애플리케이션 안에 에이전트(agent)를 심거나 연동해서 요청 흐름과 성능 데이터를 수집하고 분석하는 플랫폼입니다. 처음 접하면 메뉴가 많아서 조금 압도적이긴 한데요. 저도 처음엔 “이게 뭔가” 싶었는데 하루 이틀 만져보니 핵심은 의외로 단순했습니다.

    1. 애플리케이션에 에이전트를 붙입니다.
    2. 트래픽이 들어오면 트랜잭션 단위로 데이터를 수집합니다.
    3. 응답 시간, 에러, 외부 호출, 데이터베이스 구간을 나눠 보여줍니다.
    4. 이상 징후가 보이면 알림(alert)과 대시보드로 추적합니다.

    여기서 중요한 포인트! New Relic 사용 후기를 읽을 때 꼭 봐야 할 건 “기능이 많다”가 아니라, 문제 분석 시간이 실제로 줄었는가입니다. 저한테는 이게 가장 컸습니다. 예전에는 로그를 먼저 보고 추측했는데, New Relic을 붙인 뒤에는 느린 엔드포인트부터 보고 그다음 로그를 보는 순서로 바뀌었습니다.

    항목 인프라 모니터링 New Relic APM
    관점 서버/컨테이너 상태 요청과 코드 실행 흐름
    주요 질문 서버가 바쁜가? 왜 이 요청이 느린가?
    장애 대응 자원 병목 추정 문제 지점 구간화
    협업 효과 인프라팀 중심 개발팀-운영팀 공통 언어 제공

    3. 실전 적용: New Relic 도입 시작하기

    실전에서는 거창하게 시작하지 않는 게 중요합니다. 처음부터 모든 서비스에 다 붙이려 하면 운영팀이 먼저 지칩니다 ㅎㅎ 저는 트래픽이 많고 문의가 자주 들어오던 핵심 API부터 시작했습니다.

    3-1. 애플리케이션 연결 전 체크 포인트

    • 어떤 서비스가 가장 중요한지 우선순위를 정합니다.
    • 운영 환경 변수 관리 방식을 먼저 정리합니다.
    • 로그, 메트릭, 트레이스 중 무엇을 먼저 볼지 결정합니다.
    • 알림을 누구에게 어떤 채널로 보낼지 정합니다.

    3-2. Docker 환경에서 기본 연결 예시

    아래는 개념적으로 가장 많이 쓰는 방식입니다. 실제 변수명이나 연동 방식은 언어와 런타임에 따라 조금씩 다를 수 있으니, 운영 중인 스택에 맞춰 공식 문서를 같이 보시는 게 좋습니다.

    export NEW_RELIC_LICENSE_KEY="YOUR_LICENSE_KEY"
    export NEW_RELIC_APP_NAME="my-production-api"
    export NEW_RELIC_ENVIRONMENT="production"
    
    docker run -d \
      --name my-api \
      -e NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY" \
      -e NEW_RELIC_APP_NAME="$NEW_RELIC_APP_NAME" \
      -e NEW_RELIC_ENVIRONMENT="$NEW_RELIC_ENVIRONMENT" \
      my-api-image:latest

    쿠버네티스(Kubernetes, 컨테이너 오케스트레이션) 환경이라면 보통 Secret과 Deployment에 나눠 넣게 됩니다.

    apiVersion: v1
    kind: Secret
    metadata:
      name: newrelic-secret
    type: Opaque
    stringData:
      NEW_RELIC_LICENSE_KEY: "YOUR_LICENSE_KEY"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-api
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: my-api
      template:
        metadata:
          labels:
            app: my-api
        spec:
          containers:
            - name: my-api
              image: my-api:latest
              env:
                - name: NEW_RELIC_LICENSE_KEY
                  valueFrom:
                    secretKeyRef:
                      name: newrelic-secret
                      key: NEW_RELIC_LICENSE_KEY
                - name: NEW_RELIC_APP_NAME
                  value: "my-production-api"
                - name: NEW_RELIC_ENVIRONMENT
                  value: "production"

    이 단계에서 제가 느낀 건 하나였습니다. 성능 모니터링 도입 자체는 생각보다 어렵지 않은데, 이름 규칙과 태깅 전략을 대충 잡으면 나중에 대시보드가 금방 지저분해진다는 점입니다. 서비스명, 환경명, 팀 구분 태그는 초반에 깔끔하게 맞춰두는 게 좋습니다.

    컨테이너 기반 서비스에 New Relic 환경 변수와 에이전트가 연결되는 흐름을 보여주는 구성 다이어그램입니다.

    4. 실제로 가장 많이 본 화면: 성능 모니터링의 3대 지표

    New Relic을 1년 정도 쓰면서 가장 자주 본 건 화려한 종합 대시보드가 아니라, 결국 느린 트랜잭션 상세 화면이었습니다. 장애나 성능 저하가 오면 순서는 거의 비슷했습니다.

    1. 응답 시간이 튄 시간대를 확인합니다.
    2. 어떤 엔드포인트가 느렸는지 봅니다.
    3. DB(Database, 데이터베이스) 구간인지, 외부 API 호출인지 나눕니다.
    4. 필요하면 로그와 애플리케이션 코드로 내려갑니다.

    예를 들어 API 평균 응답 시간이 갑자기 늘었는데 CPU는 멀쩡한 경우가 있었거든요. 처음엔 애플리케이션 내부 로직 문제인 줄 알았습니다. 근데 실제로 써보니까 외부 결제 API 호출 구간이 길어지고 있더라고요. 만약 APM이 없었다면 앱 서버 스케일 아웃부터 했을 수도 있습니다. 그런 의미에서 New Relic은 문제 해결의 방향을 틀리지 않게 해주는 장치였습니다.

    이 부분이 바로 제가 말하고 싶은 New Relic 사용 후기의 핵심입니다. 대시보드가 예쁜 것보다, 장애 대응에서 잘못된 가설을 빨리 버리게 해주는 게 훨씬 중요합니다.

    5. ⚠️ 1년 쓰면서 겪은 성능 모니터링의 실제 어려움

    좋은 얘기만 하면 블로그 글이 너무 광고 같잖아요. 실제 운영에서는 분명히 아쉬운 점도 있었습니다. 저도 삽질 좀 했습니다 ㅎㅎ

    5-1. 데이터가 많아지면 처음엔 어디를 봐야 할지 헷갈립니다

    메뉴가 다양하고 기능도 넓어서, 초반에는 오히려 “뭘 봐야 하지?” 상태가 옵니다. 이건 도구의 문제라기보다 온보딩 문제에 가깝더라고요.

    해결법: 처음부터 모든 기능을 다 보지 말고 아래 4가지만 먼저 익히는 게 좋았습니다.

    • APM 서비스 목록
    • 트랜잭션 응답 시간
    • 에러율
    • 알림 조건(Alert Condition)

    5-2. 에이전트만 붙였다고 끝이 아닙니다

    간혹 “붙였는데 데이터가 생각보다 의미가 없다”는 경우가 있습니다. 이유는 보통 서비스명 혼선, 환경 태그 누락, 로그와 추적의 연결 부족이더라고요.

    kubectl get pods -n production
    kubectl describe deployment my-api -n production
    kubectl logs deploy/my-api -n production --tail=200

    이런 기본 점검을 통해 환경 변수가 빠졌는지, 배포가 꼬였는지 먼저 확인했습니다. 은근히 단순한 실수가 많습니다.

    5-3. 경고 임계치(alert threshold)를 너무 예민하게 잡으면 피로해집니다

    이건 정말 많이 겪었습니다. 처음엔 에러율이나 응답 시간을 민감하게 잡아야 잘 잡히는 줄 알았거든요. 근데 새벽에 의미 없는 알림이 반복되면 팀이 결국 무시하게 됩니다. 그러면 진짜 장애 때도 놓치게 되죠.

    해결법: 비즈니스 영향이 있는 지표부터 우선순위를 줬습니다. 예를 들면 핵심 API 응답 시간, 결제 실패율, 로그인 오류율처럼요.

    5-4. 로그만 믿던 습관을 버리는 데 시간이 걸렸습니다

    이건 도구보다 사람의 습관 문제였어요. 저도 처음엔 장애 나면 바로 로그부터 열었는데, 나중엔 APM에서 느린 구간을 먼저 확인한 뒤 로그를 보는 방식이 훨씬 빨랐습니다. 혹시 지금도 로그 검색부터 시작하고 계신다면, 이 순서 한번 바꿔보세요. 생각보다 체감이 큽니다.

    New Relic 사용 후기에서 다루는 응답 시간과 에러율 분석 대시보드 이미지

    트랜잭션 분석 화면에서 병목 구간과 에러 추세를 확인하는 모습을 시각화한 이미지입니다.

    6. 검증: 1년 New Relic 운영으로 실제 어떻게 바뀌었나

    수치를 지어내서 말하고 싶지는 않습니다. 다만 운영 체감은 분명했습니다. 가장 큰 변화는 문제 확인까지 걸리는 시간이 줄었다는 점이었습니다. 예전에는 느리다는 제보가 오면 서버 지표, 로드밸런서, 로그, DB를 순서 없이 왔다 갔다 했는데요. New Relic을 붙인 뒤에는 병목 후보를 먼저 좁힌 뒤 확인하는 흐름이 자리 잡았습니다.

    • 장애 대응 회의에서 추측성 발언이 줄었습니다.
    • 개발팀과 운영팀이 같은 화면을 보고 이야기하게 됐습니다.
    • “서버는 안 바쁜데 왜 느리지?” 같은 상황에서 방향을 빨리 잡았습니다.
    • 릴리스 직후 성능 변화도 이전보다 빨리 감지했습니다.

    특히 애플리케이션 성능 관리가 필요한 팀이라면, 배포 직후의 미묘한 성능 저하를 잡는 데 도움이 됩니다. 장애가 아니라서 그냥 지나칠 수 있는 수준의 지연도 추세로 보면 보이거든요. 이건 나중에 큰 장애를 막는 데 꽤 중요했습니다.

    # 배포 직후 기본 확인 루틴 예시
    kubectl rollout status deployment/my-api -n production
    kubectl top pods -n production
    kubectl logs deploy/my-api -n production --since=10m

    그리고 New Relic 화면에서 같은 시간대의 응답 시간과 에러율을 같이 보면, 배포 영향인지 외부 의존성 문제인지 감이 빨리 옵니다. 드디어 됐다! 싶은 순간이 이런 데서 나오더라고요.

    7. New Relic 도입이 잘 맞는 경우와 기대를 버려야 할 부분

    모든 도구가 그렇듯 New Relic도 만능은 아닙니다. 그래서 기대치를 현실적으로 잡는 게 중요합니다.

    잘 맞는 경우 주의할 점
    여러 서비스가 연결된 구조 단일 정적 서비스만 운영하면 체감이 작을 수 있음
    개발팀과 운영팀이 함께 장애 대응 팀 내 공통 해석 기준이 없으면 화면만 늘어남
    배포 후 성능 영향 확인이 중요 알림 설계를 대충 하면 노이즈가 많아짐
    DB, 외부 API 병목이 자주 발생 에이전트 연결만으로 모든 원인이 자동 설명되진 않음

    즉, New Relic 사용 후기를 한 줄로 요약하면 이렇습니다. 문제를 자동으로 해결해주진 않지만, 문제를 훨씬 빨리 이해하게 해줍니다. 이 차이가 운영에서는 꽤 큽니다.

    New Relic 사용 후기 기반의 도입 전후 문제 해결 흐름 비교 인포그래픽

    도입 전후의 장애 대응 방식, 협업 흐름, 분석 속도 차이를 한눈에 보여주는 비교 이미지입니다.

    8. 정리: APM과 성능 모니터링의 현실적 결론

    1년 정도 운영하면서 느낀 결론은 분명합니다. 성능 모니터링은 단순히 그래프를 쌓는 작업이 아니라, 장애 대응의 사고방식을 바꾸는 작업이었습니다. 저도 처음엔 기능이 많아서 약간 부담스러웠는데, 실제로 써보니까 핵심은 복잡하지 않았습니다. 중요한 서비스부터 작게 시작하고, 알림을 다듬고, 팀이 같은 지표를 보게 만드는 것. 결국 이 세 가지가 제일 중요하더라고요.

    혹시 지금 APM 도입을 고민 중이시라면, 처음부터 거창하게 가지 마세요. 가장 느리다고 의심되는 API 하나, 에러가 자주 나는 서비스 하나부터 붙여보시면 됩니다. 그리고 인프라 지표와 애플리케이션 지표를 분리해서 보지 말고 함께 보셔야 합니다. 그 순간부터 운영이 꽤 달라집니다.

    다음 글에서는 로그(Log, 애플리케이션 기록)와 트레이스(Trace, 요청 흐름 추적)를 같이 볼 때 어떤 식으로 운영 효율이 올라가는지 정리해보려고 합니다. 이전 글에서 다뤘던 Kubernetes 리소스 관찰 전략과도 연결되는 내용이라, 같이 보시면 더 이해가 잘 되실 겁니다.

    자주 묻는 질문

    • Q. New Relic은 인프라 모니터링만으로 대체 가능한가요?
      A. 어렵습니다. 인프라 지표는 서버 상태를, APM은 요청 흐름과 병목 지점을 보여주기 때문에 역할이 다릅니다.
    • Q. 성능 모니터링 도입 초반에 가장 중요한 설정은 뭔가요?
      A. 서비스 이름 규칙, 환경 구분, 핵심 알림 조건입니다. 이 세 가지가 엉키면 나중에 데이터 해석이 힘들어집니다.
    • Q. 개발팀 없이 운영팀만 써도 효과가 있나요?
      A. 있습니다. 다만 코드 레벨 원인 분석까지 빨리 가려면 개발팀과 같은 화면을 보며 협업하는 편이 훨씬 좋습니다.
  • [모니터링] Grafana Cloud 비용 분석, 자체 Prometheus로 돌아간 이유

    [모니터링] Grafana Cloud 비용 분석, 자체 Prometheus로 돌아간 이유

    [모니터링] Grafana Cloud 비용 분석, 자체 Prometheus로 돌아간 이유

    Grafana Cloud 비용이 예전보다 훨씬 민감하게 느껴지기 시작하면, 그때부터는 단순히 ‘관리형이라 편하다’만으로 설명이 안 되더라고요. 저도 홈랩과 소규모 운영 환경에서 Grafana Cloud를 꽤 편하게 썼었는데, 메트릭(Metrics, 시계열 지표)과 로그(Logs, 시스템/애플리케이션 기록)가 늘어나면서 청구 구조를 다시 뜯어보게 됐습니다. 특히 Grafana Cloud 비용은 사용량이 조금만 늘어도 체감상 훅 올라갈 수 있어서, 모니터링 비용을 어디까지 SaaS로 맡길지 진지하게 다시 보게 되거든요.

    이번 글은 ‘무조건 자체 호스팅이 낫다’는 이야기가 아닙니다. Grafana Cloud 공식 가격 구조를 기준으로 왜 비용이 커지는지, 그리고 어떤 조건에서 자체 호스팅 ROI(Return on Investment, 투자 대비 효과)가 맞아떨어지는지 제가 정리한 기준을 공유해보려는 글입니다. 중간에 실제로 바로 올려볼 수 있는 Prometheus(프로메테우스, 메트릭 수집기) + Grafana(그라파나, 시각화 도구) 구성 예시도 넣어둘게요.

    Grafana Cloud와 자체 Prometheus 구성을 비용 관점에서 비교한 전체 아키텍처 예시입니다.

    1. 왜 Grafana Cloud 비용이 갑자기 아프게 느껴질까

    처음엔 진짜 편합니다. 가입하고, 에이전트 붙이고, 대시보드 몇 개 열면 끝이거든요. 저도 처음엔 이게 뭔가 싶을 정도로 진입 장벽이 낮아서 아주 만족했었습니다. 근데 여기서 중요한 포인트가 있습니다. 편한 구조일수록 과금 단위가 눈에 잘 안 들어온다는 점입니다.

    Grafana 공식 가격 페이지를 보면, Pro 플랜은 월 플랫폼 요금(platform fee)에 더해 사용량 기반 과금이 붙습니다. 메트릭은 active series(액티브 시리즈, 실제로 수집 중인 시계열 개수) 기준이고, 로그와 트레이스는 process / write / retain처럼 단계별 과금 구조가 걸려 있더라고요. 쉽게 말해, 데이터를 많이 넣는 환경에서는 ‘조금 늘었네’라고 생각했는데 청구서는 전혀 조금이 아닐 수 있다는 뜻입니다.

    • 메트릭: active series가 늘면 비용이 바로 반응합니다.
    • 로그: 수집량뿐 아니라 처리와 저장이 분리돼 보여서 체감이 더 큽니다.
    • 트레이스: 장애 분석엔 좋지만, 운영 습관 없이 켜두면 금방 비싸집니다.
    • 보존 기간(retention, 데이터 보관 기간): 길어질수록 관리형 서비스의 장점은 커지지만 비용도 같이 올라갑니다.

    이런 구조 때문에 클라우드 가격이 ‘갑자기 2배 오른 느낌’으로 다가오는 경우가 많습니다. 꼭 단가만 오른 게 아니라, 내가 보내는 데이터 패턴과 과금 기준이 맞물리면서 총액이 급격히 커지는 거죠. 실제로 써보니까, 비용 문제는 제품 성능보다 카디널리티(cardinality, 라벨 조합 폭발) 관리 실패에서 먼저 터지는 경우가 많더라고요.

    2. Grafana Cloud 가격 구조를 쉽게 풀어보면

    공식 가격 페이지 기준으로 현재 눈에 띄는 포인트는 이렇습니다. 글을 읽는 시점에 바뀔 수 있으니, 실제 계약 전에 공식 페이지는 꼭 다시 확인하셔야 합니다.

    항목 공식 페이지 기준 포인트 운영자가 봐야 할 부분
    Metrics Free는 월 10k active series, 14일 보관 라벨 설계가 나쁘면 금방 초과합니다.
    Metrics Pro 월 플랫폼 요금 + 사용량 기반 가격 노드 수보다 series 폭증이 더 위험합니다.
    Logs Free 월 50GB, 14일 보관 테스트 환경은 괜찮지만 운영 로그는 빠듯할 수 있습니다.
    Logs Pro process, write, retain 기반 사용량 과금 수집, 저장, 보관을 따로 봐야 합니다.
    Retention Pro 기준 metrics 13개월, logs/traces 30일 장기 보관이 필요한 조직엔 장점이 큽니다.

    쉽게 말해 이렇습니다. Grafana Cloud 비용은 서버 대수보다도 ‘얼마나 많은 시계열과 데이터를 계속 보내고 있는가’에 더 민감합니다. VM 몇 대 수준에서는 괜찮아 보여도, 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션) 들어가고 라벨이 늘고, 로그를 구조화해서 많이 쌓기 시작하면 얘기가 달라집니다.

    그래서 저는 비용 분석할 때 아래 세 가지부터 봅니다.

    1. active series가 왜 늘고 있는지
    2. scrape interval(스크랩 주기, 수집 간격)이 과한지
    3. 꼭 SaaS에 남겨야 하는 데이터와 로컬에 둬도 되는 데이터를 구분했는지

    3. 자체 호스팅 ROI, 저는 이렇게 계산합니다

    여기서 많이들 실수하는 게 있습니다. 자체 Prometheus로 돌린다고 해서 무조건 싸지 않거든요. 저도 처음엔 ‘VM 하나면 끝 아닌가?’ 싶었는데, 막상 해보면 백업, 디스크, 알람, 업그레이드, 장애 대응까지 다 내 일이 됩니다. 삽질 좀 했습니다 ㅎㅎ

    그래도 자체 호스팅 ROI가 좋아지는 구간은 분명히 있습니다.

    • 메트릭은 많은데 장기 보관이 꼭 필요하지 않을 때
    • 운영 인력이 있어 기본 유지보수를 감당할 수 있을 때
    • 로그와 트레이스를 전부 관리형으로 둘 필요가 없을 때
    • 홈랩, 사내 관제, 내부 서비스처럼 외부 SLA보다 비용 통제가 더 중요할 때

    반대로 아래 조건이면 Grafana Cloud가 여전히 훨씬 편할 수 있습니다.

    • 24×7 대응과 장기 보관이 중요할 때
    • 멀티 리전, 팀 협업, 권한 관리가 이미 복잡할 때
    • 장애 나면 관제 스택을 직접 고칠 사람이 없을 때

    제가 실제로는 이렇게 따집니다. 월 청구서 증가분과 직접 운영할 때 들어가는 VM/디스크/백업 비용 + 관리 시간을 같이 봅니다. 숫자를 억지로 예쁘게 만들 필요는 없고요. 한 달에 두세 번만 대시보드 보고, 보관 기간도 15일~30일이면, 자체 Prometheus 쪽이 생각보다 금방 수지가 맞는 경우가 있습니다.

    4. 그래서 저는 Prometheus + Grafana 조합으로 다시 갔습니다

    이번 선택의 핵심은 단순했습니다. 메트릭은 자체 호스팅, 필요하면 나중에 일부만 외부로 보낸다. Prometheus는 구조가 명확하고, Grafana는 익숙하고, 둘 다 문서가 탄탄합니다. 무엇보다 제가 어디서 비용이 나는지 눈으로 볼 수 있더라고요.

    Prometheus 공식 문서를 보면 로컬 저장소(local storage)는 기본적으로 15일 retention입니다. 그리고 저장소 크기를 제어하려면 --storage.tsdb.retention.time 또는 --storage.tsdb.retention.size를 잡을 수 있습니다. 또 중요한 경고가 하나 있는데, NFS 같은 비POSIX 환경은 권장되지 않습니다. 이거 모르고 네트워크 스토리지에 올렸다가 애매한 문제 겪는 경우 진짜 많습니다.

    Grafana Cloud 비용 절감을 위해 구성한 자체 Prometheus 홈랩 다이어그램

    Docker Compose 기반으로 Prometheus와 Grafana를 엮은 홈랩 구성 예시입니다.

    5. 실전 구현: Docker Compose로 10분 안에 올리기

    복잡하게 시작할 필요 없습니다. 가장 단순하고 관리하기 쉬운 형태로 먼저 띄우는 게 좋습니다. 아래 예시는 Prometheus와 Grafana를 같은 Docker Compose 네트워크에 올리는 방식입니다.

    5-1. 디렉터리 구조

    monitoring/
    ├── docker-compose.yaml
    ├── prometheus/
    │   └── prometheus.yml
    └── grafana/
        └── provisioning/
            └── datasources/
                └── prometheus.yaml

    5-2. docker-compose.yaml

    services:
      prometheus:
        image: prom/prometheus:latest
        container_name: prometheus
        restart: unless-stopped
        command:
          - --config.file=/etc/prometheus/prometheus.yml
          - --storage.tsdb.path=/prometheus
          - --storage.tsdb.retention.time=15d
          - --storage.tsdb.retention.size=20GB
          - --web.enable-lifecycle
        ports:
          - "9090:9090"
        volumes:
          - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
          - prometheus-data:/prometheus
    
      grafana:
        image: grafana/grafana:latest
        container_name: grafana
        restart: unless-stopped
        ports:
          - "3000:3000"
        volumes:
          - grafana-data:/var/lib/grafana
          - ./grafana/provisioning:/etc/grafana/provisioning:ro
        depends_on:
          - prometheus
    
    volumes:
      prometheus-data:
      grafana-data:

    여기서 중요한 포인트는 두 가지입니다. 첫째, retention.time과 retention.size를 둘 다 넣어 디스크 폭주를 막는 것. 둘째, 영속 볼륨(persistent volume)을 꼭 쓰는 것입니다. 안 그러면 컨테이너 다시 뜰 때 데이터가 날아가거든요.

    5-3. prometheus.yml

    global:
      scrape_interval: 15s
      evaluation_interval: 15s
    
    scrape_configs:
      - job_name: prometheus
        static_configs:
          - targets:
              - prometheus:9090

    Prometheus 공식 시작 가이드의 가장 기본적인 예시는 자기 자신을 스크랩하는 구성입니다. 저는 처음에 딱 여기서 시작하시는 걸 추천합니다. 이것만으로도 Prometheus가 정상 수집 중인지 바로 검증할 수 있거든요.

    5-4. Grafana 데이터소스 프로비저닝

    apiVersion: 1
    
    datasources:
      - name: Prometheus
        type: prometheus
        access: proxy
        url: http://prometheus:9090
        isDefault: true
        jsonData:
          httpMethod: POST

    Grafana 공식 문서에도 나오지만, 같은 Compose 네트워크라면 http://prometheus:9090처럼 서비스 이름으로 붙이는 방식이 제일 깔끔합니다. 많은 분들이 여기서 localhost:9090 넣고 왜 안 되지? 하는데, 컨테이너 안에서 localhost는 자기 자신이거든요. 저도 처음엔 여기서 한 번 헷갈렸습니다.

    5-5. 실행

    cd monitoring
    docker compose up -d
    
    docker compose ps
    curl http://localhost:9090/-/ready
    curl http://localhost:3000/api/health

    Prometheus는 http://localhost:9090, Grafana는 http://localhost:3000으로 들어가면 됩니다. Grafana 데이터소스가 자동으로 붙어 있으면 거의 다 된 겁니다. 드디어 됐다! 하는 순간이 여기서 오더라고요.

    Grafana Cloud 비용 대안으로 자체 Prometheus를 연결한 Grafana 설정 이미지

    Grafana에서 Prometheus 데이터소스가 정상 연결된 상태를 보여주는 설정 화면 예시입니다.

    6. 운영하면서 꼭 챙겨야 하는 비용 절감 포인트

    자체 호스팅으로 돌아왔다고 모니터링 비용 문제가 자동 해결되진 않습니다. 그냥 청구서 대신 디스크와 리소스가 문제를 말해줄 뿐이죠. 그래서 아래 기준은 꼭 잡고 가는 편이 좋습니다.

    1. scrape interval을 무작정 5초로 두지 않습니다. 15초나 30초면 충분한 경우가 많습니다.
    2. high cardinality 라벨을 줄입니다. 요청 ID, 세션 ID 같은 값은 거의 독입니다.
    3. 장기 보관이 필요하면 Prometheus 단독보다 Thanos, Mimir 같은 별도 구조를 검토합니다.
    4. 메트릭과 로그를 분리해서 생각합니다. 둘을 한 번에 SaaS에서 빼면 운영 복잡도가 급격히 올라갈 수 있습니다.

    특히 모니터링 비용은 수집량보다도 라벨 설계에서 무너지는 경우가 많습니다. 예를 들어 pod 이름, request path, 사용자별 식별자가 섞이면 active series가 순식간에 불어납니다. 클라우드 가격이 비싸다고 느껴질 때는 서비스 갈아타기 전에 먼저 series 폭증 원인을 보는 게 맞습니다. 이게 정말 중요한 포인트입니다!

    7. ⚠️ 실제로 많이 겪는 트러블슈팅

    • Grafana에서 데이터가 안 보임
      원인: 데이터소스 URL을 localhost:9090으로 넣은 경우가 많습니다. 컨테이너 분리 환경이면 서비스명이나 내부 네트워크 주소를 써야 합니다.
    • 디스크가 생각보다 빨리 참
      원인: retention은 시간만 잡고 size 제한을 안 둔 경우가 많습니다. --storage.tsdb.retention.size를 같이 설정하세요.
    • Prometheus 재시작 후 데이터가 날아감
      원인: 볼륨 마운트가 빠졌을 가능성이 큽니다. 특히 테스트하다가 귀찮아서 빼면 꼭 나중에 후회합니다.
    • NFS에 올렸더니 이상하게 불안정함
      원인: Prometheus 공식 문서에서도 로컬 파일시스템 사용을 강하게 권장합니다. 네트워크 파일시스템은 피하는 편이 안전합니다.

    저는 한때 백업 편하겠다고 네트워크 스토리지를 먼저 떠올렸었는데, 이건 운영 편의보다 안정성이 우선이더라고요. 결국은 로컬 디스크 + 스냅샷/백업 전략으로 다시 정리했습니다.

    8. 검증 결과: 무엇이 좋아졌고 무엇을 잃었나

    직접 운영으로 바꾸고 나면 제일 먼저 느끼는 건 비용 예측 가능성입니다. Grafana Cloud 비용처럼 사용량 급등에 따라 청구서가 튀는 대신, 이제는 VM 크기와 디스크 크기, retention 정책으로 상한선을 제가 결정할 수 있습니다. 이거 진짜 편하더라고요. 적어도 월말에 놀랄 일은 줄어듭니다.

    반대로 잃는 것도 분명합니다.

    • 관리형 서비스의 즉시성
    • 긴 retention과 팀 기능의 편의성
    • 장애 시 벤더에 기대는 심리적 안정감

    그래서 결론은 단순합니다. 메트릭 위주의 소규모~중간 규모 환경이라면 자체 Prometheus는 충분히 매력적입니다. 반면 로그, 트레이스, 장기 보관, 멀티팀 협업이 핵심이면 Grafana Cloud가 여전히 좋은 선택일 수 있습니다. 결국 핵심은 도구 종교가 아니라 ROI와 운영 책임 범위입니다.

    Grafana Cloud 비용 분석 후 자체 Prometheus 전환 결과 대시보드 이미지

    자체 Prometheus 전환 후 Grafana 대시보드와 리소스 사용량을 함께 확인하는 결과 예시입니다.

    9. 정리와 FAQ

    자주 묻는 질문

    • Q. Grafana Cloud를 완전히 버려야 하나요?
      아닙니다. 메트릭만 자체 호스팅하고, 로그나 외부 공유 대시보드는 관리형을 유지하는 혼합형도 충분히 좋습니다.
    • Q. 자체 호스팅 ROI는 언제 좋아지나요?
      청구서 증가 속도보다 VM/스토리지/운영 시간 증가 속도가 느릴 때입니다. 특히 retention이 짧고 운영 범위가 작을수록 유리합니다.
    • Q. 다음 단계는 뭘 보면 좋을까요?
      다음 글에서는 Node Exporter, Alertmanager, 그리고 카디널리티 줄이는 실전 팁을 정리해볼 예정입니다. 이전 글에서 다뤘던 홈랩 스토리지 설계와도 연결해서 보시면 더 이해가 쉬우실 겁니다.

    정리하면 이렇습니다. Grafana Cloud 비용이 부담스럽다고 느껴지는 순간이 오면, 무조건 해지부터 할 필요는 없습니다. 먼저 내 사용량 구조를 보고, 어떤 데이터가 비용을 끌어올리는지 파악한 뒤, 메트릭부터 단계적으로 분리해보세요. 저처럼 한 번 직접 돌려보면, 어떤 환경에서 자체 호스팅이 맞는지 감이 꽤 선명해집니다. 처음엔 헷갈려도, 막상 해보면 생각보다 어렵지 않습니다.

    Grafana Cloud 비용과 자체 호스팅 ROI를 비교한 인포그래픽

    Grafana Cloud와 자체 호스팅 ROI를 한눈에 정리한 비교 인포그래픽 예시입니다.

    참고한 공식 문서

  • [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 마이크로서비스 아키텍처(MSA)를 운영하시는 분들이라면 한 번쯤은 머리 싸매고 고민했을 주제, 바로 분산 추적(Distributed Tracing)에 대해 이야기해보려고 해요. 특히 Kubernetes(쿠버네티스) 환경에서 OpenTelemetry(오픈텔레메트리)를 활용해서 어떻게 이 복잡한 문제를 풀어갈 수 있었는지, 제가 직접 삽질했던 경험들을 솔직하게 공유해볼게요.

    요즘 애플리케이션들은 웬만하면 다 마이크로서비스 형태로 쪼개져 있잖아요? 서비스 간 호출이 꼬리에 꼬리를 물고 이어지다 보니, ‘도대체 이 에러가 어디서부터 시작된 거야?’ 하고 원인을 찾기 시작하면 정말 막막할 때가 많더라고요. 저도 처음엔 로그만 가지고 씨름했는데, 시간이 갈수록 이건 아니다 싶었어요. 그래서 자연스럽게 분산 추적에 관심을 가지게 되었고, 홈랩에서 이것저것 실험하다가 OpenTelemetry라는 보석 같은 녀석을 만나게 되었죠. 🎉

    이 글을 통해 여러분도 저처럼 서비스 트러블슈팅 시간을 확 줄이고, 시스템 전체를 한눈에 파악하는 데 큰 도움을 받으실 수 있을 거예요. 자, 그럼 함께 시작해볼까요?

    OpenTelemetry와 Kubernetes 환경에서 분산 추적이 어떻게 작동하는지 보여주는 전체 아키텍처 다이어그램입니다.

    OpenTelemetry, 분산 추적, 그리고 Observability: 개념 정리

    본격적인 설정에 들어가기 전에, 몇 가지 핵심 개념을 짚고 넘어가야겠죠? 제가 처음 이 분야를 접했을 때 가장 헷갈렸던 부분들이거든요.

    • Observability (옵저버빌리티, 관측 가능성): 시스템 내부 상태를 외부에서 추론할 수 있는 능력이에요. 단순히 ‘이상이 있다/없다’를 넘어 ‘왜 이상이 발생했는지’까지 파악할 수 있는 거죠. 보통 Logs(로그), Metrics(메트릭), Traces(추적) 세 가지 기둥으로 구성돼요.
    • Distributed Tracing (분산 추적): 마이크로서비스 아키텍처에서 사용자 요청이 여러 서비스를 거쳐 처리될 때, 그 전체 흐름을 따라가며 추적하는 기술입니다. 각 서비스 간 호출이 하나의 Trace(트레이스, 추적)로 묶이고, 그 안에서 각 작업 단위는 Span(스팬, 구간)으로 표현돼요. 쉽게 말해, 요청의 여권을 만들어서 각 서비스마다 도장을 찍고 다니게 하는 거라고 보면 돼요.
    • OpenTelemetry (오픈텔레메트리): CNCF(Cloud Native Computing Foundation)에서 주도하는 오픈소스 프로젝트예요. 벤더에 종속되지 않고, 모든 Observability 데이터를 수집하고 내보내는 표준화된 방법을 제공합니다. 애플리케이션 코드에 SDK(Software Development Kit)를 넣어 계측(Instrumentation)하고, OpenTelemetry Collector(컬렉터)를 통해 데이터를 수집하고 원하는 백엔드(Jaeger, Prometheus, Zipkin 등)로 전송할 수 있어요.

    처음엔 이 용어들이 다 비슷비슷하게 느껴졌는데, 결국 OpenTelemetry는 Observability를 구현하기 위한 도구고, 그중 분산 추적은 특히 마이크로서비스 환경에서 빛을 발하는 핵심 기능이라고 이해하면 편해요.

    실전 구현: K8s 환경에서 OpenTelemetry Collector 설정하기

    자, 이제 직접 Kubernetes 클러스터에 OpenTelemetry를 설정해볼 시간입니다. 저는 주로 Helm(헬름)을 사용해서 배포하는 편인데, 이게 관리하기도 편하고 설정도 직관적이더라고요.

    1. OpenTelemetry Collector 배포 전략 선택

    OpenTelemetry Collector는 데이터를 수집하고 처리해서 백엔드로 보내는 역할을 합니다. K8s 환경에서는 보통 두 가지 방식으로 배포할 수 있어요.

    1. Agent (DaemonSet) 모드: 각 노드에 하나씩 Collector를 배포해서, 해당 노드에서 실행되는 애플리케이션들의 트레이스/메트릭을 수집합니다. 노드별로 리소스를 분리할 수 있고 안정적이거든요. 저는 주로 이 방식을 선호해요.
    2. Gateway (Deployment) 모드: 클러스터 내부에 중앙 Collector를 배포해서 모든 트래픽을 한곳으로 모읍니다. 관리는 편하지만, 부하가 한곳에 집중될 수 있고 네트워크 경로가 복잡해질 수 있죠.

    이 글에서는 DaemonSet 모드로 Agent를 배포하는 방법을 기준으로 설명드릴게요. 각 노드에서 효율적으로 데이터를 수집하는 데 유리하거든요.

    2. Helm으로 OpenTelemetry Collector 배포

    먼저 OpenTelemetry Helm Chart를 추가하고 업데이트합니다.

    
    helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
    helm repo update
    

    다음으로, values.yaml 파일을 만들어서 Collector 설정을 커스터마이징해요. 여기서는 Jaeger(예거) 백엔드로 트레이스를 내보내는 설정을 해볼 거고, Jaeger는 분산 추적 데이터를 시각화하는 데 정말 유용한 도구예요.

    
    # otel-collector-values.yaml
    agent:
      mode: "daemonset"
      config:
        receivers:
          otlp:
            protocols:
              grpc:
              http:
        processors:
          batch:
        exporters:
          jaeger:
            endpoint: "jaeger-collector.monitoring.svc.cluster.local:14250" # Jaeger Collector 서비스 주소
            tls:
              insecure: true # 실제 프로덕션 환경에서는 TLS 설정 필요
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [batch]
              exporters: [jaeger]
    
    # Jaeger도 함께 배포한다고 가정합니다. (별도 Helm Chart 사용)
    # jaeger:
    #   agent:
    #     strategy: "daemonset"
    #   collector:
    #     ingress:
    #       enabled: true
    #       hosts:
    #         - jaeger.mydomain.com
    

    위 values.yaml 파일로 Helm을 이용해 Collector를 배포해요.

    
    helm install otel-collector open-telemetry/opentelemetry-collector -f otel-collector-values.yaml -n monitoring --create-namespace
    

    -n monitoring은 monitoring 네임스페이스에 배포하겠다는 의미예요. --create-namespace로 없으면 새로 만들어줍니다. 이 과정이 끝나면 각 노드에 OpenTelemetry Collector Agent가 실행될 거고요.

    Kubernetes 클러스터 내 OpenTelemetry Collector DaemonSet 배포 구성도

    Kubernetes 클러스터 내에서 OpenTelemetry Collector Agent가 DaemonSet으로 배포되어 각 노드의 애플리케이션 트레이스를 수집하는 구성도입니다.

    3. 애플리케이션 계측 (Instrumentation)

    Collector가 준비되었으니, 이제 애플리케이션에서 트레이스를 생성해서 Collector로 보내야 해요. OpenTelemetry는 다양한 언어별 SDK를 제공하거든요. 예를 들어 Python 애플리케이션이라면 다음과 같이 계측할 수 있어요.

    
    from opentelemetry import trace
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import SimpleSpanProcessor
    
    # 리소스 설정 (서비스 이름 등)
    resource = Resource.create({"service.name": "my-python-service"})
    
    # TracerProvider 생성
    tracer_provider = TracerProvider(resource=resource)
    trace.set_tracer_provider(tracer_provider)
    
    # OTLP 익스포터 설정 (Collector Agent의 주소)
    # Kubernetes 내부에서 서비스 이름으로 접근 가능합니다.
    # otel-collector는 'otel-collector-agent' 서비스로 노출됩니다.
    exporter = OTLPSpanExporter(endpoint="otel-collector-agent.monitoring.svc.cluster.local:4317")
    
    # SpanProcessor 등록
    tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
    
    # Tracer 가져오기
    tracer = trace.get_tracer(__name__)
    
    # 트레이스 생성 예시
    with tracer.start_as_current_span("my-operation") as span:
        span.set_attribute("http.method", "GET")
        span.set_attribute("http.route", "/data")
        # ... 여기에 실제 비즈니스 로직
        print("Doing some work...")
        with tracer.start_as_current_span("sub-operation"):
            print("Doing sub-work...")
    
    print("Trace sent!")
    

    otel-collector-agent.monitoring.svc.cluster.local:4317이 여기서 핵심인데요, 이는 Kubernetes 서비스 디스커버리를 통해 monitoring 네임스페이스의 otel-collector-agent 서비스(OpenTelemetry Collector Agent가 노출하는 gRPC 포트)로 트레이스를 보낸다는 의미예요.

    ⚠️ 삽질 & 트러블슈팅 경험

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질과 해결책을 공유해 드릴게요. 여러분은 저처럼 헤매지 마시길!

    1. Collector Agent 포드 상태 불량: kubectl get pods -n monitoring으로 확인했을 때 Collector Agent 포드가 CrashLoopBackOff 상태인 경우가 있었어요. kubectl logs <pod-name> -n monitoring으로 로그를 확인해보니, 설정 파일에 오타가 있거나 리소스 부족으로 인해 시작되지 못하는 경우가 많더라고요. 특히 config: 아래 들여쓰기(indentation) 오류가 잦았습니다.

    2. 네트워크 연결 문제: 애플리케이션에서 Collector로, Collector에서 Jaeger로 트레이스가 전송되지 않는 문제가 있었어요. 이럴 땐 다음을 확인해보세요.

      • Kubernetes Service Name: 애플리케이션에서 Collector로 트레이스를 보낼 때, otel-collector-agent.monitoring.svc.cluster.local:4317 주소가 올바른지 확인해야 합니다. 서비스 이름이 잘못되었거나 네임스페이스가 다르면 연결이 안 되죠.
      • Port: OTLP gRPC 기본 포트인 4317이 맞는지, 또는 OTLP HTTP 기본 포트인 4318이 맞는지 확인하세요.
      • Network Policy: 혹시 Kubernetes Network Policy(네트워크 정책)가 설정되어 있다면, 애플리케이션 파드에서 Collector 파드로의 4317/4318 포트 통신이 허용되어 있는지 확인해야 해요. 저도 이 정책 때문에 한참을 헤맸거든요.
      • Firewall: 클러스터 외부와 통신하는 경우, 방화벽 규칙도 확인해야 합니다.
    3. Jaeger UI에서 트레이스 안 보임: Collector는 잘 동작하는데 Jaeger UI에서 아무것도 안 보이는 경우가 있었어요. 이건 주로 Collector의 exporters 설정 문제이거나 Jaeger Collector 서비스 주소가 잘못된 경우였거든요. Jaeger Collector의 포트(gRPC는 14250, HTTP는 14268)가 맞는지, 서비스 이름이 정확한지 꼼꼼히 확인해야 해요. 💡

    이런 문제들을 해결할 때 가장 중요한 건 로그(Logs)를 꼼꼼히 확인하고, kubectl describe pod 명령어로 파드의 이벤트와 환경 변수를 살펴보는 거예요. 그리고 curl이나 telnet 같은 간단한 도구로 포트 연결 테스트를 해보는 것도 큰 도움이 됩니다.

    OpenTelemetry Collector 트러블슈팅 과정에서 로그를 분석하는 엔지니어의 모습

    OpenTelemetry Collector 설정 중 문제가 발생하여 로그와 Kubernetes 이벤트를 분석하며 트러블슈팅하는 인프라 엔지니어의 모습입니다.

    결과 확인 및 활용 사례

    모든 설정이 완료되고 애플리케이션에서 트레이스를 보내기 시작하면, 이제 Jaeger UI 같은 백엔드에서 멋진 시각화된 트레이스를 확인할 수 있어요. 저도 처음 성공했을 때 ‘드디어 됐다!’ 하고 외쳤던 기억이 나네요. 🎉

    Jaeger UI에 접속해서 서비스 이름을 선택하고 검색하면, 아래와 같이 요청의 전체 흐름을 시각적으로 볼 수 있습니다. 각 스팬이 어떤 서비스에서 얼마나 시간을 소모했는지, 어떤 에러가 발생했는지 한눈에 파악할 수 있어요.

    • 성능 병목 지점 파악: 특정 서비스나 함수 호출에서 유독 시간이 오래 걸린다면, 그 부분이 성능 병목 지점일 가능성이 높아요. 트레이스를 통해 정확히 어디서 시간이 지연되는지 찾아낼 수 있죠.
    • 에러 원인 분석: 에러가 발생한 스팬을 클릭하면 관련 로그나 태그(tags) 정보를 확인하여 문제의 근본 원인을 빠르게 파악할 수 있어요. 수많은 로그를 뒤지는 것보다 훨씬 효율적이죠.
    • 서비스 의존성 파악: 복잡한 마이크로서비스 간의 호출 관계를 시각적으로 이해하는 데 큰 도움이 돼요. 새로운 팀원이 합류했을 때 시스템 구조를 설명하는 자료로도 활용할 수 있거든요.
    Jaeger UI에서 분산 추적 데이터가 성공적으로 시각화된 대시보드 스크린샷

    Jaeger UI에서 애플리케이션의 분산 추적 데이터가 성공적으로 수집되어 시각화된 대시보드 스크린샷입니다. 서비스 간 호출 흐름과 각 스팬의 지연 시간을 보여줍니다.

    마무리하며: OpenTelemetry, 선택이 아닌 필수

    OpenTelemetry를 Kubernetes 환경에서 설정하고 활용하는 과정이 처음엔 다소 복잡하게 느껴질 수 있어요. 하지만 한 번 구축하고 나면 얻을 수 있는 이점은 정말 상상 이상이라고 생각합니다. 특히 복잡한 마이크로서비스 아키텍처에서는 분산 추적이 선택이 아닌 필수가 되고 있거든요.

    저도 13년 동안 수많은 인프라 환경을 운영하면서, 이렇게 시스템의 속을 들여다볼 수 있는 도구의 중요성을 절실히 느꼈어요. OpenTelemetry는 단순히 에러를 찾는 것을 넘어, 시스템의 건강 상태를 지속적으로 모니터링하고 최적화하는 데 큰 인사이트를 제공해줍니다.

    다음 글에서는 OpenTelemetry를 활용해서 메트릭(Metrics)과 로그(Logs)를 수집하고 시각화하는 방법에 대해 좀 더 깊이 있게 다뤄볼 예정이에요. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!

    OpenTelemetry가 제공하는 Observability의 세 가지 핵심 기둥인 Logs, Metrics, Traces를 시각적으로 요약한 인포그래픽입니다.