패킷이 물리 스위치를 거쳐 노드에 도달하고, 리눅스 커널과 CNI(eBPF/Cilium)를 통과해 최종 Pod 내 애플리케이션 소켓에 도달하기까지의 전체 데이터 플레인(Data Plane) 경로 및 구간별 병목 지점입니다.
대규모 트래픽 환경(Spark 셔플, StarRocks 쿼리, vLLM/학습 분산 통신)에서 병목은 특정 계층의 버퍼 고갈, 락 경합, 불필요한 컨텍스트 스위칭에서 발생합니다.
[ 물리 스위치 (ToR/Spine) ]
│ (물리 링크: 25G/100G, MTU, PFC/ECN)
▼
[ 1. NIC 하드웨어 계층 ] ─── Ring Buffer / RSS / PCIe 대역폭
│ (DMA 전송 & Hard IRQ)
▼
[ 2. 드라이버 & 커널 NAPI ] ─── softirq (ksoftirqd) / CPU 코어 경합
│ (sk_buff 할당 & eBPF 훅)
▼
[ 3. CNI / 네트워크 서브시스템 ] ─── Cilium(tc/XDP eBPF) / Conntrack / 라우팅
│ (가상 인터페이스 통과 or BPF Host Routing 우회)
▼
[ 4. 커널 소켓 계층 (TCP/IP) ] ─── rmem/wmem 버퍼 / Listen 백로그 / TCP 윈도우
│ (시스템 콜: read/epoll & 유저 공간 복사)
▼
[ 5. Pod 애플리케이션 계층 ] ─── Event Loop / 런타임 스레드 풀 / GC Stop-the-world
물리 케이블을 타고 패킷이 서버의 NIC 포트에 도달하기 직전의 구간입니다.
Egress Drop Counter, ECN Marked Packets.NIC 물리 포트의 PHY 칩이 전기/광 신호를 프레임으로 변환하여 호스트 메모리로 전송(DMA)하는 단계입니다.
ethtool -S <ethX> | grep -E "rx_dropped|rx_missed_errors|rx_discards"ethtool -G <ethX> rx 4096 tx 4096 (버퍼를 하드웨어 최대치로 확장).cat /proc/interrupts | grep <ethX> (특정 큐 카운터만 급증하는지 점검).softirq) 구간NIC가 호스트 RAM에 패킷을 쓰고 커널에 "가져가라"고 알리는 단계입니다.
net_rx_action(SoftIRQ)이 실행되는데, 초당 패킷 수(PPS)가 수백만 개에 달하면 CPU가 소프트 인터럽트 처리에 갇힙니다.top에서 %si(softirq) 수치가 100%에 근접하며 패킷 처리가 멈춤.cat /proc/net/softnet_stat의 첫 번째 열(처리 건수) 대비 세 번째 열(time_squeeze: 처리 시간 초과로 양보한 횟수) 증가 여부.sysctl -w net.core.netdev_budget=600, net.core.netdev_max_backlog=100000.패킷이 파드 IP를 찾아 라우팅되고 필터링되는 논리적 네트워크 구간입니다.
veth 인터페이스를 거치면서 TCP/IP 스택을 두 번 통과(노드 네임스페이스 파드 네임스페이스)하고 불필요한 컨텍스트 스위칭이 발생합니다.nf_conntrack 테이블이 가득 참.nf_conntrack: table full, dropping packet 커널 로그와 함께 신규 세션 드롭.sysctl net.netfilter.nf_conntrack_count vs net.netfilter.nf_conntrack_max.preallocateMaps)되지 않아 동적 할당 오버헤드 발생.커널이 수신한 TCP 세그먼트를 순서대로 조립하여 애플리케이션의 소켓 수신 큐(Receive Queue)에 담는 구간입니다.
net.ipv4.tcp_rmem이 너무 작으면 송신측에 ACK를 보낼 때 윈도우 크기를 0으로 줄여버립니다(TCP ZeroWindow).ss -nt 명령에서 Recv-Q가 가득 차 있는지 확인.somaxconn)Connection refused 또는 Connection timed out.netstat -s | grep -i "listen" (times the listen queue of a socket overflowed).커널 메모리 공간(Kernel Space)의 소켓 버퍼에서 사용자 프로세스(User Space)로 데이터가 넘어와 실제 비즈니스 로직에 들어가는 구간입니다.
read/recv) 오버헤드 및 커널-유저 메모리 복사read() 시스템 콜을 지나치게 자주 호출하면 CPU가 유저-커널 모드 전환(Context Switch)에 낭비됩니다.Recv-Q는 차오르는데 애플리케이션이 이를 꺼내가지 못해 타임아웃 발생.| 구간 | 주요 증상 | 핵심 진단 명령 / 메트릭 |
|---|---|---|
| 스위치/물리 | 패킷 손실, RTO 재전송 증가 | ToR CLI, `netstat -s |
| NIC / Ring | 프레임 폐기, Rx 에러 | `ethtool -S |
| 인터럽트 (NAPI) | CPU %si 100% 포화 | mpstat -P ALL 1, /proc/net/softnet_stat |
| CNI (Cilium) | Conntrack 풀, BPF 드롭 | cilium bpf drop list, `dmesg |
| 커널 TCP 소켓 | ZeroWindow, 수신 큐 정체 | ss -itmp, `netstat -s |
| 애플리케이션 | 응답 지연, EventLoop 지연 | pidstat -w, JVM GC 로그, pprof |
===
CPU 병목은 단순히 top 명령어에서 CPU 사용률이 100%를 찍는 현상만을 의미하지 않습니다. 최신 고성능 멀티소켓 서버(Intel Xeon 등)에서는 CPU 코어의 파이프라인 연산기 자체는 놀고 있는데(Stall), 메모리나 버스를 기다리느라 멈춰 있는 대기 병목이 전체 성능 저하의 절반 이상을 차지합니다.
실제 마이크로아키텍처 파이프라인부터 메모리 계층, OS 스케줄러, 컨테이너 Cgroups 제약까지 하부 하드웨어에서 상위 애플리케이션에 이르는 구간별 CPU 병목 지점을 정리했습니다.
[ 1. CPU 코어 & 파이프라인 ] ─── 프론트엔드 분기 예측 실패 / 백엔드 실행 유닛 포화
│
[ 2. 캐시 계층 (L1/L2/L3) ] ─── 캐시 라인 미스 / False Sharing (캐시 바운싱)
│
[ 3. 메인 메모리 & 인터커넥트 ] ─── UPI 버스 경합 / 원격 NUMA 접근 지연 / 메모리 대역폭 포화
│
[ 4. 커널 & 스케줄러 계층 ] ─── 컨텍스트 스위칭 오버헤드 / 락(Spinlock) 경합 / 시스템 콜
│
[ 5. 컨테이너/K8s Cgroups ] ─── CFS 쿼터에 의한 CPU Throttling / 코어 분산 배치
CPU 코어 내부에서 기계어 명령어를 디코딩하고 연산 유닛(ALU, FMA 등)으로 밀어 넣는 초미세 연산 구간입니다. 인텔 Top-down 분석 방법론(TMA)의 핵심 영역입니다.
if/else)이 많은 코드나 거대한 바이너리에서 CPU 분기 예측기(Branch Predictor)가 다음 실행 경로를 틀리게 예측할 때 발생합니다.perf stat -e branch-misses,branches ./appCPU 연산 속도(약 0.3ns)에 비해 메인 메모리 DRAM 접근(약 60~100ns)은 수백 배 느리기 때문에, 캐시 적중률(Hit Ratio)이 무너지면 CPU는 정지합니다.
perf stat -e L1-dcache-load-misses,LLC-load-misses듀얼 소켓 이상 서버 환경에서 CPU 성능을 가장 크게 갉아먹는 아키텍처 병목입니다.
numastat -c <프로세스명> (numa_miss 및 foreign 카운트 증가 확인).하드웨어 위에서 프로세스와 스레드를 조율하는 리눅스 커널 스택에서 발생하는 병목입니다.
vmstat 1에서 cs(Context Switch) 수치가 초당 수십만 이상으로 치솟거나, pidstat -w로 자발적/비자발적 스위칭 확인.mmap_lock)을 동시에 수정하려 할 때.top에서 커널 모드 CPU 점유율을 나타내는 %sy(System CPU)가 30~50% 이상 급증합니다. 코어가 일은 안 하고 락이 풀릴 때까지 빈 루프를 돌며 타인을 기다리는 상태입니다.perf top을 실행했을 때 상위 심볼에 queued_spin_lock_slowpath, native_queued_spin_lock_slowpath가 1위를 차지함.%si / SoftIRQ)하드웨어와 OS는 여유가 있는데 K8s의 리소스 격리 정책 때문에 인위적으로 CPU가 멈추는 구간입니다.
resources.limits.cpu 설정 시 리눅스 CFS 스케줄러는 기본 100ms(cpu.cfs_period_us=100000) 주기로 할당량을 체크합니다. 멀티스레드 앱이 주기 시작 20ms 만에 할당된 CPU 시간을 다 써버리면, 남은 80ms 동안 프로세스를 강제로 정지(Freeze)시킵니다.cat /sys/fs/cgroup/cpu/kubepods/pod.../cpu.stat
# nr_throttled 및 throttled_time 수치 급증 여부 확인
| 병목 증상 | 의심 구간 | 핵심 확인 지표 / 툴 | 해결 방안 |
|---|---|---|---|
| CPU %us 100%이나 IPC 극히 낮음 | 캐시 미스 / DRAM 대기 | perf stat (IPC < 1.0, LLC-misses) | 데이터 메모리 지역성 개선, HugePages 적용 |
| CPU %sy(System) 급증 | 락 경합 / 시스템 콜 | perf top (queued_spin_lock), strace | 락 세분화, I/O 버퍼링, Connection Pool 튜닝 |
| CPU %si(SoftIRQ) 급증 | 네트워크/NIC 패킷 쏠림 | mpstat -P ALL 1, /proc/interrupts | RPS/RSS 해시 분산, NIC 코어 바인딩 |
| 노드 한산한데 Pod 지연 발생 | K8s CFS 스로틀링 | cgroup cpu.stat (throttled_time) | CPU Limit 제거 or Guaranteed QoS(정수 정적 바인딩) |
| 특정 소켓만 CPU 포화 | NUMA 불균형 | numastat, lscpu | Pod 리소스 크기 단일 NUMA 노드로 재조정 |
===
대규모 분산 환경(K8s, Cilium, 고성능 스토리지/DB, AI 서빙)에서 실무적으로 성능 저하가 가장 빈번하게 터지는 지점과 튜닝을 통해 성능을 극적으로 끌어올릴 수 있는 핵심 개선 구간을 네트워크와 CPU 각각 우선순위별로 분류했습니다.
네트워크 성능 저하의 약 80%는 양 끝단(하드웨어 버퍼/큐와 커널/CNI 인터페이스)에서 발생합니다.
[빈도/개선 여지 최상] NIC Ring Buffer & RSS ──> 커널 SoftIRQ / NAPI
[빈도/개선 여지 상] CNI 데이터 플레인 (veth vs eBPF Host Routing)
[빈도/개선 여지 중상] 소켓 버퍼 (rmem/wmem & ZeroWindow)
[빈도 중 / 개선 난이도 상] ToR 스위치 Incast & 패킷 드롭
ethtool -G <NIC> rx 4096 tx 4096 적용만으로 재전송으로 인한 네트워크 지연이 즉시 80% 이상 해소됩니다.%si) 특정 코어 쏠림%si 100%를 치면서 나머지 코어가 놀고 있어도 패킷이 드롭됩니다.ethtool -L), IRQ 코어 친화도(Affinity) 분산, net.core.netdev_budget 상향.rmem/wmem이 작아 윈도우가 닫히며(ZeroWindow) 대역폭이 1/10 토막 납니다.net.ipv4.tcp_rmem, net.core.rmem_max를 16MB 수준으로 상향하여 BDP(Bandwidth-Delay Product) 확보.CPU는 "K8s 인위적 제한(CFS)", "듀얼 소켓 간 메모리 이동(NUMA)", "락(Lock) 경합" 세 지점에서 실무 병목이 집중됩니다.
[빈도 최상 / 개선 즉각적] K8s CFS Bandwidth Throttling (CPU Limit)
[빈도 상 / 성능 개선 큼] NUMA 원격 메모리 참조 & UPI 버스 경합
[빈도 상 / 해결 난이도 상] 커널 Spinlock 경합 & 컨텍스트 스위칭
[빈도 중 / 구조적 개선] 캐시 미스 (L3 Cache Miss / False Sharing)
limits.cpu 때문에 100ms 주기 내에서 할당량을 다 썼다고 커널이 프로세스를 50~80ms 동안 완전히 얼려버림(Freeze). Pod P99 응답 지연이 급증하는 1위 원인입니다.limits.cpu를 제거하거나,static CPU Manager를 통해 Guaranteed QoS (Requests == Limits, 정수 코어)로 배포하여 CFS 쿼터 메커니즘을 완전히 우회.--topology-manager-policy=single-numa-node 설정.numa_balancing=0 비활성화로 불필요한 백그라운드 페이지 마이그레이션 방지.%sy 급증)mmap_lock), 커널 소켓 테이블, 파일 디스크립터 경합으로 빈 루프를 돌며 대기.| 영역 | 최우선 작업 대상 (Quick Win) | 튜닝 대상 파라미터 / 아키텍처 | 예상 효과 |
|---|---|---|---|
| Network #1 | NIC Ring Buffer 최대화 | ethtool -G <eth> rx 4096 tx 4096 | 버스트 트래픽 시 패킷 드롭 및 재전송 즉시 제거 |
| Network #2 | CNI eBPF 바이패스 | Cilium Native Routing + BPF Host Routing | veth/iptables 오버헤드 제거, 레이턴시 극소화 |
| Network #3 | TCP 수신 버퍼 확장 | net.core.rmem_max, tcp_rmem 16MB | 25G/100G 환경에서 Throughput 저하(ZeroWindow) 방지 |
| CPU #1 | K8s CPU Throttling 제거 | Pod Guaranteed QoS (정수 코어 바인딩) | P99 레이턴시 스파이크 및 간헐적 프리징 해결 |
| CPU #2 | NUMA 도메인 로컬화 | Kubelet Single-NUMA 정책, 소켓별 Pod 분할 | L3 캐시 히트율 극대화, UPI 링크 병목 제거 |
| CPU #3 | 메모리 TLB 미스 축소 | OS & Pod 레벨 HugePages 2MB 할당 | 대용량 메모리(DB, vLLM, MinIO) 주소 변환 오버헤드 감소 |
===
노드 인수 직후 또는 성능 이상 감지 시 네트워크 버퍼/드롭, CPU 스로틀링, NUMA 크로스 접근, 커널 락 경합을 일괄 점검하는 올인원 쉘 스크립트(check_bottlenecks.sh)입니다.
의존성 패키지가 없는 환경에서도 커널 가상 파일시스템(/proc, /sys)을 직접 파싱하여 동작하며, 판정 결과(PASS/WARN/FAIL)와 함께 즉시 개선 가이드를 출력합니다.
check_bottlenecks.sh)#!/usr/bin/env bash
#
# Kubernetes Node Performance & Bottleneck Checker
# Target OS: RHEL 10.x / Rocky / AlmaLinux / CentOS Stream
#
set -o pipefail
# ANSI 색상 코드 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
print_header() {
echo -e "\n${BLUE}${BOLD}================================================================${NC}"
echo -e "${BLUE}${BOLD} $1${NC}"
echo -e "${BLUE}${BOLD}================================================================${NC}"
}
report_pass() { echo -e " [${GREEN}PASS${NC}] $1"; }
report_warn() { echo -e " [${YELLOW}WARN${NC}] $1"; }
report_fail() { echo -e " [${RED}FAIL${NC}] $1"; }
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}루트(root) 권한으로 실행해야 전체 항목을 점검할 수 있습니다.${NC}"
exit 1
fi
print_header "1. 네트워크 계층 병목 진단 (NIC, Ring Buffer, Drop)"
# 물리 NIC 인터페이스 자동 탐색 (가상 인터페이스/루프백 제외)
PHYSICAL_NICS=$(ip -o link show | awk -F': ' '{print $2}' | grep -vE '^(lo|veth|docker|cbr|flannel|cilium|lxc|cali|br-)')
for NIC in $PHYSICAL_NICS; do
# 상태가 UP인 인터페이스만 점검
OPERSTATE=$(cat /sys/class/net/"$NIC"/operstate 2>/dev/null)
if [ "$OPERSTATE" != "up" ]; then
continue
fi
echo -e "\n * 인터페이스: ${BOLD}$NIC${NC}"
# 1-1. 링 버퍼(Ring Buffer) 점검 (ethtool 필요)
if command -v ethtool &>/dev/null; then
RING_INFO=$(ethtool -g "$NIC" 2>/dev/null)
if [ -n "$RING_INFO" ]; then
CURR_RX=$(echo "$RING_INFO" | awk '/Current Hardware Settings:/,/^$/' | awk '/RX:/{print $2}' | head -1)
MAX_RX=$(echo "$RING_INFO" | awk '/Pre-set maximums:/,/^$/' | awk '/RX:/{print $2}' | head -1)
if [ -n "$CURR_RX" ] && [ -n "$MAX_RX" ]; then
if [ "$CURR_RX" -eq "$MAX_RX" ]; then
report_pass "Rx Ring Buffer가 최댓값($CURR_RX)으로 설정되어 있습니다."
else
report_warn "Rx Ring Buffer가 최댓값보다 작습니다. (현재: $CURR_RX / 최대: $MAX_RX)"
echo -e " ${YELLOW}-> 개선 조치: ethtool -G $NIC rx $MAX_RX tx $MAX_RX${NC}"
fi
fi
fi
# 1-2. NIC 하드웨어 패킷 드롭 및 에러 카운터 점검
NIC_DROPS=$(ethtool -S "$NIC" 2>/dev/null | grep -iE 'rx_dropped|rx_missed_errors|rx_discards|drop' | awk '$2 > 0 {print $1, $2}')
if [ -z "$NIC_DROPS" ]; then
report_pass "NIC 하드웨어 버퍼 레벨 패킷 드롭/에러가 없습니다 (0)."
else
report_fail "NIC 하드웨어 패킷 드롭이 감지되었습니다:\n$NIC_DROPS"
echo -e " ${RED}-> 개선 조치: 링 버퍼 확장 및 NIC 대역폭/스위치 Flow Control 점검 필요${NC}"
fi
else
report_warn "ethtool이 설치되어 있지 않아 링 버퍼 점검을 생략합니다."
fi
# 1-3. OS 레벨 인터페이스 드롭 확인
OS_RX_DROP=$(cat /sys/class/net/"$NIC"/statistics/rx_dropped 2>/dev/null || echo 0)
if [ "$OS_RX_DROP" -eq 0 ]; then
report_pass "OS 수신 패킷 드롭 통계 정상 (rx_dropped: 0)"
else
report_warn "OS 수신 패킷 드롭 발생 이력 (rx_dropped: $OS_RX_DROP)"
fi
done
# 1-4. 커널 NAPI SoftIRQ time_squeeze 점검
if [ -f /proc/net/softnet_stat ]; then
TIME_SQUEEZE=$(awk '{sum += hex2dec("0x"$3)} END {print sum}' /proc/net/softnet_stat 2>/dev/null || \
awk '{print "0x"$3}' /proc/net/softnet_stat | while read h; do printf "%d\n" $h; done | awk '{sum+=$1} END {print sum}')
if [ "$TIME_SQUEEZE" -eq 0 ]; then
report_pass "NAPI SoftIRQ 버짓 초과(time_squeeze) 없음 (0)"
else
report_warn "SoftIRQ 처리 시간 초과 이력 발생 ($TIME_SQUEEZE 건)"
echo -e " ${YELLOW}-> 개선 조치: sysctl -w net.core.netdev_budget=600${NC}"
fi
fi
# 1-5. TCP 수신 버퍼(rmem) 설정 점검
RMEM_MAX=$(sysctl -n net.core.rmem_max 2>/dev/null || echo 0)
if [ "$RMEM_MAX" -ge 16777216 ]; then
report_pass "TCP 최대 수신 버퍼가 고속망에 맞게 설정됨 ($(( RMEM_MAX / 1024 / 1024 )) MB)"
else
report_warn "TCP 최대 수신 버퍼(net.core.rmem_max)가 작습니다 ($(( RMEM_MAX / 1024 )) KB)"
echo -e " ${YELLOW}-> 개선 조치: sysctl -w net.core.rmem_max=16777216 (16MB 권장)${NC}"
fi
print_header "2. CPU 계층 병목 진단 (K8s Throttling, NUMA, Spinlock)"
# 2-1. K8s Cgroup CPU Throttling 발생 여부 점검
CGROUP_THROTTLED=0
# Cgroups v1 & v2 호환 탐색
for STAT_FILE in $(find /sys/fs/cgroup -name "cpu.stat" 2>/dev/null); do
THROTTLED=$(awk '/nr_throttled/{print $2}' "$STAT_FILE" 2>/dev/null)
if [ -n "$THROTTLED" ] && [ "$THROTTLED" -gt 0 ]; then
CGROUP_THROTTLED=$((CGROUP_THROTTLED + THROTTLED))
fi
done
if [ "$CGROUP_THROTTLED" -eq 0 ]; then
report_pass "컨테이너 CPU Throttling(강제 프리징) 발생 내역 없음"
else
report_fail "컨테이너 CPU Throttling 발생 감지 (누적 $CGROUP_THROTTLED 회)"
echo -e " ${RED}-> 주요 워크로드(StarRocks, vLLM)의 CPU Limits 제거 또는 Guaranteed QoS 적용 필요${NC}"
fi
# 2-2. NUMA 밸런싱 및 원격 메모리 미스(Remote Miss) 점검
NUMA_NODES=$(ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l)
if [ "$NUMA_NODES" -gt 1 ]; then
echo -e " * 멀티 NUMA 노드 감지: 총 ${BOLD}$NUMA_NODES 개${NC}"
AUTO_NUMA=$(sysctl -n kernel.numa_balancing 2>/dev/null || echo 0)
if [ "$AUTO_NUMA" -eq 0 ]; then
report_pass "자동 커널 NUMA 밸런싱 비활성화됨 (numa_balancing=0, 레이턴시 튐 방지)"
else
report_warn "자동 커널 NUMA 밸런싱이 켜져 있습니다 (numa_balancing=1)"
echo -e " ${YELLOW}-> DB/스토리지 지연 방지를 위해 'sysctl -w kernel.numa_balancing=0' 권장${NC}"
fi
if command -v numastat &>/dev/null; then
NUMA_MISS=$(numastat 2>/dev/null | awk '/numa_miss/{sum += $2 + $3} END {print sum}')
if [ -n "$NUMA_MISS" ] && [ "${NUMA_MISS%.*}" -gt 100000 ]; then
report_warn "NUMA 원격 메모리 미스 누적 다수 ($NUMA_MISS 건)"
echo -e " ${YELLOW}-> Kubelet --topology-manager-policy=single-numa-node 적용 검토${NC}"
else
report_pass "NUMA 원격 메모리 할당 상태 양호"
fi
fi
else
report_pass "단일 NUMA 노드 구조 (크로스 소켓 페널티 없음)"
fi
# 2-3. 커널 스핀락 및 시스템 CPU(%sy) 실시간 샘플링 (1초)
if command -v mpstat &>/dev/null; then
CPU_SY=$(mpstat 1 1 | awk '/Average:/ {print $5}')
if (( $(echo "$CPU_SY > 20.0" | bc -l 2>/dev/null || [ "${CPU_SY%.*}" -gt 20 ] && echo 1 || echo 0) )); then
report_warn "시스템 커널 CPU 점유율(%sy)이 높습니다 (${CPU_SY}%)"
echo -e " ${YELLOW}-> 락(Spinlock) 경합 또는 과도한 시스템 콜 의심 (perf top 점검 필요)${NC}"
else
report_pass "시스템 커널 CPU 점유율 안정적 (${CPU_SY}%)"
fi
fi
print_header "3. 커널 시스템 파라미터 & 테이블 용량 점검"
# 3-1. Conntrack 테이블 여유량 점검
if [ -f /proc/sys/net/netfilter/nf_conntrack_count ]; then
CONN_COUNT=$(cat /proc/sys/net/netfilter/nf_conntrack_count)
CONN_MAX=$(cat /proc/sys/net/netfilter/nf_conntrack_max)
USAGE_PCT=$(( CONN_COUNT * 100 / CONN_MAX ))
if [ "$USAGE_PCT" -lt 70 ]; then
report_pass "Conntrack 테이블 사용률 정상 (${USAGE_PCT}%, $CONN_COUNT / $CONN_MAX)"
else
report_fail "Conntrack 테이블 포화 위험 (${USAGE_PCT}%, $CONN_COUNT / $CONN_MAX)"
echo -e " ${RED}-> 개선 조치: sysctl -w net.netfilter.nf_conntrack_max=2097152${NC}"
fi
fi
# 3-2. 파일 디스크립터(File Descriptors) 점검
FILE_NR=$(cat /proc/sys/fs/file-nr | awk '{print $1}')
FILE_MAX=$(cat /proc/sys/fs/file-nr | awk '{print $3}')
FILE_USAGE_PCT=$(( FILE_NR * 100 / FILE_MAX ))
if [ "$FILE_USAGE_PCT" -lt 70 ]; then
report_pass "시스템 파일 디스크립터 여유 (사용량: $FILE_NR / 최대: $FILE_MAX)"
else
report_warn "파일 디스크립터 사용률 높음 (${FILE_USAGE_PCT}%)"
echo -e " ${YELLOW}-> 개선 조치: sysctl -w fs.file-max=20971520${NC}"
fi
# 3-3. 비대칭 라우팅 및 BGP ECMP 필터 (rp_filter)
RP_FILTER=$(sysctl -n net.ipv4.conf.all.rp_filter 2>/dev/null)
if [ "$RP_FILTER" -eq 2 ]; then
report_pass "BGP/ECMP 및 비대칭 라우팅용 rp_filter 정상 설정됨 (Loose Mode: 2)"
else
report_warn "rp_filter가 Strict(1) 또는 미설정($RP_FILTER) 상태입니다."
echo -e " ${YELLOW}-> BGP 다중 경로 패킷 드롭 방지: sysctl -w net.ipv4.conf.all.rp_filter=2${NC}"
fi
print_header "진단 완료: 이상 징후([WARN]/[FAIL]) 항목을 우선 조치하세요."
cat << 'EOF' > /usr/local/bin/check_bottlenecks.sh
# (위 스크립트 내용 붙여넣기)
EOF
chmod +x /usr/local/bin/check_bottlenecks.sh
sudo /usr/local/bin/check_bottlenecks.sh
Rx Ring Buffer 경고 시: 즉시 ethtool -G <NIC> rx 4096 tx 4096를 적용하고 /etc/sysconfig/network-scripts/ 또는 Systemd 서비스에 영구 반영합니다.CPU Throttling 실패 시: 지연 시간에 민감한 Pod(StarRocks, vLLM, PostgreSQL) 매니페스트에서 CPU Limit을 제거하거나 Kubelet static CPU Manager를 통해 Guaranteed QoS로 배포합니다.SoftIRQ time_squeeze 경고 시: 초당 패킷 수가 NAPI 예산을 넘어서고 있으므로 net.core.netdev_budget=600으로 완화합니다.===
운영 중인 K8s 워커 노드 및 베어메탈 시스템에서 부하를 주지 않고 실시간으로 NUMA 토폴로지 균형, 원격 메모리 참조(Remote Access), 커널 자동 밸런싱 영향도, 주요 Pod/프로세스별 NUMA 바인딩 상태를 진단하는 모니터링 스크립트(check_numa_efficiency.sh)입니다.
check_numa_efficiency.sh)#!/usr/bin/env bash
#
# NUMA Operational Efficiency & Locality Checker for Production Nodes
# Target OS: RHEL 9.x/10.x, Rocky, CentOS Stream
#
set -o pipefail
# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
print_header() {
echo -e "\n${BLUE}${BOLD}================================================================${NC}"
echo -e "${BLUE}${BOLD} $1${NC}"
echo -e "${BLUE}${BOLD}================================================================${NC}"
}
report_pass() { echo -e " [${GREEN}PASS${NC}] $1"; }
report_warn() { echo -e " [${YELLOW}WARN${NC}] $1"; }
report_info() { echo -e " [${BLUE}INFO${NC}] $1"; }
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}정확한 프로세스 cgroup 및 메모리 조회를 위해 root 권한으로 실행하세요.${NC}"
exit 1
fi
# 1. NUMA 기본 하드웨어 토폴로지 점검
print_header "1. NUMA 토폴로지 및 메모리 사용률 분포"
NUMA_COUNT=$(ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l)
if [ "$NUMA_COUNT" -le 1 ]; then
report_info "단일 NUMA 노드 시스템입니다. (크로스 소켓 통신 오버헤드 없음)"
exit 0
fi
echo -e " * 감지된 NUMA 노드 수: ${BOLD}${NUMA_COUNT}개${NC}\n"
printf " %-10s %-12s %-12s %-12s %-10s\n" "Node" "Total(MB)" "Used(MB)" "Free(MB)" "Usage(%)"
echo " ------------------------------------------------------------"
NODE_MEM_IMBALANCE=0
MIN_USAGE=100
MAX_USAGE=0
for ((i=0; i<NUMA_COUNT; i++)); do
MEMINFO="/sys/devices/system/node/node$i/meminfo"
if [ -f "$MEMINFO" ]; then
TOTAL_KB=$(awk '/MemTotal/{print $4}' "$MEMINFO")
FREE_KB=$(awk '/MemFree/{print $4}' "$MEMINFO")
USED_KB=$((TOTAL_KB - FREE_KB))
TOTAL_MB=$((TOTAL_KB / 1024))
USED_MB=$((USED_KB / 1024))
FREE_MB=$((FREE_KB / 1024))
USAGE_PCT=$((USED_KB * 100 / TOTAL_KB))
[ "$USAGE_PCT" -gt "$MAX_USAGE" ] && MAX_USAGE=$USAGE_PCT
[ "$USAGE_PCT" -lt "$MIN_USAGE" ] && MIN_USAGE=$USAGE_PCT
printf " %-10s %-12d %-12d %-12d %-9d%%\n" "node$i" "$TOTAL_MB" "$USED_MB" "$FREE_MB" "$USAGE_PCT"
fi
done
# 노드 간 메모리 불균형 점검 (편차 30% 초과 여부)
IMBALANCE_DIFF=$((MAX_USAGE - MIN_USAGE))
echo ""
if [ "$IMBALANCE_DIFF" -gt 30 ]; then
report_warn "NUMA 노드 간 메모리 사용 편차가 큽니다. (최대 편차: ${IMBALANCE_DIFF}%)"
echo -e " ${YELLOW}-> 특정 소켓에만 워크로드가 편중되어 조기 OOM 또는 Swap 위험이 있습니다.${NC}"
else
report_pass "NUMA 노드 간 메모리 사용률이 비교적 균등하게 분산되어 있습니다. (편차: ${IMBALANCE_DIFF}%)"
fi
# 2. 커널 자동 NUMA 밸런싱 설정 점검
print_header "2. 커널 NUMA 밸런싱 (Auto NUMA Balancing)"
AUTO_NUMA=$(sysctl -n kernel.numa_balancing 2>/dev/null || echo 0)
if [ "$AUTO_NUMA" -eq 0 ]; then
report_pass "kernel.numa_balancing = 0 (비활성화 상태: 대규모 분산 환경 권장)"
else
report_warn "kernel.numa_balancing = 1 (활성화 상태)"
echo -e " ${YELLOW}-> StarRocks, MinIO, K8s 환경에서는 백그라운드 페이지 스캔으로 인한 레이턴시 스파이크 유발 가능.${NC}"
echo -e " ${YELLOW}-> 조치: sysctl -w kernel.numa_balancing=0${NC}"
fi
# 3. 원격 메모리 접근(Remote Memory Access) 통계 실측
print_header "3. NUMA 메모리 할당 효율성 (numastat)"
if [ -f /sys/devices/system/node/node0/numastat ]; then
TOTAL_HIT=0
TOTAL_MISS=0
TOTAL_FOREIGN=0
for ((i=0; i<NUMA_COUNT; i++)); do
HIT=$(awk '/numa_hit/{print $2}' "/sys/devices/system/node/node$i/numastat")
MISS=$(awk '/numa_miss/{print $2}' "/sys/devices/system/node/node$i/numastat")
FOREIGN=$(awk '/numa_foreign/{print $2}' "/sys/devices/system/node/node$i/numastat")
TOTAL_HIT=$((TOTAL_HIT + HIT))
TOTAL_MISS=$((TOTAL_MISS + MISS))
TOTAL_FOREIGN=$((TOTAL_FOREIGN + FOREIGN))
done
# 원격 메모리 할당 비율 계산 (Miss Ratio)
TOTAL_ALLOC=$((TOTAL_HIT + TOTAL_MISS))
if [ "$TOTAL_ALLOC" -gt 0 ]; then
MISS_RATIO=$(awk "BEGIN {printf \"%.2f\", ($TOTAL_MISS / $TOTAL_ALLOC) * 100}")
echo -e " * 누적 로컬 할당 (numa_hit) : ${TOTAL_HIT}"
echo -e " * 누적 원격 할당 (numa_miss): ${TOTAL_MISS} (원격 할당률: ${BOLD}${MISS_RATIO}%${NC})"
echo -e " * 타 노드 대리 할당 (foreign): ${TOTAL_FOREIGN}\n"
if (( $(echo "$MISS_RATIO > 5.0" | bc -l 2>/dev/null || [ "${MISS_RATIO%.*}" -gt 5 ] && echo 1 || echo 0) )); then
report_warn "원격 메모리 할당률이 5%를 초과합니다 (${MISS_RATIO}%)."
echo -e " ${YELLOW}-> 로컬 소켓의 메모리가 부족하여 원격 소켓의 DRAM에 메모리를 할당했습니다 (UPI 트래픽 증가).${NC}"
else
report_pass "원격 메모리 할당률이 매우 낮습니다 (${MISS_RATIO}% - 로컬 메모리 적중률 우수)."
fi
fi
fi
# 4. 주요 고부하 워크로드(K8s Pod / 프로세스) NUMA 바인딩 진단
print_header "4. 주요 애플리케이션의 NUMA 바인딩 상태 (Top Memory Procs)"
echo -e " * 메모리 점유 상위 주요 프로세스의 CPU/Memory 노드 격리 상태:\n"
printf " %-8s %-18s %-16s %-12s %-10s\n" "PID" "COMMAND" "CPUs_ALLOWED" "MEMs_ALLOWED" "ISOLATION"
echo " ----------------------------------------------------------------------"
# 메모리 사용량 상위 8개 프로세스 추출 (커널 스레드 제외)
TOP_PIDS=$(ps -eo pid,%mem,comm --sort=-%mem | awk '$1 !~ /PID/ && $3 !~ /^\[.*\]$/ {print $1}' | head -n 8)
for PID in $TOP_PIDS; do
if [ -f "/proc/$PID/status" ]; then
COMM=$(cat "/proc/$PID/comm" 2>/dev/null | cut -c 1-17)
CPUS=$(awk -F':\t' '/Cpus_allowed_list/{print $2}' "/proc/$PID/status" 2>/dev/null)
MEMS=$(awk -F':\t' '/Mems_allowed_list/{print $2}' "/proc/$PID/status" 2>/dev/null)
# Mems_allowed_list가 단일 노드(예: 0 또는 1)인지, 다중 노드(예: 0-1)인지 검증
if [[ "$MEMS" =~ ^[0-9]+$ ]]; then
STATUS="${GREEN}Single-NUMA${NC}"
else
STATUS="${YELLOW}Cross-NUMA${NC}"
fi
printf " %-8s %-18s %-16s %-12s " "$PID" "$COMM" "$CPUS" "$MEMS"
echo -e "$STATUS"
fi
done
echo ""
report_info "Cross-NUMA로 표시된 워크로드는 두 소켓의 메모리를 동시에 참조하여 UPI 지연이 발생할 수 있습니다."
report_info "K8s Pod의 경우 'resources.requests == resources.limits'(정수 코어)로 Guaranteed QoS를 주면 단일 NUMA로 고정됩니다."
# 5. 주요 인터페이스(NIC) PCIe NUMA 지역성 매핑
print_header "5. 주요 물리 NIC의 NUMA 도메인 정렬"
PHYS_NICS=$(ip -o link show | awk -F': ' '{print $2}' | grep -vE '^(lo|veth|docker|cbr|flannel|cilium|lxc|cali|br-)')
for NIC in $PHYS_NICS; do
if [ -d "/sys/class/net/$NIC/device" ]; then
NIC_NUMA=$(cat "/sys/class/net/$NIC/device/numa_node" 2>/dev/null || echo "-1")
SPEED=$(cat "/sys/class/net/$NIC/speed" 2>/dev/null || echo "N/A")
if [ "$NIC_NUMA" -ge 0 ]; then
echo -e " * 인터페이스 ${BOLD}$NIC${NC} (${SPEED} Mbps) -> ${GREEN}NUMA Node $NIC_NUMA${NC}에 직결됨"
else
echo -e " * 인터페이스 ${BOLD}$NIC${NC} -> NUMA 노드 식별 불가 ($NIC_NUMA)"
fi
fi
done
print_header "진단 완료"
sudo bash check_numa_efficiency.sh
원격 할당률(numa_miss)이 5% 이상으로 높을 때podAntiAffinity 또는 Kubelet Single NUMA 설정)해야 합니다.Cross-NUMA(예: MEMs_allowed_list: 0-1)로 뜰 때requests.cpu == limits.cpu (정수 코어) 및 requests.memory == limits.memory로 맞추어 Kubelet CPU/Topology Manager가 Single-NUMA로 격리하도록 유도합니다.NIC의 NUMA Node와 워크로드 위치 불일치 시