본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

VPP 데이터 플레인 문제 해결

원문 보기 위키 갱신

VPP 데이터 플레인을 사용하는 클러스터에서 문제가 생겼을 때 따라 해 볼 수 있는 진단 절차를 안내해요. calivppctl 설치 방법부터 로그 확인, 패킷 트레이싱, 크래시 분석까지 실제 명령과 함께 정리했어요. VPP 데이터 플레인을 구성하지 않은 클러스터라면 이 문서는 필요하지 않아요.

출처: 문서

본문

큰 그림

이 페이지는 VPP 데이터 플레인의 문제 해결 단계를 설명해요. VPP 데이터 플레인을 구성하지 않았다면 이 페이지는 필요 없어요!

VPP 데이터 플레인에서 문제를 만나면 Calico slack의 #vpp 채널로 이야기하거나 GitHub)에 새 이슈를 열어 알려 주세요.

calivppctl 설치

calivppctl은 vpp 컨테이너 이미지와 함께 배포되는 bash 헬퍼 스크립트예요. 다음 방법으로 호스트에 설치할 수 있고, VPP 데이터 플레인이 설치된 클러스터의 로그 수집과 디버깅에 도움이 돼요.

  • curl로 설치

    curl https://raw.githubusercontent.com/projectcalico/vpp-dataplane/v3.32.0/test/scripts/vppdev.sh | tee /usr/bin/calivppctl
    chmod +x /usr/bin/calivppctl
    
  • docker로 설치(Calico VPP가 실행 중인 클러스터)

    vppcontainer=$(docker ps | grep vpp_calico-vpp | awk '{ print $1 }')
    docker cp ${vppcontainer}:/usr/bin/calivppctl /usr/bin/calivppctl
    
  • kubectl로 설치(Calico VPP가 실행 중인 클러스터)

    vpppod=$(kubectl -n calico-vpp-dataplane get pods -o wide | grep calico-vpp-node- | awk '{ print $1 }' | head -1)
    kubectl -n calico-vpp-dataplane exec -it ${vpppod} -c vpp -- cat /usr/bin/calivppctl | tee /usr/bin/calivppctl > /dev/null
    chmod +x /usr/bin/calivppctl
    

문제 해결

쿠버네티스 클러스터

먼저 쿠버네티스가 정상적으로 떠 있는지 확인해야 해요.

  • service kubelet status가 첫 번째 힌트를 줘요.
  • 문제는 kubelet 로그에 기록되는데, systemd를 사용한다면 journalctl -u kubelet -r -n200 명령으로 확인할 수 있어요.

SECONDARY: 쿠버네티스는 swap이 활성화된 상태로 동작하지 않아요.

calico-vpp-node 데몬셋 시작

클러스터가 정상적으로 시작되면 다음 문제 후보는 데몬셋 구성이에요. 가장 좋은 방법은 pod부터 살펴보는 거예요. pod들이 정상 실행 중인가요? hugepages 가용량, 메모리 같은 구성 문제는 보통 여기에 기록돼요:

kubectl -n calico-vpp-dataplane describe pod/calico-vpp-node-XXXXX

SECONDARY: 이 시점에 hugepages가 부족하다면 할당한 뒤 kubelet을 재시작해야 반영돼요(예: service kubelet restart)

VPP가 정상 동작하는지 확인

pod들이 더 이상 문제를 보고하지 않으면 pod들이 시작된 상태일 거예요. 각 노드에는 컨테이너가 두 개 있어요. VPP 프로세스를 시작하고 연결을 구성하는 vpp-manager, 그리고 pod 연결, 서비스 로드 밸런싱, BGP, 정책 등을 처리하는 에이전트죠.

먼저 VPP가 정상 실행 중인지 확인해요. 연결 구성이나 인터페이스 이름이 잘못됐다면 여기에 표시돼요. 이게 실행되면 다른 노드를 VPP 경유로 ping할 수 있어야 해요.

# Print VPP's log : basic connectivity and NIC configuration
calivppctl log -vpp myk8node1

그다음 에이전트가 보고하는 문제를 확인해요(예: 포트가 이미 사용 중이라 BGP listen에 실패하거나, 구성 일부가 빠진 경우). 오류가 없다면 pod에서 nslookup kubernetes.default가 동작해야 해요.

# Print the logs for the Calico VPP data plane agent, programming serviceIPs, BGP, ...
calivppctl log -agent myk8node1

여기까지 잘 안 풀리면 export로 export.tar.gz 번들을 만들어 #vpp 채널에서 도움을 요청할 수 있어요.

calivppctl export

VPP CLI 접근

더 깊이 디버깅하거나 패킷을 트레이싱하고 VPP 내부를 살펴보려면 다음 명령으로 vpp 셸을 열 수 있어요.

calivppctl vppctl myk8node1

인터페이스 나열과 기본 확인

기존 인터페이스와 기본 카운터를 나열하려면 이렇게 해요.

vpp# show int
vpp# show int addr

기본 인터페이스(예: dpdk를 사용하는 경우)를 더 자세히 보려면 오류와 드롭을 여기서 확인하세요.

vpp# show hardware-interfaces

오류를 확인할 만한 다른 곳들

vpp# show log       # VPP startup log
vpp# show err       # Prints out packet counters (not always actual errors, but includes drops)
vpp# show buffers   # You should have non zero free buffers, otherwise traffic won't flow

패킷 트레이싱

내부 네트워크 구성

우선 네트워크가 어떻게 생겼는지 간단한 그림으로 보면: k8-calico-vpp

컨테이너 인터페이스 이름은 tun[0-9]+ 형태예요. 어떤 인터페이스가 어떤 컨테이너에 속하는지 다음 순서로 찾을 수 있어요:

  • VPP에 접속

    calivppctl vppctl NODENAME
    
  • 인터페이스 나열

    vpp# show interface
                  Name               Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count
    avf-0/d8/a/0                      1      up          9000/0/0/0     tx packets                     2
                                                                        tx bytes                     216
    local0                            0     down          0/0/0/0
    tap0                              2      up           0/0/0/0       rx packets                     9
    [...]
    tun3                              5      up           0/0/0/0       rx packets                     5
                                                                        rx bytes                     431
                                                                        tx packets                     5
                                                                        tx bytes                     387
                                                                        ip4                            5
    
  • 주소 11.0.166.132의 라우트 조회

    vpp# show ip fib 11.0.166.132
    ipv4-VRF:0, fib_index:0, flow hash:[src dst sport dport symmetric ] epoch:0 flags:none locks:[adjacency:1, default-route:1, ]
    11.0.166.132/32 fib:0 index:19 locks:5
      cnat refs:1 entry-flags:uRPF-exempt,interpose, src-flags:added,contributing,active, cover:-1 interpose:
          [@0]: [4] cnat-client:[11.0.166.132] tr:0 sess:1
        path-list:[26] locks:3 flags:shared, uPRF-list:24 len:1 itfs:[5, ]
          path:[32] pl-index:26 ip4 weight=1 pref=0 attached-nexthop:  oper-flags:resolved, cfg-flags:attached,
            11.0.166.132 tun3 (p2p)
          [@0]: ipv4 via 0.0.0.0 tun3: mtu:9000 next:7
      [...]
    
  • 이 주소는 tun3 뒤에 있어요. 인터페이스에 대해 더 알고 싶다면(Linux에서의 이름, 큐, 디스크립터 등)

    vpp# show tun tun3
    Interface: tun3 (ifindex 5)
      name "eth0"
      host-ns "/proc/17675/ns/net"
      [...]
    

    tap0는 호스트 연결을 제공하는 인터페이스로, Linux 쪽에서는 원래 인터페이스 이름을 사용해요(show tap tap0과 show ip punt redirect를 사용하세요).

클러스터 내부 트래픽 캡처

클러스터에서 두 pod가 서로 통신하는 경우를 생각해 봐요(위의 그림 참고). 트래픽은 세 위치에서 관찰할 수 있어요:

  • pod에서 나갈 때(첫 번째 pod 안의 Linux)
  • VPP를 통과할 때
  • 두 번째 pod에서 수신될 때(다시 Linux)

세 경우를 모두 다뤄요. 먼저 VPP 내부(트래픽이 pod에서 오는지, 호스트 밖에서 오는지에 따라), 그다음 pod 내부(보통 tcpdump 사용)예요.

VPP 안에서 트래픽 캡처

pod에서 오는 트래픽

다음 코드는 특정 노드의 컨테이너에서 오는 모든 트래픽을 캡처하고, 원하는 패킷을 grep으로 찾아 어떻게 처리됐는지 확인하게 해 줘요.

# Make sure that the trace buffer is clean in VPP
calivppctl vppctl NODENAME clear trace
# Add a trace from the virtio-input input-node
calivppctl vppctl NODENAME trace add virtio-input 500
# generate some traffic
calivppctl vppctl NODENAME show trace max 500 > somefile
# Grep for your IPs
cat somefile | grep '1.2.3.4 -> 5.6.7.8' -A40 -B40

출력에는 패킷이 VPP에서 수신부터 송신까지 거치는 전체 경로가 담겨 있어서 처음에는 꽤 복잡해 보여요.

vpp# show trace
Packet 1

00:09:46:518858: virtio-input
 This packet has been received on the interface number #2 (column Idx in `show int`)
 and is 688 Bytes long
  virtio: hw_if_index 2 next-index 1 vring 0 len 688
    hdr: flags 0x00 gso_type 0x00 hdr_len 0 gso_size 0 csum_start 0 csum_offset 0 num_buffers 1
00:09:46:518866: ip4-input
 we read TCP header, addresses and ports
  TCP: 20.0.0.1 -> 11.0.166.133
    tos 0x00, ttl 64, length 688, checksum 0x1bc5 dscp CS0 ecn NON_ECN
    fragment id 0x56fd, flags DONT_FRAGMENT
  TCP: 6443 -> 34112
    seq. 0xa1f93599 ack 0x818eb1c1
    flags 0x18 PSH ACK, tcp header: 32 bytes
    window 502, checksum 0x00b7
00:09:46:518870: ip4-lookup
  fib 0 dpo-idx 5 flow hash: 0x00000000
  TCP: 20.0.0.1 -> 11.0.166.133
    tos 0x00, ttl 64, length 688, checksum 0x1bc5 dscp CS0 ecn NON_ECN
    fragment id 0x56fd, flags DONT_FRAGMENT
  TCP: 6443 -> 34112
    seq. 0xa1f93599 ack 0x818eb1c1
    flags 0x18 PSH ACK, tcp header: 32 bytes
    window 502, checksum 0x00b7
00:09:46:518873: ip4-cnat-tx
 We need to do some NATing as it's Kubernetes
  found: session:[20.0.0.1;6443 -> 11.0.166.133;34112, TCP] => 11.96.0.1;443 -> 11.0.166.133;34112 lb:-1 age:4190
00:09:46:518879: ip4-rewrite
 We rewrite the ip packet
 mac addresses only when coming / going to a PHY, as tun interfaces are L3-only
  tx_sw_if_index 6 dpo-idx 7 : ipv4 via 0.0.0.0 tun4: mtu:9000 next:8 flow hash: 0x00000000
  00000000: 450002b056fd40003f0625650b6000010b00a68501bb8540a1f93599818eb1c1
  00000020: 801801f620c700000101080a3f906c98fbaaba031703030277413d39
 Output happens on the interface `tun4`
00:09:46:518880: tun4-output
  tun4
  00000000: 450002b056fd40003f0625650b6000010b00a68501bb8540a1f93599818eb1c1
  00000020: 801801f620c700000101080a3f906c98fbaaba031703030277413d39b97817c1
  00000040: 41392fdbe0e9d4886849851476cdb8986362ee2f789bfefd8a5c106c898d1309
  00000060: 4f8f8cb89159d99e986813a48d91334930eb5eb10ca4248c
00:09:46:518881: tun4-tx
    buffer 0x24cf615: current data 0, length 688, buffer-pool 1, ref-count 1, totlen-nifb 0, trace handle 0x1000000
  ipv4 tcp hdr-sz 52 l2-hdr-offset 0 l3-hdr-offset 0 l4-hdr-offset 20 l4-hdr-sz 32
  0x0b60: 40:00:3f:06:25:65 -> 45:00:02:b0:56:fd

Packet 2
[...]

물리 NIC에서 오는 트래픽

물리 NIC에서 오는 트래픽을 캡처하려면 trace add를 사용하되 virtio-input 대신 dpdk-input, af-packet-input, af_xdp-input, avf-input 같은 다른 소스 노드를 지정해야 해요.

show run을 실행하면 트레이싱할 X-input 노드가 무엇인지 힌트를 얻을 수 있어요.

예를 들어 아래 show run 출력을 보면 avf-input을 트레이싱 소스 노드로 쓰면 될 것 같아요:

vpp# show run
Thread 1 vpp_wk_0 (lcore 25)
Time 1.9, 10 sec internal node vector rate 1.05 loops/sec 1074819.68
  vector rates in 7.5356e0, out 7.5356e0, drop 0.0000e0, punt 0.0000e0
             Name                 State         Calls          Vectors        Suspends         Clocks       Vectors/Call
avf-input                        polling           2233530               0               0          8.24e1            0.00
ip4-cnat-snat                    active                  1               1               0          5.35e3            1.00
ip4-cnat-tx                      active                 14              15               0          1.18e3            1.07
[...]

그러므로 컨테이너 트래픽과 마찬가지 방법으로 이렇게 사용하면 돼요.

# Make sure that the trace buffer is clean in VPP
calivppctl vppctl NODENAME clear trace
# Add a trace from the virtio-input input-node
calivppctl vppctl NODENAME trace add avf-input 500
# generate some traffic
calivppctl vppctl NODENAME show trace max 500 > somefile
# Grep for your IPs
cat somefile | grep '1.2.3.4 -> 5.6.7.8' -A40 -B40

Wireshark로 확인

트레이스 대신 캡처 파일을 만들어 Wireshark에서 분석할 수도 있어요. 다음과 같이 해요:

vpp# pcap dispatch trace on max 1000 file vppcapture buffer-trace dpdk-input 1000
vpp# pcap dispatch trace off

이러면 /tmp/vppcapture라는 파일이 만들어져요.

그다음 호스트에서 이렇게 실행해요:

calivppctl sh vpp NODENAME
root@server:~ mv /tmp/vppcapture /var/lib/vpp/
root@server:~ exit

이제 파일이 'NODENAME' 호스트의 /var/lib/vpp/vppcapture에 있어요. 이후 scp NODENAME:/var/lib/vpp/vppcapture .로 내려받아 Wireshark로 열면 돼요. 자세한 내용은 여기를 참고하세요.

pod에서 수신되는 트래픽

pod가 실제로 수신한 트래픽을 보려면(pod 안에 tcpdump가 설치된 경우) pod 안에서 tcpdump -ni eth0를 실행하면 돼요. pod에 tcpdump가 없다면 pod 트래픽을 캡처할 수 있는 방법이 두 가지 있어요:

호스트에 tcpdump가 있는 경우

호스트에 tcpdump가 설치되어 있다면 nsenter로 pod의 네트워크 네임스페이스에 붙어서 컨테이너 인터페이스에 호스트의 tcpdump를 사용할 수 있어요.

docker에서는 다음과 같이 동작해요:

  • 확인할 컨테이너 ID를 찾기

    docker ps
    CONTAINER ID        IMAGE                        COMMAND                CREATED              STATUS              PORTS               NAMES
    4c01db0b339c        ubuntu:12.04                 bash                   17 seconds ago       Up 16 seconds       3300-3310/tcp       webapp
    
  • 컨테이너 PID 얻기

    docker inspect --format '{{ .State.Pid }}' 4c01db0b339c
    12345
    
  • 붙기

    nsenter -t 12345 -n bash
    

tcpdump는 없지만 python이 있을 때

다음 코드로 python에서 AF_PACKET 소켓을 열고, 앞서처럼 실행 중인 네임스페이스에 붙여서 실행하세요.

#!/usr/bin/env python
from socket import *
from struct import unpack

IFNAME = "eth0"
N_PKT = 50
MTU=1500

sock = socket(AF_PACKET, SOCK_DGRAM, 0x0800)
sock.bind((IFNAME, 0x0800))
for _ in range(N_PKT):
    data = sock.recvfrom(MTU, 0)[0]
    src_addr = inet_ntop(AF_INET, data[12:16])
    dst_addr = inet_ntop(AF_INET, data[16:20])
    src_port, = unpack("!H", data[20:22])
    dst_port, = unpack("!H", data[22:24])
    data_len, = unpack("!H", data[24:26])
    cksum, = unpack("!H", data[26:28])

    print("%s:%d -> %s:%d len %d cs %d" % (src_addr, src_port, dst_addr, dst_port, data_len, cksum))

권한이 필요하기 때문에 보통은 호스트에서 실행하는 편이 쉬워요. 호스트에서는 echo "위의 python 코드" | nsenter -t <thePID> -n python으로 이 코드를 실행할 수 있어요.

kubelet 에이전트로 향하는 트래픽

kubelet 에이전트는 네트워크 네임스페이스 없이 호스트에서 직접 실행되기 때문에, 이곳과 통신하는 pod(예: coredns 리졸버) 트래픽은 특별한 경로를 거쳐요. kubelet으로 향하는 패킷은 VPP의 punt 메커니즘이 가로채고, Linux에서 원래 인터페이스와 같은 이름을 가진 tap 인터페이스를 통해 호스트로 전달돼요.

VPP 안의 트래픽을 디버깅하려면 trace를 사용해 트래픽이 tap0 인터페이스로 정상적으로 punt되는지 확인하세요.

호스트에서는 평소처럼 tcpdump로 트래픽을 확인할 수 있어요.

크래시와 coredump

VPP가 예기치 않게 종료되면 /var/lib/vpp/ 디렉터리에 coredump 파일 vppcore.vpp_main.<pid>를 생성해요. 이 상황을 만나면 추가 문제 해결에 도움이 되도록 coredump를 만든 vpp 이미지의 정확한 버전(이미지 해시 기준)을 기록해 두세요.

coredump를 탐색하려면 이렇게 실행해요:

docker run -it --entrypoint=bash -v /var/lib/vpp/vppcore.vpp_main.12345:/root/vppcore calicovpp/vpp:VERSION

이제 vpp 컨테이너 안에서 셸을 사용할 수 있어요.

apt update && apt install -y gdb
gdb vpp ./vppcore

더 알아보기 (Learn more)

calivppctl은 설치, 로그 확인, 트레이싱, 내보내기까지 VPP 디버깅을 한곳에서 처리해 주는 헬퍼예요. 문제를 좁힐 때는 kubelet 상태 → 데몬셋 pod → VPP 로그 → 에이전트 로그 순서로 확인하는 흐름이 좋아요. coredump가 남는다면 이미지 버전을 반드시 기록해 두고 gdb로 분석하세요.