디버깅 · 성능 측정
VPP 운영 중 실제로 마주치는 문제를 진단·복구하고, 성능을 측정·비교하는 방법을 정리합니다. 패킷 트레이싱·PCAP·이벤트 로거 같은 내부 계측 도구, 증상별 트러블슈팅 매트릭스, 흔한 설정 실수와 안티패턴, HTTP 프로토콜 레벨 트러블슈팅, TRex 기반 외부 벤치마크까지 다룹니다.
성능 측정 및 벤치마크
VPP 구성을 완료한 뒤, 실제 처리량과 지연을 측정하는 체계적인 방법을 소개합니다. 수치 없는 "잘 되는 것 같다"는 운영에서 통하지 않습니다.
TRex를 이용한 트래픽 생성
TRex는 DPDK 기반 고성능 트래픽 생성기로, VPP 벤치마크의 사실상 표준입니다. CSIT(Continuous System Integration Testing)에서도 TRex를 사용합니다.
# TRex STL(Stateless) 프로파일 — NAT44 부하 테스트
from trex_stl_lib.api import *
class STLS1(object):
def create_stream(self):
base_pkt = Ether() / IP(
src="192.168.1.10",
dst="198.51.100.10"
) / UDP(
sport=1024,
dport=80
) / ("X" * 16)
# 소스 IP와 포트를 변경하여 다양한 플로우 생성
vm = STLScVmRaw([
STLVmFlowVar(name="src_ip",
min_value="192.168.1.10",
max_value="192.168.1.250",
size=4, op="inc"),
STLVmWrFlowVar(fv_name="src_ip",
pkt_offset="IP.src"),
STLVmFlowVar(name="src_port",
min_value=1024,
max_value=60000,
size=2, op="inc"),
STLVmWrFlowVar(fv_name="src_port",
pkt_offset="UDP.sport"),
STLVmFixIpv4(offset="IP"),
])
return STLStream(
packet=STLPktBuilder(pkt=base_pkt, vm=vm),
mode=STLTXCont(pps=1000000) # 1Mpps부터 시작
)
def get_streams(self, **kwargs):
return [self.create_stream()]
def register():
return STLS1()
# TRex 실행 및 결과 확인
$ sudo ./t-rex-64 -i -c 4 --cfg /etc/trex_cfg.yaml
# TRex 콘솔에서:
trex> start -f nat44_test.py -m 5mpps -d 60
trex> stats
# TX: 5.00 Mpps, RX: 4.98 Mpps → 드롭율 0.04%
# VPP 측에서 동시에 확인:
vpp# show runtime
vpp# show interface GigabitEthernet0/8/0
vpp# show nat44 sessions count
VPP 내부 성능 카운터 해석
show runtime 출력의 각 열이 무엇을 뜻하는지 정확히 알아야 병목을 찾을 수 있습니다.
Name State Calls Vectors Suspends Clocks Vectors/Call
dpdk-input polling 1234567 9.87e7 0 22.1 79.9
ethernet-input active 1234567 9.87e7 0 8.3 79.9
ip4-input active 1234567 9.87e7 0 12.5 79.9
ip4-lookup active 1234567 9.87e7 0 15.2 79.9
nat44-ed-in2out active 1200000 9.60e7 0 44.3 80.0
ip4-rewrite active 1200000 9.60e7 0 9.8 80.0
GigE0/9/0-output active 1200000 9.60e7 0 5.1 80.0
GigE0/9/0-tx active 1200000 9.60e7 0 18.7 80.0
| 열 | 의미 | 정상 범위 | 이상 징후 |
|---|---|---|---|
| Calls | 노드가 호출된 횟수 | — | — |
| Vectors | 처리한 총 패킷 수 | — | 이전 노드보다 현저히 적으면 드롭 발생 |
| Vectors/Call | 호출당 평균 배치 크기 | 64~256 | <10이면 배치 효율 저하 |
| Clocks | 패킷당 평균 CPU 클럭 | 노드 의존 | 특정 노드가 100+ 이면 병목 |
| Suspends | 자발적 중단 횟수 | 0 | >0이면 스케줄링 문제 |
Clocks가 가장 높은 노드가 처리 비용의 병목입니다. NAT의 경우 nat44-ed-in2out이 보통 가장 높고, IPsec에서는 esp-encrypt/esp-decrypt가 지배적입니다. Vectors/Call이 낮다면 입력 큐가 적절히 배치되지 않아 벡터 효율이 떨어지는 것이므로 RSS 큐와 워커 매핑을 점검하세요.
디버깅 및 트러블슈팅
패킷 트레이싱
VPP의 패킷 트레이싱은 가장 강력한 디버깅 도구입니다. 패킷이 어떤 노드를 거쳐 어떤 결정을 받았는지 라인별로 확인할 수 있습니다.
# 트레이싱 활성화 (dpdk-input 노드에서 10개 패킷)
vpp# trace add dpdk-input 10
# 트래픽 발생 후 결과 확인
vpp# show trace
Packet 1
00:00:01:123456: dpdk-input
GigabitEthernet0/8/0 rx queue 0
buffer 0x9a340: current data 0, length 98, buffer-pool 0
trace_handle 0x1000001
l2-hdr-offset 0 ip4-hdr-offset 14
00:00:01:123458: ethernet-input ← L2 분류
IP4: fa:16:3e:aa:bb:cc -> fa:16:3e:dd:ee:ff
00:00:01:123459: ip4-input ← IPv4 처리 진입
TCP: 192.168.1.100 -> 10.0.0.1
tos 0x00, ttl 64, length 84, checksum 0x1234 dscp CS0 ecn NON_ECN
fragment id 0x0001, flags DONT_FRAGMENT
TCP: 45000 -> 80
seq 0x12345678 ack 0x00000000
flags [SYN] window 65535
00:00:01:123460: ip4-lookup ← FIB 룩업
fib 0 dpo-idx 7 flow hash: 0x00001234
TCP: 192.168.1.100 -> 10.0.0.1
00:00:01:123461: ip4-rewrite ← MAC 재작성
tx_sw_if_index 2 dpo-idx 7
adjacency rewrite: GigabitEthernet0/9/0
dst aa:bb:cc:dd:ee:01 src fa:16:3e:xx:yy:zz
00:00:01:123462: GigabitEthernet0/9/0-output ← 인터페이스 출력
GigabitEthernet0/9/0
IP4: fa:16:3e:xx:yy:zz -> aa:bb:cc:dd:ee:01
00:00:01:123463: GigabitEthernet0/9/0-tx ← TX 큐 전송
buffer 0x9a340: current data -14, length 112
| 디버깅 시나리오 | trace add 대상 | 확인 포인트 |
|---|---|---|
| 패킷이 VPP에 도달하지 않음 | dpdk-input 또는 af-xdp-input | RX 큐 수신 여부 |
| 라우팅(Routing) 문제 | ip4-input | ip4-lookup의 dpo-idx |
| NAT 변환 문제 | nat44-in2out-output | 세션 매칭, 주소 변환(Address Translation) |
| ACL 드롭 | acl-plugin-in-ip4-fa | 매치 규칙, deny 여부 |
| IPsec 터널(Tunnel) 문제 | ipsec-input-ip4 | SA 매칭, 복호화(Decryption) 에러 |
| 인터페이스 미출력 | interface-output | TX 큐 도달 여부 |
에러 카운터 분석
vpp# show errors
Count Node Reason
15230 dpdk-input no buffer available
842 ip4-input ip4 spoofed local-address packet drops
156 ip4-lookup ip4 destination lookup miss
23 acl-plugin-in-ip4-fa acl deny
5 nat44-in2out no translation
2 ipsec-input-ip4 SA not found
| 에러 | 노드 | 원인 | 해결 |
|---|---|---|---|
| no buffer | dpdk-input | 버퍼 풀 고갈 | buffers-per-numa 증가, hugepage 추가 |
| ttl-exceeded | ip4-input | TTL이 0에 도달 | 라우팅 루프 확인 |
| destination lookup miss | ip4-lookup | FIB에 경로 없음 | show ip fib로 경로 확인 |
| adjacency incomplete | ip4-rewrite | ARP 미해석 | show ip neighbor, ARP 상태 확인 |
| acl deny | acl-plugin | ACL 규칙에 의한 차단 | show acl-plugin acl로 규칙 확인 |
| no translation | nat44 | NAT 세션/주소 풀 소진 | show nat44 sessions, 주소 풀 확인 |
| SA not found | ipsec-input | IPsec SA 매칭 실패 | show ipsec sa, SPI 확인 |
| interface down | interface-output | 출력 인터페이스 down | show interface, admin up 확인 |
| worker handoff drops | handoff | 핸드오프 큐 오버플로 | 워커 부하 분산(Load Balancing), RSS 확인 |
| dpdk driver init fail | dpdk | NIC 바인딩 실패 | dpdk-devbind --status, VFIO/UIO 확인 |
PCAP 캡처
# 인입 패킷 PCAP 캡처
vpp# pcap trace rx max 1000 file rx-capture.pcap
# 송출 패킷 캡처
vpp# pcap trace tx max 1000 file tx-capture.pcap
# 드롭 패킷 캡처 (문제 진단에 가장 유용)
vpp# pcap trace drop max 1000 file drop-capture.pcap
# 특정 인터페이스만 캡처
vpp# pcap trace rx max 500 intfc GigabitEthernet0/8/0 file intf-rx.pcap
# 캡처 중지 및 저장
vpp# pcap trace off
# 파일 위치: /tmp/*.pcap
/tmp/*.pcap 파일을 Wireshark에서 바로 열 수 있습니다. pcap trace drop은 VPP가 드롭한 패킷만 캡처하므로, "왜 패킷이 전달되지 않는가?"에 대한 직접적인 답을 제공합니다.
이벤트 로거와 코어 덤프(Core Dump)
# 이벤트 로거 활성화
vpp# event-logger resize 1000000
vpp# event-logger restart
vpp# event-logger save /tmp/elog.dat
# GDB로 VPP 디버깅 (디버그 빌드 필요)
$ gdb /usr/bin/vpp_main core.12345
(gdb) bt # 백트레이스
(gdb) thread apply all bt # 모든 스레드 백트레이스
(gdb) frame 3 # 특정 프레임으로 이동
(gdb) p *b # vlib_buffer_t 내용 출력
# startup.conf에서 코어 덤프 활성화
# unix { full-coredump }
# coredump 크기 제한 해제: ulimit -c unlimited
coredump_filter를 조정하여 hugepage를 제외하면 코어 크기를 줄일 수 있습니다: echo 0x33 > /proc/$(pidof vpp)/coredump_filter
자주 발생하는 문제와 해결
| 문제 | 증상 | 진단 방법 | 해결 |
|---|---|---|---|
| 버퍼 고갈 | RX 드롭 급증 | show errors no-buffer | buffers-per-numa 증가 |
| DPDK 초기화 실패 | VPP 시작 실패 | /var/log/vpp/vpp.log | VFIO/UIO 바인딩, IOMMU 확인 |
| Hugepage 부족 | VPP 시작 실패 또는 OOM | cat /proc/meminfo | hugepage 추가 할당 |
| 워커 정지 | 특정 큐 처리 중단 | show runtime Suspends | 배리어 장기 홀드 확인 |
| ARP 미해석 | adjacency incomplete | show ip neighbor | 연결성 확인, proxy-arp |
| NAT 세션 소진 | no translation 에러 | show nat44 sessions | 세션 한도/타이머(Timer) 조정 |
| RSS 불균형 | 일부 워커만 과부하 | show runtime per-worker | RSS 해시 키 변경, handoff |
| MTU 불일치 | 패킷 드롭/단편화(Fragmentation) | show hardware | 인터페이스 MTU 통일 |
| CLI 소켓 거부 | vppctl 연결 실패 | 소켓 권한 확인 | api-segment { gid vpp } |
| 플러그인 충돌 | VPP 크래시 | 코어 덤프 분석 | 문제 플러그인 비활성, 버전 확인 |
트러블슈팅 가이드
VPP 실전 구성에서 자주 만나는 문제와 체계적인 진단 방법을 정리합니다.
패킷이 전달되지 않는 경우
# 1단계: 인터페이스 상태 확인
vpp# show interface
# 모든 인터페이스가 'up'인지 확인
# 'admin-down'이면: set interface state up
# 2단계: 수신 패킷 확인
vpp# clear interface counters
# 트래픽 생성 후:
vpp# show interface
# rx-pkts가 증가하는지 확인
# 증가하지 않으면: DPDK 바인딩, Hugepage, NIC 드라이버 문제
# 3단계: 패킷 추적
vpp# clear trace
vpp# trace add dpdk-input 20
# 트래픽 생성 후:
vpp# show trace
# 패킷이 어느 노드에서 드롭되는지 확인
# 'error' 또는 'drop' 노드에서 끝나면 해당 에러 메시지 확인
# 4단계: 에러 카운터 확인
vpp# show errors
# 가장 높은 에러 카운터부터 추적
# 5단계: FIB(라우팅 테이블) 확인
vpp# show ip fib # 전체 라우팅 테이블
vpp# show ip fib 198.51.100.0/24 # 특정 서브넷 경로
자주 발생하는 문제와 해결법
| 증상 | 진단 명령 | 원인 | 해결 |
|---|---|---|---|
| VPP 시작 실패 | journalctl -u vpp | Hugepage 부족, DPDK 바인딩 실패 | echo 1024 > /proc/sys/vm/nr_hugepages, dpdk-devbind.py -b vfio-pci |
| DPDK 인터페이스 미표시 | show interface | NIC가 DPDK에 바인딩되지 않음 | dpdk-devbind.py --status로 확인, modprobe vfio-pci |
| ARP 미응답 | show ip neighbors | 인터페이스에 IP 미할당, uRPF 드롭 | set interface ip address 재확인. uRPF/ip4-local 트랩의 자세한 진단은 데이터 경로 — L3 라우팅 및 VRF 기초 참조 |
| NAT 세션 미생성 | show nat44 sessions | in/out 방향 설정 오류 | set interface nat44 in/out 방향 교차 확인 |
| memif 연결 안됨 | show memif | 소켓 경로/권한, master/slave 역할 | 양쪽 소켓 파일 경로 동일 확인, 권한 chmod 770 |
| IPsec 단방향만 동작 | show ipsec sa detail | SPI/키 불일치, 라우팅 미설정 | 양쪽 설정 교차 검증, show ip fib |
| 처리량 기대 이하 | show runtime | 워커 불균형, 배치 효율 저하 | RSS 큐 수와 워커 수 일치, isolcpus 확인 |
패킷 캡처 (pcap)
패킷 내용을 직접 확인해야 할 때는 VPP의 내장 pcap 기능을 사용합니다. tcpdump와 달리 커널을 거치지 않으므로 DPDK 인터페이스의 패킷도 캡처할 수 있습니다.
# 특정 인터페이스의 수신 패킷 캡처
vpp# pcap trace rx tx max 1000 intfc GigabitEthernet0/8/0 \
file /tmp/vpp-capture.pcap
# 트래픽 발생 후 캡처 중지
vpp# pcap trace off
# 호스트에서 Wireshark로 분석
$ wireshark /tmp/vpp-capture.pcap
# 또는 tcpdump로 빠르게 확인
$ tcpdump -r /tmp/vpp-capture.pcap -nn | head -20
# 그래프 노드 단위 캡처 (더 세밀한 분석)
vpp# pcap trace rx tx max 500 \
classify-table-index 0 \
file /tmp/vpp-classified.pcap
max를 작게 설정하고, 분석이 끝나면 즉시 pcap trace off로 중지하세요. 대량 캡처가 필요하면 trace add와 show trace로 노드 경로만 먼저 확인하는 것이 효율적입니다.
흔한 실수와 안티패턴
VPP 운영 시 자주 발생하는 실수와 권장 해결 방법을 정리합니다. 특히 초기 구축 시 환경 설정 오류가 대부분의 장애 원인입니다.
Hugepage 설정 누락/부족
| 증상 | 원인 | 해결 방법 |
|---|---|---|
VPP 시작 실패: rte_eal_init error | Hugepage 미할당 | 커널 부트 파라미터에 hugepages=2048 추가 |
| VPP 시작 후 버퍼 부족 경고 | Hugepage 부족 | 워커 수 × 4GB 이상 확보 (NUMA 당) |
| DPDK 인터페이스 생성 실패 | 1G hugepage만 할당, 2M 미할당 | hugeadm --pool-pages-min 2MB:2048 |
| NUMA 편향 성능 저하 | NIC NUMA와 hugepage NUMA 불일치 | NIC NUMA 노드에 hugepage 분배: echo 1024 > /sys/devices/system/node/node0/hugepages/.../nr_hugepages |
# Hugepage 상태 확인
$ cat /proc/meminfo | grep -i huge
HugePages_Total: 2048
HugePages_Free: 1536
HugePages_Rsvd: 0
Hugepagesize: 2048 kB
# 부팅 시 영구 설정 (GRUB)
GRUB_CMDLINE_LINUX="default_hugepagesz=2M hugepagesz=2M hugepages=2048 \
hugepagesz=1G hugepages=4 iommu=pt intel_iommu=on"
# 런타임 할당 (재부팅 없이)
$ echo 2048 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
$ sudo mkdir -p /dev/hugepages
$ sudo mount -t hugetlbfs none /dev/hugepages
DPDK 인터페이스 바인딩 오류
| 드라이버 | IOMMU 필요 | 장점 | 주의사항 |
|---|---|---|---|
| vfio-pci (권장) | 예 (intel_iommu=on) | DMA 격리, 보안, SR-IOV 지원 | IOMMU 미활성 시 바인딩 실패 |
| uio_pci_generic | 아니요 | IOMMU 없는 환경에서 동작 | 보안 격리 없음, root 필요 |
| igb_uio (DPDK) | 아니요 | 레거시 NIC 호환 | 별도 모듈 빌드 필요, 유지보수 중단 추세 |
# 잘못된 예: IOMMU 없이 vfio-pci 바인딩 시도
$ sudo dpdk-devbind.py --bind=vfio-pci 0000:00:08.0
# Error: Cannot bind to driver vfio-pci: No IOMMU group
# 올바른 해결 방법 1: IOMMU 활성화
# GRUB에 intel_iommu=on iommu=pt 추가 후 재부팅
# 올바른 해결 방법 2: IOMMU 없는 환경에서 vfio 사용
$ sudo modprobe vfio enable_unsafe_noiommu_mode=1
$ echo 1 | sudo tee /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
$ sudo dpdk-devbind.py --bind=vfio-pci 0000:00:08.0
# NIC 현재 상태 확인
$ dpdk-devbind.py --status
Network devices using DPDK-compatible driver
============================================
0000:00:08.0 'Virtio network device' drv=vfio-pci
Network devices using kernel driver
====================================
0000:00:03.0 'Virtio network device' drv=virtio-pci
dpdk-devbind.py --bind=virtio-pci 0000:00:08.0을 사용합니다. 관리 인터페이스(SSH 접속용)를 실수로 DPDK에 바인딩하면 원격 접속이 끊어지므로 반드시 OOB(Out-of-Band) 관리 인터페이스를 별도 확보하세요.
startup.conf 워커 스레드 / CPU 핀닝 실수
| 실수 패턴 | 증상 | 올바른 설정 |
|---|---|---|
main-core와 corelist-workers 중복 | 메인/워커 경합, 불안정 | 겹치지 않는 CPU 코어 할당 |
| 하이퍼스레딩 형제 코어에 워커 배치 | 캐시 경합으로 성능 50% 저하 | 물리 코어만 사용: lscpu -e로 확인 |
| NUMA 노드 교차 배치 | 원격 메모리 접근 지연 | NIC와 같은 NUMA 노드의 코어 사용 |
| 워커 수 > RSS 큐 수 | 일부 워커 유휴 | num-rx-queues와 워커 수 일치 |
| isolcpus 미적용 | OS 프로세스가 VPP 코어에 스케줄됨 | 커널 부트 파라미터: isolcpus=1-3 |
# CPU 토폴로지 확인
$ lscpu -e
CPU NODE SOCKET CORE L1d L2 L3 ONLINE
0 0 0 0 0 0 0 yes ← main-core (관리용)
1 0 0 1 1 1 0 yes ← worker 0
2 0 0 2 2 2 0 yes ← worker 1
3 0 0 3 3 3 0 yes ← worker 2
4 0 0 0 0 0 0 yes ← HT sibling of core 0 (사용 금지)
# NIC NUMA 노드 확인
$ cat /sys/bus/pci/devices/0000:00:08.0/numa_node
0
# 올바른 startup.conf
cpu {
main-core 0 /* 관리 전용 */
corelist-workers 1-3 /* NIC와 같은 NUMA, HT 제외 */
skip-cores 0 /* 기본값 유지 */
}
# 커널 부트 파라미터 (VPP 코어 격리)
GRUB_CMDLINE_LINUX="isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3"
벡터 크기와 배치 처리 오해
VPP의 벡터 처리 모델에서 벡터 크기(VLIB_FRAME_SIZE)는 한 번에 처리되는 패킷 묶음의 크기입니다. 벡터가 클수록 I-cache 효율이 높아지지만, 지연는 증가합니다.
| 오해 | 실제 |
|---|---|
| 벡터 크기가 클수록 항상 좋다 | 처리량은 증가하지만 지연도 증가. 실시간 트래픽(VoIP 등)에서는 VLIB_FRAME_SIZE를 줄이는 것이 유리 |
| 벡터 크기를 임의로 조절할 수 있습니다 | 입력 노드의 수신 버스트 크기(DPDK rx-burst)가 실질적 벡터 크기 결정. show runtime의 Vectors/Call로 확인 |
| 모든 노드가 동일한 벡터 크기를 처리 | 분기 노드(classify, ACL)에서 벡터가 분할될 수 있음. show runtime에서 노드별 Vectors/Call 차이 확인 |
| 패킷이 없으면 CPU를 쉰다 | 기본 DPDK 폴링은 항상 100% CPU 사용. poll-sleep-poll 또는 interrupt mode로 완화 가능 (아래 성능 최적화 참조) |
show runtime의 Vectors/Call 값이 1에 가까우면 패킷이 산발적으로 도착하는 것이므로 벡터 처리의 이점이 줄어듭니다. 이 경우 배치 크기를 늘리거나(num-rx-desc 2048), 트래픽 패턴을 분석하여 폴링 주기를 조정하세요.
memif 연결 시 소켓 권한 문제
# 흔한 오류: Permission denied
vpp# create interface memif id 0 socket-id 1 slave
# create memif: connect error (Permission denied)
# 원인 진단
$ ls -la /run/vpp/
srwxrwx--- 1 root root 0 memif-chain.sock ← 그룹 접근 필요
# 해결: 공통 그룹 사용
$ sudo groupadd vpp-memif
$ sudo usermod -aG vpp-memif vpp-user1
$ sudo usermod -aG vpp-memif vpp-user2
# startup.conf에서 그룹 지정
unix {
gid vpp-memif
}
# 소켓 디렉터리 권한 확인 (실행 권한 필수)
$ sudo chmod 2770 /run/vpp/
$ sudo chgrp vpp-memif /run/vpp/
# 연결 상태 확인
vpp# show memif
interface memif1/0
socket-id 1 id 0 mode ethernet
status: connected ← 정상
ring 0: region 0 num-s 2048 ...
link up
L7 트러블슈팅 — HTTP 파싱 오류부터 워커 편중까지
L4 트러블슈팅이 패킷 드롭·재전송·MTU 같은 전송 계층 문제에 집중한다면, L7 트러블슈팅은 한 단계 더 위에서 시작합니다. 이 절은 운영에서 자주 마주치는 6가지 L7 증상을 골라, 증상 → 가설 → 진단 명령 → 해결 패턴 순으로 정리합니다.
- 호스트 스택 — 트러블슈팅 —
show session verbose컬럼 해석, TLS CPS 저하 진단, 세션 누수, 워커 편향의 자세한 절차 - QUIC — 디버깅 및 트러블슈팅 — QUIC 0-RTT, 패킷 손실, 핸드셰이크 실패 진단
- TLS · 프록시 — 인증서·SNI·TLS 종단 관련 깊이 있는 디버깅
증상별 진단 매트릭스
| 증상 | 1차 의심 | 진단 명령 | 해결 |
|---|---|---|---|
| 특정 클라이언트만 400 폭증 | HTTP 파서 엄격성 / 헤더 한도 | show stat /http/parse_errors | 헤더 한도 상향 또는 클라이언트 수정 |
| p99 지연만 튐 | 워커 편중 / 캐시 미스 | show stat /http/in_flight per-worker | RSS 재구성 또는 캐시 튜닝 |
| 5xx 단속 발생 | 업스트림 실패 / 회로 차단 | show http gateway upstream | 업스트림 헬스체크 강화 |
| HTTP/2 스트림 멈춤 | 흐름 제어 윈도 고갈 | show http2 stream <id> | SETTINGS_INITIAL_WINDOW_SIZE 상향 |
| TLS 핸드셰이크 실패율 상승 | 인증서 만료 / SNI 미일치 | show stat /tls/handshakes_total | 인증서 갱신 / SNI 설정 |
| 캐시 적중률 급락 | Vary 헤더 변화 / 키 폭증 | show http gateway cache | Vary 정규화 / 키 설계 재검토 |
L7 pcap — vppctl trace + 외부 분석
VPP는 자체 패킷 트레이스(trace add)와 pcap 캡처(pcap trace)를 모두 제공합니다. L7 문제 해결에는 두 도구를 조합해야 합니다.
# 1) HTTP 게이트웨이 인터페이스에서 캡처
vppctl pcap trace rx tx max 5000 \
intfc TenGigabitEthernet0/0/0 \
file /tmp/http-debug.pcap \
filter classify table 0 match l3 ip4 dst 192.0.2.10 \
l4 tcp dst-port 443
# 2) 캡처 시작 후 재현
curl -k -v https://192.0.2.10/api/v1/version
# 3) 캡처 종료
vppctl pcap trace off
# 4) Wireshark에서 분석
wireshark /tmp/http-debug.pcap
# - TLS pre-master secret 있으면 복호화 후 HTTP 메시지 확인
# - HTTP/2는 Wireshark가 자동 해석 (SETTINGS·HEADERS·DATA 프레임)
# 5) 그래프 노드 트레이스 (헤더 파싱 단계까지 추적)
vppctl trace add dpdk-input 100
vppctl show trace | grep -A 20 'http-static'
show 명령 카드
# 세션 레이어 상태
vppctl show session verbose 2 # 모든 활성 세션
vppctl show session thread 0 verbose # 워커 0만
# HTTP/2 스트림 상세
vppctl show http2 conn
vppctl show http2 stream <conn-id> <stream-id>
# - state: open / half-closed / closed
# - recv_window / send_window
# - bytes_sent / bytes_recv
# TLS 세션
vppctl show tls
vppctl show tls openssl ctx 0
# - cipher / version / sni / alpn
# - resumption: full / psk / 0-rtt
# 게이트웨이 라우팅·캐시
vppctl show http gateway routes
vppctl show http gateway upstream
vppctl show http gateway cache top 20
# 워커 편중 진단
for i in 0 1 2 3; do
echo "Worker $i:"
vppctl show session thread $i | grep -c 'state ESTABLISHED'
done
자주 마주치는 증상 — 해결 레시피 4가지
레시피 1: 특정 vhost만 5xx
rate(http_requests_total{status=~"5..", vhost="api.example.com"}[5m])추세 확인- 같은 시간대
http_upstream_failures_total{upstream="api-backend-1"}동반 상승 여부 - 업스트림 헬스체크 결과(
show http gateway upstream)에서 응답 시간 분포 확인 - 해결: 업스트림 health check interval 단축, 회로 차단 임계 강화, 트래픽 일부를 백업 업스트림으로 이동
레시피 2: HTTP/2 스트림이 멈춤
show http2 stream에서send_window=0확인- 같은 연결의 다른 스트림도 같은 상태면 연결 윈도 고갈, 한 스트림만이면 스트림 윈도 고갈
- 대형 응답 BDP 계산:
BDP = bandwidth × RTT. 100Mbps × 100ms = 1.25MB - 해결:
SETTINGS_INITIAL_WINDOW_SIZE를 BDP × 2 이상으로 설정. VPP CLI 또는 startup.conf의 http2 절
레시피 3: 워커 편중으로 한 워커만 100%
vppctl show runtime에서 워커별 vector rate 비교 — 한 워커만 0.9 이상이면 편중show session thread N에서 활성 세션 수 비교- 가설 1: NIC RSS 키가 비대칭 →
show hardware-interfaces에서 RSS 큐 분포 확인 - 가설 2: 한 클라이언트가 단일 연결로 거대한 HTTP/2 스트림 폭주 →
SETTINGS_MAX_CONCURRENT_STREAMS하향 - 해결: RSS 대칭 해시(Toeplitz 키 0x6d5a) 적용, 또는 HAProxy 같은 L7 LB로 사전 분산
레시피 4: 캐시 적중률 급락
show http gateway cache의 entries / bytes / evictions 추세 확인- evictions 폭증 + 적중률 감소 → 캐시 크기 부족 또는 키 폭증
- 키 폭증 가설: 새 배포 후
Vary헤더에 변동성 큰 헤더(예:User-Agent) 추가됨 - 해결:
Vary정규화, 캐시 크기 상향, 또는 stale-while-revalidate 도입으로 백그라운드 갱신
http_request_duration_seconds p99, traffic = http_requests_total rate, errors = 5xx 비율, saturation = http_in_flight + 워커 vector rate. 이 네 가지만 모니터링해도 운영 사고의 80%는 즉시 감지할 수 있습니다.
Common Pitfalls
- 총량 메트릭만 봄 — 전체 5xx가 0.5%여도 특정 vhost나 path는 50%일 수 있습니다. 라벨로 분해해야 합니다.
- p99 대신 평균만 봄 — 평균은 long-tail을 숨깁니다. 사용자 체감은 p95~p99에서 결정됩니다.
- 워커 단위 미관찰 — 노드 전체 CPU가 50%여도 한 워커는 100%일 수 있습니다. per-worker 게이지가 필수입니다.
- pcap 대용량 — 100Gbps 트래픽을 그대로 캡처하면 디스크가 즉시 가득 찹니다. 반드시 classify table로 5튜플 필터를 좁히고, max를 작게 잡아야 합니다.
- 메트릭 카디널리티 폭발 — 라벨에 path를 그대로 넣으면 카디널리티가 폭발합니다.
/api/v1/users/12345→/api/v1/users/:id로 정규화해야 합니다.
디버깅 보조 도구 — PG · nsim · BPF Trace Filter
VPP에는 장애 재현과 조건부 트레이스를 돕는 소규모 도구 세 개가 있습니다. 일상 운영에서 자주 언급되지 않지만 어려운 버그를 잡을 때 결정적인 역할을 합니다. 상시 관측 도구(perfmon/sFlow/IPFIX 등)는 관측성 · 원격 측정에서 다룹니다.
Packet Generator (PG) — 재현 주입
문제를 겪은 패킷 패턴을 외부 장비 없이 VPP 안에서 직접 재현하고 싶을 때 PG가 최적입니다. 헤더 조합을 스크립트로 정의하고, 원하는 비율로 특정 노드에 주입할 수 있어 엣지 케이스 회귀 테스트에도 적합합니다.
vpp# packet-generator new {
name bug-repro
limit 100
node ip4-input
size 64-1500
data { IP4: 10.0.0.1 -> 10.0.0.2
UDP: 1234 -> 5678
incrementing 100 }
}
vpp# trace add pg-input 100
vpp# packet-generator enable-stream bug-repro
vpp# show trace
장점은 외부 트래픽 생성기(TRex 등) 없이도 패킷 레벨 정확도로 동일 시나리오를 재현할 수 있다는 점입니다. 단점은 pps가 제한적이라 성능 재현 테스트에는 부적합합니다.
nsim — 네트워크 지연 시뮬레이션
nsim은 인터페이스 송신 경로에 지연·대역폭 한계·손실을 주입해 WAN 환경을 재현합니다. 리눅스 netem의 VPP 버전으로, TCP 혼잡 제어가 특정 조건에서 어떻게 동작하는지 검증할 때 결정적입니다.
vpp# nsim output-feature enable-disable <if>
vpp# set nsim delay 100.0 ms bandwidth 10.0 mbit packets-per-drop 1000
vpp# show nsim
실전 시나리오 예:
- 클라이언트가 고속 백본을 거쳐 접속할 때만 발생하는 TLS 재전송 버그 재현
- HTTP/3 QUIC connection migration의 handoff 지연 측정
- TCP BBR vs CUBIC 혼잡 제어 차이 검증
BPF Trace Filter — 조건부 트레이스
일반 trace add는 지정 노드에 들어오는 모든 패킷을 캡처합니다. 트래픽이 많으면 금세 버퍼가 가득 차고, 정작 문제 패킷은 놓치기 쉽습니다. BPF Trace Filter는 tcpdump 문법으로 트레이스 대상을 정밀 필터링합니다.
vpp# set trace filter function-name vlib_trace_filter_bpf expr "host 192.0.2.100 and tcp port 443"
vpp# trace add dpdk-input 1000
# 문제 재현 후
vpp# show trace
BPF 식은 libpcap 문법을 그대로 사용하므로, tcpdump -d로 익숙한 사람은 바로 쓸 수 있습니다. 고PPS 운영 환경에서 특정 플로우만 정밀 관찰할 수 있어 진단 시간이 크게 단축됩니다.
실전 예제 — ip4-rewrite 간헐적 드롭 추적
시나리오: 운영 환경에서 ip4-rewrite 노드가 특정 목적지(10.0.0.5)로 향하는 패킷을 간헐적으로 드롭한다는 신고가 들어왔습니다. 전체 트레이스를 켜면 초당 수십만 패킷이 기록되어 버퍼가 즉시 차버립니다. BPF Trace Filter로 문제 플로우만 골라냅니다.
1단계 — BPF 필터 적용 및 트레이스 시작
# ip4-rewrite 진입 패킷 중 dst=10.0.0.5 인 것만 캡처
vpp# set trace filter function-name vlib_trace_filter_bpf expr "dst host 10.0.0.5"
vpp# clear trace
vpp# trace add dpdk-input 500
# 혹은 tap 인터페이스가 rx 소스라면:
# vpp# trace add virtio-input 500
2단계 — 문제 재현
# 외부 호스트에서 10.0.0.5로 패킷 전송 (ping 또는 iperf)
ping -c 20 10.0.0.5 -I eth1
3단계 — 트레이스 출력 읽기
vpp# show trace
------------------- 정상 패킷 (목적지 도달) -------------------
Packet 1
00:00:00:001234 dpdk-input: sw_if_index 1, next-index 4
ip4-input: next ip4-rewrite
ip4-rewrite: adj-idx 12, next-index 0
Rewrite: 00:11:22:aa:bb:cc -> 10.0.0.5 (GigabitEthernet0/0/1)
GigabitEthernet0/0/1-output: 82 bytes
------------------- 드롭된 패킷 -------------------
Packet 7
00:00:00:003210 dpdk-input: sw_if_index 1, next-index 4
ip4-input: next ip4-rewrite
ip4-rewrite: adj-idx 12, next-index 1 <-- next-index 1 = ip4-drop
drop: no_route
error-drop: ip4-rewrite: no_route
트레이스 출력 읽는 법:
| 필드 | 의미 |
|---|---|
next-index | 다음으로 이동한 그래프 노드 인덱스. 0이면 정상 전달, 1 이상이면 드롭·에러 경로인 경우가 많음 |
adj-idx | adjacency 테이블 인덱스. show ip adjacencies로 매핑 확인 |
drop: no_route | FIB에 해당 목적지 경로 없음 — show ip fib 10.0.0.5로 경로 상태 점검 |
error-drop | 최종 drop 노드. 직전 노드가 실제 원인 노드 |
4단계 — 원인 확인 및 수정
# adjacency 확인
vpp# show ip adjacencies 12
# FIB 경로 상태 확인
vpp# show ip fib 10.0.0.5
# 경로가 없으면 정적 경로 추가
vpp# ip route add 10.0.0.5/32 via 192.168.1.1 GigabitEthernet0/0/0
# 필터 제거
vpp# set trace filter function-name vlib_trace_filter_bpf expr ""
tcpdump -d "dst host 10.0.0.5 and icmp"로 먼저 BPF 바이트코드를 확인하면 표현식이 의도대로 컴파일되는지 검증할 수 있습니다.
도구 선택 요약
| 상황 | 권장 |
|---|---|
| 특정 패킷 시나리오 재현 | Packet Generator + show trace |
| WAN 조건 아래서 버그 검증 | nsim + iperf3 |
| 폭주 중인 트래픽에서 한 플로우만 보기 | BPF Trace Filter |
| 노드 성능 hot spot 찾기 | perfmon topdown |
| 워커 간 시간 분포 보기 | 이벤트 로그 + G2 |
GDB로 플러그인 디버깅
패킷 트레이스나 카운터만으로 원인을 좁힐 수 없을 때는 GDB를 사용하여 실행 중인 VPP 프로세스에 직접 붙거나, 재현 시나리오를 빌드한 뒤 코어 덤프를 분석합니다. 이 섹션에서는 플러그인 개발자 관점에서 자주 쓰는 GDB 워크플로를 다룹니다.
실행 중인 VPP에 GDB 붙이기
VPP는 멀티스레드 프로세스이므로 attach 직후 모든 스레드가 일시 중지됩니다. 운영 트래픽에 영향을 주므로 반드시 스테이징 환경에서 진행합니다.
# VPP 프로세스 PID 확인
pgrep vpp
# GDB로 실행 중인 VPP에 attach
sudo gdb -p $(pgrep vpp)
# attach 후 전체 스레드 목록 확인
(gdb) info threads
# Thread 1은 메인, Thread 2+ 는 워커 스레드
# 특정 스레드로 전환
(gdb) thread 2
# 현재 스레드 콜 스택 확인
(gdb) bt
노드 함수에 브레이크포인트 설정
VPP 그래프 노드의 dispatch 함수는 VLIB_NODE_FN 매크로로 정의됩니다. 실제 심볼 이름은 빌드 시스템에 따라 달라지지만, 패턴은 일관적입니다.
# 플러그인 so 파일을 GDB에 수동 로드 (이미 attach된 경우 자동 로드됨)
(gdb) sharedlibrary my_plugin
# 노드 함수 심볼 검색 (심볼 이름 패턴 확인)
(gdb) info functions my_node
# 브레이크포인트 설정 — 함수명 방식
(gdb) break my_node_fn
# 소스 파일 + 라인 방식 (디버그 빌드 필요)
(gdb) break src/plugins/my_plugin/my_node.c:85
# 조건부 브레이크포인트 — 특정 인터페이스 인덱스일 때만
(gdb) break my_node_fn if frame->sw_if_index == 3
# 계속 실행
(gdb) continue
vlib_buffer_t 구조 확인:
# 브레이크포인트에 걸렸을 때 버퍼 내용 출력
(gdb) p *b0
# 패킷 데이터 덤프 (현재 위치에서 64바이트)
(gdb) x/64xb (char*)b0 + b0->current_data
# vlib_frame_t 내 버퍼 인덱스 배열 출력
(gdb) p/d from[0]@16
VPP GDB 헬퍼
VPP 소스 트리에는 GDB 매크로와 Python pretty-printer가 포함되어 있습니다. 디버그 빌드 환경에서 사용할 수 있습니다.
# VPP 소스의 GDB 초기화 파일 로드
# (빌드 디렉터리에 gdbinit 파일이 생성됨)
(gdb) source /path/to/vpp/build-root/build-vpp_debug-native/vpp/vpp-api/gdb_hooks.py
# 로드 후 사용 가능한 커스텀 커맨드 예시
(gdb) vpp_buffer b0 # vlib_buffer_t 요약 출력
(gdb) vpp_trace # 현재 패킷의 트레이스 출력
-DCMAKE_BUILD_TYPE=Debug, Makefile 빌드라면 make build-vpp_debug를 사용하십시오. 패키지 설치 환경이라면 vpp-dbg 패키지를 추가로 설치합니다.
코어 덤프 사후 분석
# startup.conf에서 코어 덤프 활성화
# unix { full-coredump }
# 프로세스 locked memory 제한 해제 (ulimit)
ulimit -c unlimited
# 코어 덤프 파일로 GDB 실행
sudo gdb /usr/bin/vpp /var/lib/vpp/vpp.core
# 크래시 시점의 콜 스택 확인
(gdb) bt full
# 전체 스레드 스택 한 번에 보기
(gdb) thread apply all bt
ASAN / Valgrind 활용
GDB 브레이크포인트보다 메모리 버그(사용 후 해제, 버퍼 오버플로)를 찾는 데는 AddressSanitizer(ASAN)이나 Valgrind가 더 효과적입니다.
# ASAN 빌드 — CMake 옵션 (VPP 소스 빌드 시)
cmake -DVPP_ENABLE_SANITIZE_ADDR=ON ..
make -j$(nproc)
# ASAN 환경 변수 설정 후 VPP 실행
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0 sudo -E /usr/bin/vpp -c /etc/vpp/startup.conf
# Valgrind — 성능 오버헤드가 매우 크므로 단위 테스트나 최소 재현에만 사용
sudo valgrind --tool=memcheck --leak-check=full \
--suppressions=/path/to/vpp/extras/valgrind/vpp.supp \
/usr/bin/vpp -c /etc/vpp/startup.conf
clib_mem)와 hugepage 매핑은 Valgrind 기본 suppression 없이 수천 개의 오탐을 발생시킵니다. VPP 소스 트리의 extras/valgrind/vpp.supp suppression 파일을 반드시 함께 사용하십시오. ASAN은 VPP 내장 할당자를 교체하므로 Valgrind보다 결과가 깔끔한 편입니다.
트러블슈팅 3축 매트릭스
증상(symptom) × 계층(layer) × 도구(tool) 3차원 접근법은 VPP 장애 대응 시간을 단축하는 체계적 진단 프레임워크입니다. 먼저 관찰된 증상에서 의심 계층을 좁히고, 해당 계층에 맞는 도구를 순서대로 적용합니다. "모든 것을 동시에 보는" 접근 대신, 1차 확인으로 가설을 세우고 2차 확인으로 검증하는 순서를 지키면 불필요한 수집 오버헤드를 줄일 수 있습니다.
증상별 계층 매핑
| 증상 | 의심 계층 | 1차 확인 명령 | 2차 확인 명령 |
|---|---|---|---|
| 패킷 드롭 | L2/L3 | show errors | show trace |
| 지연시간 급증 | L3/L4 | show runtime | perfmon |
| 연결 불가 | L4/TLS | show session | show tcp |
| 처리량 저하 | 드라이버/버퍼 | show buffers | show hardware-interfaces |
| CPU 100% | 노드 | show runtime | perf top |
계층 × 도구 매트릭스
| 계층 | trace | pcap | errors | perfmon | gdb |
|---|---|---|---|---|---|
| L1 (드라이버) | dpdk-input / af-xdp-input 노드 대상 | RX 큐 통계 확인 후 pcap 보조 | rx-miss, no-mbuf 카운터 주목 | cache miss · CPI 측정 | DPDK PMD 브레이크포인트 |
| L2 (이더넷) | ethernet-input 노드 추적 | L2 프레임 덤프 | bad-checksum, unknown-etype | L2 노드 클럭 수 | ethernet_input 함수 진입점 |
| L3 (IP) | ip4-input · ip6-input | ip4/ip6 필터로 캡처 | ip4-error, ttl-expired | FIB 룩업 사이클 | ip4_lookup_inline 중단점 |
| L4 (TCP/UDP) | tcp-input · udp-input | 포트 필터 pcap | tcp-drop, udp-no-port | 세션 처리 클럭 | tcp_input 함수 상태 검사 |
| TLS | tls-input · tls-output | 평문 레코드 캡처(복호화 전) | tls-error, handshake-fail | 암복호화 사이클 | OpenSSL / Picotls 콜백 |
| 버퍼 | 버퍼 alloc/free 노드 | 해당 없음 | buffer-alloc-fail 카운터 | buffer pool 점유율 | vlib_buffer_alloc 내부 추적 |
L4 세션 디버깅
VPP의 호스트 스택(Host Stack)은 TCP/UDP 세션을 독립 테이블로 관리합니다. 세션 상태 이상, 타임아웃 불일치, 테이블 누수는 연결 문제의 주요 원인이며, 아래 명령으로 계층적으로 진단할 수 있습니다.
show session 출력 해석
# 활성 세션 목록 조회
vpp# show session
Session table for fib 0
proto lcl rmt state index
TCP 10.0.0.1:80 192.168.1.100:54321 ESTABLISHED 3
TCP 10.0.0.1:80 192.168.1.101:44210 SYN_RCVD 7
주요 필드 설명:
- session-index: 세션 테이블 내 고유 인덱스.
show session index <N>으로 단일 세션을 조회할 때 사용합니다. - state: TCP 상태 머신 상태.
SYN_RCVD가 오래 유지되면 클라이언트 ACK 미도달 또는 리소스 부족을 의심합니다. - fifo-pointers: RX/TX FIFO 읽기·쓰기 포인터 차이가 지속 증가하면 애플리케이션 수신 처리가 따라가지 못하는 상태입니다.
# 상세 정보 — FIFO 포인터, 세그먼트 크기, 이벤트 큐 포함
vpp# show session verbose
Session 3 (TCP 10.0.0.1:80 <-> 192.168.1.100:54321) ESTABLISHED
Rx FIFO: size 65536 tail 0x00001A00 head 0x00001800 deq-notif 1
Tx FIFO: size 65536 tail 0x00004000 head 0x00004000 deq-notif 0
tx-fifo-size: 65536 rx-fifo-size: 65536
app-index: 2 thread-index: 1
TCP 세션 상세: 재전송과 SACK
# TCP 세션 통계 — 재전송 카운터, SACK 상태 확인
vpp# show tcp session index 3
Connection 3: state ESTABLISHED
Local 10.0.0.1:80 Remote 192.168.1.100:54321
snd_una 0x12345678 snd_nxt 0x12346000 snd_wnd 65535
rcv_nxt 0xABCD0000 rcv_wnd 65535
retransmit count: 4 <-- 재전송 횟수; 임계값 초과 시 경로 품질 의심
SACK enabled: yes
SACK blocks: [0xABCD0100-0xABCD0200] <-- 수신측 hole 위치
RTO: 204ms SRTT: 12ms RTTVAR: 8ms
retransmit count가 단시간에 급증하면 경로 손실 또는 수신 버퍼 포화를 의심합니다. SACK blocks가 지속적으로 쌓이면 중간 노드의 재정렬(reorder) 또는 선택적 드롭을 확인합니다.
세션 타임아웃 디버깅
# 현재 타임아웃 설정 확인
vpp# show session timeout
close-idle: 10.0 sec
transport-close: 5.0 sec
established: 600.0 sec
half-open: 10.0 sec
timed-wait: 60.0 sec
# 타임아웃 값 조정 — 서비스 특성에 맞게 변경
# established 타임아웃 단축 (유휴 연결 빠르게 회수)
vpp# set session timeout established 120
# half-open 타임아웃 단축 (SYN flooding 방어)
vpp# set session timeout half-open 5
# 변경 후 즉시 확인
vpp# show session timeout
established 값을 지나치게 줄이면 장기 유휴 연결(HTTP keep-alive, DB 커넥션 풀)이 예기치 않게 종료됩니다. 변경 전에 show session count로 현재 세션 수를 기록해 기준선을 확보하십시오.
세션 테이블 누수 감지
# 세션 수 시계열 관찰 패턴
# 1단계: 기준선 기록
vpp# show session count
Session counts by state:
ESTABLISHED: 142
CLOSED: 3
TIME-WAIT: 18
Total: 163
# 2단계: 트래픽 재현 또는 5분 대기 후 재측정
vpp# show session count
ESTABLISHED: 145
CLOSED: 87 <-- CLOSED가 증가했지만 회수되지 않으면 누수 의심
TIME-WAIT: 231 <-- TIME-WAIT 급증은 close_idle 타임아웃 확인 필요
Total: 463
누수 판정 패턴:
- CLOSED 누적 증가: 세션 정리 경로(
session_close) 미호출 또는 애플리케이션 디텍(detach) 누락.show session verbose로 해당 세션의 app-index를 확인합니다. - TIME-WAIT 폭증: 짧은 수명의 연결이 대량 생성·종료되는 경우.
set session timeout timed-wait 30으로 회수 주기를 단축합니다. - Total이 트래픽 하강 후에도 감소하지 않음: GC 스레드 정체 또는 타임아웃 값이 지나치게 큰 상태.
show runtime에서session-queue노드의 벡터 길이를 함께 확인합니다.