나는 쿠버네티스 모니터링 스택을 구축했고 — 부수는 것이 가장 좋은 부분이었다
Source: Dev.to
위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. 현재는 링크만 포함되어 있어 번역할 본문이 없습니다. 텍스트를 복사해서 알려 주시면 바로 번역해 드리겠습니다.
왜 이 프로젝트를 만들었는가
이 프로젝트를 이력서에 한 줄 추가하기 위해 만든 것이 아닙니다.
Prometheus와 Grafana에 대해 계속 읽으면서 내가 이해한 것처럼 고개를 끄덕였지만, 누군가가 “그럼 Prometheus가 실제로 당신의 pod를 어떻게 발견하나요?” 라고 물었을 때 멈춰버렸기 때문에 만들었습니다.
모르겠었습니다. 사실은요.
그래서 읽는 것을 멈추고 뭔가를 부수기 시작했습니다.
내가 만든 것
스크래치부터 만든 완전한 관측성 파이프라인:
- Python Flask 앱에 맞춤형 Prometheus 메트릭을 포함
- Kubernetes에 3개의 복제본으로 배포
- ServiceMonitor를 통해 Prometheus가 스크랩
- Grafana에서 PromQL 대시보드로 시각화
hey를 사용해 실제 트래픽으로 부하 테스트
레포지토리는 여기서 확인할 수 있습니다:
👉 github.com/adil-khan-723/k8s-observability-stack
하지만 코드 자체가 흥미로운 부분은 아닙니다. 실패를 보면서 배운 점은 다음과 같습니다.
실수 #1 – Grafana에서 원시 카운터 사용
무슨 일이 있었는가
패널에 http_requests_total을 추가했습니다. 숫자는 계속 올라가기만 했습니다: 1 000 → 5 000 → 23 000. 저는 “음… 괜찮은 걸까?” 라고 생각하며 바라봤습니다.
오직 증가만 하는 카운터는 자동차의 주행 거리계와 같습니다 – 현재 얼마나 빠르게 움직이고 있는지에 대한 정보를 전혀 알려주지 않습니다.
해결 방법
sum(rate(http_requests_total[1m]))
rate()는 지난 1분 동안 초당 요청 수를 계산하여 실제로 활용할 수 있는 값을 제공합니다. 이 쿼리로 전환한 뒤 로드 테스트 중 트래픽이 급증한 시점과 감소한 시점을 정확히 파악할 수 있었습니다.
교훈: 메트릭만으로는 무의미합니다. PromQL이 인사이트를 만들어냅니다.
실수 #2 – 레이트 윈도우가 너무 큼
무슨 일이 있었는가
rate(http_requests_total[8m]) 를 사용하면 그래프가 로드 테스트 중에도 의심스러울 정도로 부드럽게 보였습니다. 8분 윈도우는 모든 값을 평균화하기 때문에 30초 스파이크가 완전히 사라집니다.
해결 방법
sum(rate(http_requests_total[1m]))
이제 스파이크, 플래토, 그리고 감소가 모두 보입니다. 또한 스택형 그래프를 비활성화했는데, 이는 파드별 동작을 비교할 때 오해를 일으킬 수 있습니다.
교훈: 필요한 세분화 수준에 맞는 레이트 윈도우를 선택하세요.
실수 #3 – 모든 대상이 DOWN으로 보고됨
무슨 일이 일어났는가
부하 테스트 중에 Prometheus UI의 Status → Targets를 확인했더니 다음과 같은 메시지가 보였습니다:
context deadline exceeded
모든 세 개의 pod가 DOWN 상태였습니다. 처음에는 ServiceMonitor 설정을 탓했지만 라벨은 올바랐습니다. Prometheus는 pod를 발견할 수 있었지만, 제때 스크랩하지 못했습니다.
근본 원인
Flask 홈 라우트에 있는 time.sleep(2)가 Gunicorn 워커를 지연시켰습니다. Prometheus가 /metrics에 접근했을 때 워커가 종종 잠자는 중이어서 스크랩 타임아웃이 발생했습니다.
해결 방법
serviceMonitor.yaml에서 스크랩interval을 늘려 여유 시간을 확보합니다.- 인위적인 지연을 제거하거나 명시적으로 고려합니다.
세 개의 대상이 실시간으로 UP 상태로 전환되었습니다.
깊은 교훈: 모니터링 시스템은 애플리케이션 성능에 의존합니다. 느린 애플리케이션은 자체 가시성을 손상시킵니다. /metrics 엔드포인트에 비즈니스 로직을 두지 마세요.
실수 #4 – “Running”이 정상임을 가정하기
무슨 일이 있었나요
kubectl get pods는 모든 파드가 Running 상태임을 보여줬지만, 요청은 여전히 간헐적으로 실패했습니다. 나는 “Running”을 “모든 것이 정상이다”라고 생각하고 있었습니다. 그렇지 않습니다.
설명
Running은 컨테이너 프로세스가 시작되었음을 의미할 뿐입니다. 내부 애플리케이션이 트래픽을 제공할 준비가 되었는지는 전혀 알려주지 않습니다. 파드가 Running 상태이면서도 Flask 앱이 아직 초기화 중이거나 프로세스가 크래시되지 않은 손상된 상태일 수 있습니다.
해결 방법 – readinessProbe 추가:
readinessProbe:
httpGet:
path: /metrics
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2
Kubernetes는 이후 비정상적인 파드를 Service의 엔드포인트 목록에서 제거하므로 트래픽은 실제로 준비된 파드에만 전달됩니다. 간헐적인 실패가 사라졌습니다.
교훈: Running ≠ healthy. Readiness probes는 선택 사항이 아닙니다.
Surprise #1 – Load Isn’t Evenly Distributed
로드 테스트 중에 Grafana 패널을 분할하여 파드별 요청 비율을 표시했습니다:
rate(http_requests_total[1m])
세 개의 라인이 대략 동일하게 보이길 기대했지만, 한 파드가 지속적으로 더 많은 트래픽을 처리하고 있었습니다.
왜?
Kubernetes Service는 연결 수준에서 라운드‑로빈을 수행하며, 요청 수준에서는 그렇지 않습니다. 동시성이 높을 경우 일부 파드가 더 오래 지속되는 연결을 보유하게 되고, 그 결과 더 많은 요청을 받게 됩니다.
만약 sum(rate(http_requests_total[1m]))(전체 합계)만 보았다면 이 불균형을 전혀 발견하지 못했을 것입니다. 전체 합계는 완벽히 정상으로 보였지만, 파드별 뷰는 전혀 다른 이야기를 들려주었습니다.
교훈: 집계값은 문제를 숨깁니다. 파드별 메트릭이 필수적입니다.
Source: …
ServiceMonitor가 실제로 하는 일 (그리고 왜 혼란스러운가)
(원본 내용이 여기서 끊깁니다; 필요하면 직접 설명을 이어가세요.)
구축하기 전에 가장 혼란스러웠던 부분은 Prometheus와 Kubernetes 사이의 관계였습니다.
Prometheus는 pod를 직접 스크랩하지 않습니다. 대신 Service를 스크랩하고, 그 Service들을 ServiceMonitor라는 커스텀 리소스를 통해 발견합니다.
관계는 다음과 같습니다:
ServiceMonitor → Service 라벨과 매치 → Service가 Pod IP로 해석 → Prometheus가 각 pod를 스크랩
맞춰야 할 요소
이 과정이 정상적으로 동작하려면 다음 세 가지가 정확히 일치해야 합니다:
- ServiceMonitor selector – Service의 라벨과 일치해야 합니다.
- ServiceMonitor 네임스페이스 –
release: monitoring라벨(또는 사용 중인 Helm 릴리스 이름)이 있어야 합니다. - namespaceSelector – Service가 존재하는 네임스페이스를 가리켜야 합니다.
이 중 하나라도 잘못되면 Prometheus는 대상을 전혀 발견하지 못합니다. 오류가 표시되지 않을 뿐, 조용히 동작하지 않을 뿐입니다. 대부분의 사람들이 여기서 몇 시간을 디버깅하는 이유가 바로 이 때문입니다.
다음에 추가하고 싶은 내용
- Alertmanager – 스크랩 대상이 다운되거나 오류율이 급증할 때 알림을 전송합니다.
- HPA – 커스텀 Prometheus 메트릭을 기반으로 pod를 자동 스케일링합니다.
- Loki – 로그와 메트릭 이상 현상을 연관시킵니다.
- 고정된 의존성 버전 – 현재
requirements.txt에 버전이 명시되지 않아 재현 가능성에 위험이 있습니다.
마무리 생각
이 프로젝트에서 가장 가치 있었던 부분은 Prometheus나 Grafana 대시보드를 설정한 것이 아니라, context deadline exceeded 오류를 보고 왜 그런지 직접 파악한 순간이었습니다.
관측성을 배우는 방법은 책을 읽는 것이 아니라, 직접 시스템이 실패하는 모습을 보고 자신이 만든 도구로 진단하는 것입니다.
DevOps나 플랫폼 엔지니어링을 배우고 있다면, 무언가를 만들고, 부수고, 메트릭을 살펴보세요.
Repo:
Prometheus 스크랩과 관련해 비슷한 문제를 겪은 적이 있나요? 댓글로 알려 주세요—어떤 이상한 상황을 디버깅했는지 궁금합니다.
