Observability는 선택이 아니라 필수다. 하지만 SaaS 모니터링 비용이 인프라 비용보다 높아지는 순간이 온다. 그때가 자체 구축을 고민할 타이밍이다.
Datadog을 메인으로 사용하고 있었지만, 모든 환경에 적용하기엔 비용 부담이 있었다. Observability가 아예 없는 환경이 생기는 것보다는, 오픈소스로라도 갖추는 게 낫다고 판단했다. LGTM 스택(Loki, Grafana, Tempo, Mimir)을 EKS에 구축한 과정을 공유한다.
Datadog을 메인으로 사용하고 있지만, 모든 환경에 적용하기엔 비용 부담이 있었다. 특히 작은 규모의 계정들은 Observability가 아예 없는 상태로 운영되고 있었다.
JVM의 Cold Start는 순간 트래픽 상황에서 치명적이다. 스케일 아웃이 되어도 새 Pod가 준비되기까지 시간이 걸리면 이미 늦는다. GraalVM Native Image는 이 문제를 해결한다.
광고 푸시 후 순간 트래픽이 몰리는 서비스에 GraalVM Native 이미지를 적용했다. 개발자가 애플리케이션을 만들고, 나는 CI/CD 파이프라인 구축과 배포, 부하 테스트를 담당했다. 빌드 시간과 이미지 크기를 줄이면서 보안 규격까지 맞춘 과정을 공유한다.
이 서비스는 광고성 메시지를 대량으로 발송한 직후 트래픽이 급증한다. 사용자들이 링크를 클릭하면 반드시 이 서버를 거쳐야 하는 구조다.
Observability의 최종 목표는 MTTD(Mean Time To Detect)를 줄이는 것이다. 대시보드가 아무리 훌륭해도 24시간 지켜볼 수는 없다. Push 기반 알림이 있어야 문제를 빠르게 인지할 수 있다.
LGTM 스택에서 Mimir Ruler와 Alertmanager를 활용해 알림 시스템을 구축했다. 알림 룰을 GitOps로 관리하고, 알림 폭탄을 방지하기 위한 설계 포인트를 정리한다.
flowchart LR
subgraph GitOps[GitOps 관리]
Git[Git Repository]
CM[ConfigMap]
end
subgraph Mimir[Mimir]
Ruler[Ruler<br/>PromQL 평가]
Storage[(Metrics<br/>Storage)]
AM[Alertmanager<br/>라우팅/그룹핑]
end
subgraph Grafana[Grafana]
AlertUI[Alerting UI<br/>알림 현황 조회]
end
subgraph Slack[Slack]
Ch1[#alerts-service]
Ch2[#alerts-infra]
end
Git -->|Helm Deploy| CM
CM -->|Alert Rules| Ruler
Storage -->|메트릭 쿼리| Ruler
Ruler -->|알림 발생| AM
AM -->|namespace: app| Ch1
AM -->|namespace: observability| Ch2
AM -->|알림 상태| AlertUI
Ruler가 주기적으로 메트릭을 쿼리하고, 조건이 충족되면 Alertmanager로 알림을 보낸다. Alertmanager는 알림을 그룹핑하고, 라우팅 규칙에 따라 적절한 Slack 채널로 전송한다. Grafana에서는 현재 발생 중인 알림을 조회하고 히스토리를 확인할 수 있다.
Kubernetes에서 JVM 애플리케이션은 시작이 느리다. Pod가 뜨고 컨테이너가 Running 상태가 되어도, JVM이 준비되기까지는 시간이 더 걸린다. 이 간극을 무시하면 초기 요청이 실패한다.
Karpenter 노드 정리로 Pod가 재스케줄링될 때마다 초기 API 요청이 타임아웃으로 실패하는 문제가 있었다. ReadinessProbe만으로는 부족했다. StartupProbe와 전용 엔드포인트를 도입해서 해결한 과정을 공유한다.
새벽에 Karpenter가 비용 최적화를 위해 유휴 노드를 정리하면서 Pod가 다른 노드로 이동했다. Pod 재시작 자체는 정상이었지만, 재시작 직후 들어온 요청들이 실패했다.
Kubernetes Probe 3종류가 항상 헷갈린다. StartupProbe, ReadinessProbe, LivenessProbe. 왜 3개로 나눠져 있고, 언제 어떤 걸 써야 할까?
| Probe |
질문 |
실패 시 |
| StartupProbe |
“앱 시작 끝났어?” |
계속 대기 (다른 Probe 차단) |
| ReadinessProbe |
“트래픽 받을 수 있어?” |
Service에서 제외 (트래픽 차단) |
| LivenessProbe |
“죽은 거 아니야?” |
Pod 재시작 |
flowchart LR
subgraph Probes["Probe 역할"]
Startup[StartupProbe<br/>시작 완료?]
Readiness[ReadinessProbe<br/>트래픽 OK?]
Liveness[LivenessProbe<br/>살아있어?]
end
subgraph Actions["실패 시 동작"]
Wait[대기]
Remove[Service에서 제외]
Restart[Pod 재시작]
end
Startup -->|실패| Wait
Readiness -->|실패| Remove
Liveness -->|실패| Restart
style Startup fill:#74c0fc,color:#000
style Readiness fill:#69db7c,color:#000
style Liveness fill:#ef4444,color:#000
StartupProbe → 시작할 때만 (1회성)
ReadinessProbe → 트래픽 On/Off (반복)
LivenessProbe → 죽으면 재시작 (반복)
JVM 애플리케이션은 시작이 느리다. Spring Boot + DB 연결까지 1~2분 걸리기도 한다.
인프라에서 가장 위험한 컴포넌트는 “잘 돌아가고 있어서 아무도 신경 쓰지 않는 것"이다. ingress-nginx가 정확히 그랬다.
2025년 11월, Kubernetes 공식 블로그에서 ingress-nginx의 EOL을 발표했다. 유지보수자 1~2명(자원봉사)이 감당할 수 없는 기술 부채가 쌓였고, 후속 프로젝트(InGate)도 무산됐다. 2026년 3월부터 보안 패치, 버그 수정, 신규 K8s 버전 지원이 모두 중단된다. 클러스터에는 88개의 Ingress 리소스가 이 위에 올라가 있었다. 이 글은 “그래서 뭘로 바꿀 것인가"를 조사한 기록이다.
| 시점 |
상태 |
의미 |
| 2025-11 |
EOL 발표 |
Kubernetes 공식 블로그에서 retirement 공지 |
| 2026-02 |
Best-effort 기간 |
보안 패치 없음, 유지보수자 자발적 대응만 |
| 2026-03 |
Full EOL |
모든 지원 중단 — 보안 패치, 버그 수정, 신규 K8s 버전 지원 없음 |
| 이후 |
Repo Archived |
kubernetes-retired/로 이관 |
EOL 이후 기존 배포가 즉시 멈추지는 않는다. 하지만 보안 취약점이 발견되어도 패치가 나오지 않는다. 시한부 운영인 셈이다.
Liveness Probe는 죽은 Pod를 살리기 위한 것이다. 그런데 잘못 설정하면 멀쩡한 Pod를 죽이는 도구가 된다.
이론적으로는 알고 있었다. Kubernetes Probe 3종류에서도 “LivenessProbe는 단순하게"라고 정리했다. 그런데 실제로 겪고 나니 체감이 다르다. Karpenter 노드 교체와 무거운 Liveness Probe가 만나면서 연쇄 재시작이 발생했다. Datadog 메트릭으로 타임라인을 복원하고 원인을 분석한 과정을 공유한다.
아침에 5xx 알람이 터졌다. Spring Boot API 서버에서 약 15분간 간헐적 에러가 발생했다.
EKS 1.33이 릴리스됐다. 보통 마이너 버전 업그레이드는 “Control Plane 올리고, Addon 올리고, Node 올리면 끝"이라고 생각하기 쉽다. 나도 그렇게 생각했다.
dev 환경에서 먼저 작업하면서 예상치 못한 문제들을 만났다. VPC CNI 설정 불일치로 Pod IP 할당이 안 되고, Karpenter CRD 버전 문제로 노드 프로비저닝이 막히고, Datadog DD_HOSTNAME 누락으로 모니터링이 깨졌다.
dev에서 삽질한 덕분에 prod는 40분 만에 깔끔하게 끝냈다. 그 과정을 공유한다.
| 환경 |
노드 구성 |
특이사항 |
| dev |
MNG + Karpenter |
Self-managed Addons |
| prod |
MNG + Karpenter |
Self-managed Addons |
두 환경 모두 Self-managed Addon 방식이다. EKS managed addon이 아니라 직접 이미지 버전을 관리한다. 이게 업그레이드 시 추가 작업이 필요한 이유다.
Kubernetes에서 애플리케이션을 운영할 때, 결국 핵심은 Pod의 시작과 끝이다. 얼마나 따뜻하게 시작하고, 얼마나 우아하게 끝내느냐. Probe와 Warmup으로 시작을 제어하고, Graceful Shutdown으로 끝을 제어한다. 이 두 가지가 안정적인 서비스 운영의 기반이다.
Probe 설정은 이전 글에서 다뤘다. 이번에는 끝에 대한 이야기다. 배포할 때마다 502 에러가 발생하는 서비스가 있었다. 원인은 Pod 종료 전략의 부재였다. terminationGracePeriodSeconds와 preStop hook을 적용해서 해결한 과정을 공유한다.
Spring Boot API 서버를 배포할 때마다 간헐적으로 502 에러가 발생했다.
Karpenter는 비용 최적화 도구가 아니다. 노드 수명주기 관리 도구다. 비용 절감은 그 부산물이고, 본질은 “필요한 노드를 필요한 만큼, 최적의 상태로 유지하는 것"이다. 이 본질을 이해하지 못하면 Karpenter의 동작이 공격적으로 느껴지고, 사용자에게는 불안정한 환경으로 보인다.
beta 환경에서 QA 팀이 업무시간 중 잦은 Pod 재시작으로 불편을 호소했다. 원인을 추적하니 Karpenter의 비용 최적화 정책과 Spot 인스턴스, AMI 자동 업데이트가 복합적으로 작용하고 있었다. Karpenter의 Disruption 메커니즘을 이해하고, 비용과 안정성의 균형점을 찾은 과정을 공유한다.
Karpenter는 Kubernetes 노드의 프로비저닝과 수명주기를 자동으로 관리하는 오픈소스 프로젝트다. 원래 AWS에서 시작했지만, 현재는 kubernetes-sigs 산하의 클라우드 중립 Core와 클라우드별 Provider로 분리되어 있다. AWS(EKS)와 Azure(AKS)에서 공식 지원한다.