로그 몇 줄 남겼을 뿐인데, 왜 Kubernetes 노드의 디스크 Util은 95%가 될까?
Disk Util 95%. 노드 한 대에서만 그렇습니다.
CPU는 평온합니다. 메모리도 여유가 있습니다. 애플리케이션이 대용량 파일을 쓰는 것도 아닙니다. 그런데 특정 Worker Node의 Disk Util만 90%, 심하면 95%를 넘깁니다.
저도 이런 알림을 처음 받았을 때는 스토리지부터 의심했습니다. 디스크가 느린가. 누가 임시 파일을 쌓고 있나. 이미지 레이어가 안 지워지고 남아 있나.
계층을 따라 내려가다 보면 엉뚱한 곳에서 시작된 경우가 있었습니다.
로그였습니다.
로그는 문자열이 아닙니다. 로그도 I/O입니다.
개발자가 보는 로그와 운영체제가 보는 로그는 다르다
개발자에게 로그 한 줄은 이런 것입니다.
2026-08-14 10:21:32 INFO request completed
몇십 바이트짜리 문자열입니다. 그래서 이렇게 생각하기 쉽습니다.
"이 정도 문자열을 디스크에 쓰는 게 얼마나 부담된다고?"
저도 개발할 때는 그렇게 생각했습니다. 배포하면서 로그 레벨을 debug로 올려두고 되돌리는 걸 잊은 적도 있고요.
운영체제와 스토리지 쪽에서 보면 이야기가 달라집니다. 로그가 저장되려면 애플리케이션에서 출발한 데이터가 여러 계층을 통과합니다.
Application → stdout/stderr → Container Runtime → Log File → Filesystem → Block I/O → SSD/NVMe
그리고 Kubernetes 노드에는 애플리케이션 하나만 사는 게 아닙니다. 수십 개, 많으면 수백 개의 Pod가 동시에 로그를 뱉습니다. 여기에 kubelet, CRI-O나 containerd 같은 Container Runtime, 네트워크 컴포넌트와 각종 시스템 서비스도 계속 로그를 남깁니다.
문제는 여기서 시작됩니다.
Kubernetes의 로그는 어디로 갈까?
흔히 생기는 오해가 하나 있습니다.
"Kubernetes 로그는 전부 journald에 저장되는 것 아닌가?"
정확히는 그렇지 않습니다.
Kubernetes 공식 문서를 기준으로 보면, 일반적인 Linux 환경에서는 두 종류로 나눠서 보는 편이 낫습니다.
먼저 애플리케이션 컨테이너 로그. Pod 안의 애플리케이션이 stdout, stderr로 출력한 로그는 Container Runtime이 처리합니다. kubelet은 기본적으로 Runtime이 컨테이너 로그를 /var/log/pods 아래에 기록하도록 합니다.
Pod → stdout/stderr → Container Runtime → /var/log/pods
우리가 흔히 쓰는 kubectl logs도 이 로그를 kubelet을 통해 읽는 것입니다. 공식 문서에서도 kubelet이 컨테이너 로그 rotation과 디렉터리 구조를 관리한다고 설명합니다.
그러면 journald는 어디에 있을까
systemd 기반 Linux에서는 이야기가 하나 더 붙습니다. kubelet, containerd/CRI-O, systemd service 같은 호스트 레벨 컴포넌트는 journald에 로그를 기록합니다.
그래서 실제 Kubernetes 노드의 로그 구조는 파이프라인 하나가 아닙니다.
Kubernetes Node
┌─────────────────────┐
│ Application Pods │──→ stdout/stderr ──→ Container Runtime ──→ /var/log/pods
├─────────────────────┤
│ kubelet / Runtime │
│ system services │──→ journald ──→ Journal File
└─────────────────────┘
이 둘이 같은 노드의 디스크를 쓴다는 게 중요합니다.
그래서 로그가 많아지면 무슨 일이 생길까?
Worker Node 하나에 Pod 100개가 떠 있다고 해보겠습니다.
각 Pod가 계속 로그를 냅니다. kubelet도 상태 변화를 기록합니다. Container Runtime도 컨테이너 생성, 종료, 오류 같은 이벤트를 기록합니다. 노드에는 다른 시스템 로그도 있습니다.
물리적인 관점 하나로 모으면 이렇게 됩니다.
Pod A ─┐
Pod B ─┤
Pod C ─┤
... ├──→ Node Disk
Pod N ─┤
kubelet┤
CRI-O ─┤
systemd┘
각각은 별것 아닙니다. 합치면 이야기가 달라집니다. 로그는 작은 데이터를 아주 자주 기록하는 workload에 가깝기 때문입니다.
여기서 Throughput과 IOPS를 구분해야 한다
스토리지 문제를 볼 때 많은 사람이 용량과 전송량부터 봅니다.
"초당 몇 MB밖에 안 쓰는데 왜 디스크가 바쁘지?"
디스크 성능에는 최소한 세 가지 축이 있습니다. 얼마나 많은 데이터를 옮겼는지(Throughput), 몇 번의 I/O 작업이 발생했는지(IOPS), 각 I/O가 끝나는 데 얼마나 걸렸는지(Latency).
택배로 바꿔 보면 쉽습니다. Throughput은 하루에 몇 톤을 배달했느냐이고, IOPS는 택배기사가 몇 번이나 집을 방문했느냐입니다. 10톤짜리 화물을 한 번 옮기는 것과 작은 상자를 하나씩 1만 번 배달하는 것은 총량으로는 비슷해 보여도 하는 일이 전혀 다릅니다.
로그도 그렇습니다. 몇 GB짜리 파일 하나를 순차적으로 쓰는 것보다, 작은 로그를 매우 높은 빈도로 계속 기록하는 쪽이 스토리지 입장에서 더 까다롭습니다.
"Disk MB/s가 높지 않다"는 이유만으로 디스크 문제가 아니라고 판단하면, 저처럼 한참 헤맵니다.
journald는 log.txt가 아니다
journal을 log.txt 끝에 문자열 한 줄 덧붙이는 구조로 이해하면 곤란합니다. journald는 구조화된 binary journal을 관리합니다.
로그 하나가 저장되는 과정에는 journal 데이터 구조, 파일시스템, 파일시스템의 metadata와 journal, block device 같은 계층이 개입합니다. 애플리케이션에서 보는 "로그 100 Byte"와 storage device에서 실제로 발생하는 "물리적 Write 100 Byte"가 항상 1:1은 아니라는 뜻입니다.
넓게 보면 Write Amplification 관점입니다. 작은 논리적 변경이 아래 계층에서 더 많은 작업으로 불어납니다.
Logical Write → Journal 처리 → Filesystem metadata → Filesystem Journal → Block Write → Storage
로그가 많은 서버에서 용량만 보면 절반만 본 것입니다. 발생 빈도와 저장 구조를 같이 봐야 합니다.
Kubernetes에서는 이 문제가 증폭된다
일반적인 서버 한 대라면 로그를 만드는 애플리케이션 숫자가 제한적입니다. Kubernetes는 다릅니다. Worker Node 하나가 여러 애플리케이션의 실행 환경입니다. 한 노드에 로그 생산자가 밀집해 있다는 뜻입니다.
특정 애플리케이션이 초당 수백~수천 줄의 로그를 뱉기 시작하거나, 여러 Pod에서 동시에 오류가 반복되면 상황은 더 나빠집니다.
외부 시스템 장애를 예로 들어보겠습니다.
외부 API 장애 → Application timeout → Retry → Error Log → Retry → Error Log → Retry...
원래 문제는 외부 API 장애였습니다. 그런데 retry가 폭증하면서 로그도 폭증합니다. 노드의 Disk I/O가 올라갑니다. Disk latency가 올라가면 컨테이너와 시스템 컴포넌트도 영향을 받습니다. 그러면 다시 timeout과 오류가 늘어납니다.
장애 → Retry 증가 → Log 증가 → Disk I/O 증가 → Latency 증가 → 추가 장애
이런 피드백 루프가 만들어집니다. 장애를 기록하던 로그가 장애를 키우기 시작하는 지점입니다.
그래서 Kubernetes에서 로그를 노드에만 두면 안 된다
Kubernetes 공식 문서도 원칙 하나를 이야기합니다. 클러스터의 로그는 Node, Pod, Container와 독립적인 Storage와 Lifecycle을 가져야 한다는 것.
Kubernetes 자체는 완전한 중앙 로그 저장 시스템을 제공하지 않습니다. 그래서 보통 이런 구조를 씁니다.
Pod/Node → Logging Agent → Central Logging → Object Storage / Log Storage → Dashboard / Search / Alert
Fluent Bit, Vector 같은 Agent가 로그를 수집하고 Loki나 Elasticsearch/OpenSearch 같은 Backend로 넘기는 식입니다. 장기 보관이 필요하면 Object Storage와 붙입니다.
지킬 건 하나입니다. Worker Node의 로컬 디스크를 장기 로그 저장소로 쓰지 않는 것.
그렇다고 중앙 로그를 붙이면 끝날까?
그것도 아닙니다. Logging Agent도 공짜가 아니거든요.
Log 생성 → Local Write → Logging Agent Read → Parsing → Buffer → Network Transfer → Backend Write
로그 하나가 태어나면서 CPU, Memory, Disk, Network를 다 씁니다. 쓸데없는 DEBUG 로그가 대량으로 쏟아지면 중앙 로그 시스템을 아무리 잘 만들어놔도 비용이 다른 곳으로 옮겨갈 뿐입니다.
로그 아키텍처의 첫 질문은 "어디에 저장할까?"가 아니라 "이 로그를 왜 남기고 있는가?"라고 저는 생각합니다.
로그도 하나의 Resource다
Kubernetes에서 CPU와 Memory에는 다들 민감합니다.
resources:
requests:
cpu:
memory:
limits:
cpu:
memory:
로그에는 의외로 무관심합니다. 애플리케이션 개발자가 DEBUG 로그를 수십 배 늘려도 CPU Limit처럼 즉시 막히지 않으니까요.
로그도 자원을 씁니다. CPU, Memory, Disk, IOPS, Network, 그리고 Storage Cost. 규모가 큰 Kubernetes 환경이라면 Logging도 Capacity Planning 대상이라고 봅니다.
장애를 볼 때 한 단계 아래를 봐야 한다
Kubernetes 운영에는 재미있는 구석이 있습니다.
Pod 문제인 줄 알았는데 Node 문제이고, Node 문제인 줄 알았는데 OS 문제이고, OS 문제인 줄 알았는데 Filesystem 문제이고, Filesystem 문제인 줄 알았는데 Storage 문제인 경우가 있습니다. 반대로 Storage 문제처럼 보였는데 시작점이 애플리케이션의 과도한 로그였던 적도 있었습니다.
Application → Container → Kubernetes → Operating System → Filesystem → Storage
장애 분석에서 중요한 건 기술 하나를 깊이 아는 것보다 계층을 따라 내려갈 수 있는 능력이라고 생각합니다. CPU가 높으면 CPU만 보는 게 아니라 왜 높아졌는지 봐야 하고, Disk Util이 95%라면 디스크만 보는 게 아니라 누가, 무엇을, 얼마나 자주 쓰고 있는지를 봐야 합니다.
그래서 지금은
로그는 장애를 분석하려고 남깁니다. 많이 남길수록 안전해 보입니다. 인프라 쪽에서 보면 꼭 그렇지도 않습니다. 로그도 데이터고, 데이터는 저장되어야 하고, 저장에는 I/O가 듭니다.
수많은 워크로드가 노드 하나를 공유하는 환경에서는 로그 한 줄 한 줄이 별것 아니어도 그 합이 부하가 됩니다.
로그는 문자열이 아니라 I/O입니다. 여기까지는 확실합니다. 그럼 어디까지 줄여도 되느냐. 그건 저도 아직 답을 못 정했습니다. 로그 레벨 낮춰놓으면 꼭 그날 장애가 나고, 하필 안 남긴 그 한 줄이 아쉬워지더라고요.
참고 자료
- Kubernetes Documentation — Logging Architecture
- Kubernetes Documentation — System Logs
- Kubernetes Documentation — Observability
- systemd GitHub Issue Tracker — journald의 높은 Disk I/O 관련 사례
※ 최근 공유되는 특정 파일시스템별 '로그 한 줄당 수십 KB 이상의 쓰기' 수치는 환경과 측정 방식에 따라 달라질 수 있어 이 글에서는 특정 수치를 일반화하지 않았습니다.
이 글이 도움이 되셨나요?
버튼 하나가 다음 글을 쓰는 힘이 됩니다