26Y06n3

Young-Kyoo Kim·2026년 7월 6일

사용자가 물어본 상황을 정리하면:

  • free -h의 Used ≈ Total − Free − Buffers/Cache 인데, 이 Used가 비정상적으로 큼
  • Cache/Buffer(및 SReclaimable) 별도 확인했고 문제 없음
  • slabtop으로 본 커널 슬랩(SUnreclaimable 포함 캐시들) 사용량도 크지 않음
  • 프로세스 RSS 합계도 낮음
  • RSS + Cache + Slab 다 더해도 Used의 절반 수준 → 나머지 절반이 "어디 있는지" 불명

이건 실제로 대규모 k8s/DirectPV/Cilium 환경에서 꽤 흔히 겪는 미스터리라, 정확한 근거를 좀 더 확인하고 답변드리겠습니다.좋은 질문입니다. "RSS 합 + Cache/Buffer + Slab 합 ≈ Used의 절반"이라는 건 실제로 상당히 전형적인 패턴이고, 원인 후보가 꽤 정해져 있습니다. 순서대로 짚어보겠습니다.

1. 먼저 free의 Used 계산식부터 재확인

free의 Used는 단순히 Total - Free - Buffers - Cache가 아니라 버전/커널에 따라 계산 방식이 다릅니다. procps 최신 버전 기준:

buff/cache = Buffers + Cached + SReclaimable - Shmem
used = MemTotal - MemFree - buff/cache

여기서 중요한 건, Shmem(tmpfs, /dev/shm, 공유 메모리 세그먼트)이 Cached에 포함되어 있다가 다시 빠져서 실제로는 "Used" 쪽으로 넘어간다는 점입니다. 즉 tmpfs를 많이 쓰는 컴포넌트(예: 컨테이너 런타임, 일부 CSI 드라이버, 로그 버퍼)가 있으면 free -h의 buff/cache에는 안 잡히고 Used에 잡힙니다.

grep -Ei 'Shmem|MemTotal|MemFree|Cached|Buffers|SReclaimable|SUnreclaim' /proc/meminfo

Shmem 값이 큰데 RSS로는 안 잡힌다면 → tmpfs/shm에 데이터가 떠 있는 것.

2. /proc/meminfo에서 놓치기 쉬운 필드들

slabtop과 RSS 합만으로는 안 보이는 커널 메모리 영역이 꽤 많습니다. 아래를 전부 확인해보세요.

cat /proc/meminfo

특히 주목할 항목:

필드의미놓치는 이유
PageTables프로세스 페이지 테이블RSS와 별개로 집계, 프로세스 수/매핑 많으면 증가
KernelStack커널 스택 (스레드당)스레드 수 많은 프로세스(예: MinIO, cilium-agent)에서 커짐
VmallocUsedvmalloc 영역 실사용량slabtop에 안 잡힘 (아래 3번 참고)
Percpuper-CPU 할당 메모리코어 수 많으면 누적
HugePages_Rsvd / HugePages_Total예약만 되고 미사용인 hugepageRSS에도 slab에도 안 잡히고 Used만 깎아먹음
Mlocked / Unevictablemlock된 페이지특정 앱이 잠가둔 메모리
Bounce구형 DMA bounce buffer특정 HBA/RAID 카드 드라이버에서 발생 가능
Committed_AS vs 실사용오버커밋 여부 참고용-

HugePages_Total이 0이 아닌데 AnonHugePages나 실제 사용처가 없다면, 누군가 vm.nr_hugepages를 설정해놨고 아무도 안 쓰는 상태일 수 있습니다 — 이건 RSS에도 slab에도 안 잡히지만 Used를 그대로 깎아먹는 대표적인 "숨은 메모리"입니다.

3. Cilium/eBPF 쪽 — 실제로 유력한 용의자

말씀하신 증상(slab 정상, RSS 정상, 그런데 Used만 큼)은 eBPF map이 vmalloc으로 할당되는 경우와 정확히 맞아떨어집니다.

핵심 포인트: Cilium의 BPF map(conntrack, NAT, LB service, policy, ipcache, endpoint별 policy map 등)은 대부분 생성 시점에 max_entries × value_size만큼 커널 메모리를 즉시 확보합니다. 실제 엔트리 수와 무관하게 최대 용량 기준으로 잡힙니다. 그리고 이 할당은 크기에 따라 kmalloc이 아니라 vmalloc 경로를 타는 경우가 많아서 — slabtop에는 전혀 안 보이고, /proc/meminfoVmallocUsed에만 잡힙니다.

확인 방법:

# vmalloc 총 사용량
grep Vmalloc /proc/meminfo

# 상세 내역 (bpf 관련 항목 grep)
sudo cat /proc/vmallocinfo | grep -i bpf | awk '{print $2}' | \
  sed 's/[^0-9]//g' | awk '{sum+=$1} END {print sum/1024/1024 " MB"}'

# 현재 로드된 bpf map들과 각각의 크기 확인
sudo bpftool map show
sudo bpftool map show -j | jq -r '.[] | "\(.name) max_entries=\(.max_entries) value_size=\(.value_size) key_size=\(.key_size)"'

이걸로 max_entries × (key_size+value_size)를 map별로 합산해보면 실제로 수백 MB~수 GB가 나오는 경우가 흔합니다. 특히:

  • cilium_ct4_global / cilium_ct6_global (conntrack, 기본 100만 엔트리급)
  • cilium_lb4_services, cilium_lb4_backends
  • 엔드포인트별 policy map — 파드 churn(생성/삭제)이 잦은 환경에서 엔드포인트 삭제 시 map이 정리 안 되고 /sys/fs/bpf 밑에 pin된 채 남는 버그가 실제로 보고된 적 있습니다.
# pin된 orphan map/link 확인 (실제 살아있는 엔드포인트 수와 비교)
sudo find /sys/fs/bpf -type f | wc -l
cilium-dbg endpoint list   # 또는 cilium endpoint list

pin된 개수가 실제 엔드포인트/서비스 수보다 훨씬 많다면 정리가 안 되고 누적되는 것입니다.

알려진 이슈 사례 (실제 GitHub 이슈 기준):

  • Cilium 1.16.0~1.16.1: L2 neighbor discovery 에러 처리 경로에서 메모리 누수 보고 → l2NeighDiscovery 비활성화가 임시 우회책으로 쓰인 사례가 있습니다.
  • Cilium 1.19.0: 업그레이드 후 cilium-agent가 과도한 메모리를 사용해 kubelet이 OOMKill되는 사례가 커뮤니티에 보고되었습니다 (조사 진행 중인 이슈).

두 사례 다 cilium-agent 자체(유저스페이스 프로세스) 문제이므로, 이건 RSS로 잡혀야 정상입니다. 사용자분이 "RSS는 낮다"고 하셨으니 이 경로보다는 커널 쪽 BPF map(vmalloc)이 더 유력한 후보입니다. 혹시 cilium-agent 버전을 알려주시면 해당 버전에 알려진 이슈가 있는지 더 좁혀볼 수 있습니다.

4. 죽은 cgroup(memcg)이 메모리를 물고 있는 경우 ("zombie cgroup")

파드/컨테이너 churn이 잦은 클러스터(특히 DirectPV처럼 볼륨 마운트/언마운트가 잦고, MinIO 오토스케일링/재시작이 있는 환경)에서 자주 나오는 패턴입니다.

  • 파드가 삭제되어도 해당 cgroup이 커널 내부적으로 즉시 회수되지 않고 "dying" 상태로 남을 수 있습니다.
  • 이 메모리는 살아있는 프로세스의 RSS에도 안 잡히고(프로세스가 이미 없으니까), slabtop에서도 캐시 타입별 합산으로 보이기 때문에 "어느 cgroup이 물고 있는지"는 안 보입니다.
  • 주로 dentry/inode 캐시가 dead memcg에 pin되어 회수가 안 되는 커널 버그/특성 때문에 발생합니다.

확인:

# cgroup v2 기준, dying(죽었지만 회수 안 된) memcg 개수/메모리
find /sys/fs/cgroup -name memory.stat 2>/dev/null | wc -l   # 참고용 총 개수
cat /sys/fs/cgroup/memory.stat 2>/dev/null | grep -i slab

# 커널 로그에 memcg 관련 경고가 있는지
dmesg | grep -i -E 'memcg|oom|cgroup'

# 커널 버전 확인 (이 문제는 커널/cgroup 버전에 따라 다름)
uname -r

5. DirectPV/MinIO 관점에서 추가로 볼 것

  • MinIO AIStor는 성능을 위해 O_DIRECT를 쓰는 경우가 많지만, 메타데이터 파일이나 저널, 혹은 일부 경로는 buffered I/O를 쓸 수 있습니다. 이 경우 Cached가 올라가야 정상인데, 만약 write 후 즉시 drop_caches 되지 않는 이상한 패턴이라면 Cached에는 안 잡히고 다른 형태(예: dirty page가 writeback 대기 중 Bounce/Writeback)로 잠깐 잡힐 수 있습니다.
  • DirectPV는 XFS를 직접 포맷해서 씁니다. XFS 저널/inode 관련 slab(xfs_inode, xfs_ili, xfs_buf)은 slabtop에 보통 잡히지만, XFS의 buffer cache(xfs_buf)가 page cache와 별도로 pin되는 경우가 있어 이것도 확인해보세요:
slabtop -o | grep -i xfs
  • 컨테이너 오버레이(overlay2 등)를 쓰고 있다면 dangling overlay 레이어가 페이지캐시를 무한정 물고 있는 경우도 있습니다.

6. 원인을 좁히는 실전 순서 (추천)

# 1) 전체 그림 재확인
cat /proc/meminfo

# 2) vmalloc/hugepage/shmem/kernelstack/pagetables 합산해서 gap과 비교
# 3) bpf map 총량 계산
sudo bpftool map show -j | jq -r '[.[] | (.max_entries * (.value_size + .key_size))] | add'

# 4) pin된 bpf map/link 개수 vs 실제 리소스 개수 비교
sudo find /sys/fs/bpf -type f | wc -l

# 5) cilium 자체 메모리 압박 지표 확인
cilium status --verbose | grep -i -A2 'BPF Maps'

정리

가능성 높은 순서로 보면:
1. Cilium BPF map이 vmalloc으로 커널 메모리를 max_entries 기준으로 선점 — slabtop/RSS 어디에도 안 보이고 VmallocUsed에만 잡힘 (가장 유력)
2. HugePages 예약 후 미사용 — RSS/slab 모두에 안 잡힘
3. Shmem/tmpfs 사용량 — free의 buff/cache 계산에서 다시 빠져나가 Used로 들어감
4. 죽은 cgroup(memcg)이 회수 안 된 캐시를 물고 있음 — 파드 churn 많은 클러스터에서 흔함
5. cilium-agent 자체의 유저스페이스 메모리 누수(1.16.0-1.16.1 L2 neighbor discovery, 1.19.0 보고 사례) — 이건 RSS로 잡혀야 하므로 후순위

/proc/meminfo 전체 값과 bpftool map show -j 결과를 공유해주시면 실제로 gap이 어디서 나는지 계산해서 짚어드릴 수 있습니다.

0개의 댓글