이번엔 native routing (오버레이 네트워크 없는 구조)에서 통신이 어떻게 이뤄지는지 확인해보겠습니다.
cilium과 샘플 애플리케이션 구성하기
버전은 1.17.6이고 ipam은 kubernetes 모드를 사용했습니다. datapath는 netkit이 아닌 veth 모드입니다.
helm install cilium cilium/cilium --version 1.17.6 --namespace kube-system \
--set k8sServiceHost=auto --set ipam.mode="kubernetes" \
--set k8s.requireIPv4PodCIDR=true --set ipv4NativeRoutingCIDR=10.244.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=true --set endpointRoutes.enabled=true \
--set kubeProxyReplacement=true --set bpf.masquerade=true --set installNoConntrackIptablesRules=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set hubble.enabled=true --set hubble.relay.enabled=true --set hubble.ui.enabled=true \
--set hubble.ui.service.type=NodePort --set hubble.ui.service.nodePort=30003 \
--set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload\,traffic_direction}" \
--set operator.replicas=1 --set debug.enabled=true
샘플 애플리케이션도 같이 배포해봅니다.
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webpod
spec:
replicas: 2
selector:
matchLabels:
app: webpod
template:
metadata:
labels:
app: webpod
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- sample-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: webpod
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: webpod
labels:
app: webpod
spec:
selector:
app: webpod
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
EOF
# kind-control-plane 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl-pod
labels:
app: curl
spec:
nodeName: kind-control-plane
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# kind-worker 노드에 client-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: client-pod
labels:
app: client
spec:
nodeName: kind-worker
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
구성 결과
커널 버전 : 6.14.10
같은 노드 위 파드간 통신
서비스를 끼지 않고 통신할 때
먼저 client-pod → webpod-697b545f57-tdbfg로 확인해보겠습니다.
curl http://10.244.1.182
먼저 hubble로 확인해보겠습니다.
로그에서 서로간의 SYN, ACK FIN을 보낸 로그까지 확인이 가능합니다.
이상태에서 tcpdump를 노드에서 떠보겠습니다.
먼저 client-pod의 veth pair인터페이스인 lxc93b551d233cf에 tcpdump를 떠보겠습니다.
참고)
파드가 어떤 veth 케이블과 연결되어있는지 확인하려면 cilium bpf endpoint list를 확인하면 됩니다.
여기서 ifindex값을 보고 ip a 결과와 연결 하면 됩니다.
root@kind-worker:/home/cilium# cilium bpf endpoint list
IP ADDRESS LOCAL ENDPOINT INFO
10.244.1.113:0 (localhost)
10.244.1.182:0 id=359 sec_id=32693 flags=0x0000 ifindex=9 mac=1E:97:A3:67:C6:D1 nodemac=A6:25:B4:A1:25:0A parent_ifindex=0
172.17.0.4:0 (localhost)
10.244.1.71:0 id=2543 sec_id=30542 flags=0x0000 ifindex=11 mac=DA:15:43:39:36:5E nodemac=32:17:55:23:39:08 parent_ifindex=0
확인 결과 통신은 잘되는데 이상한점이 있습니다. hubble과는 다르게 들어오는 응답에대한 패킷 로그가 보이지 않습니다.
root@kind-worker:/# tcpdump -i lxc93b551d233cf
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lxc93b551d233cf, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:00:54.904025 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [S], seq 2226486015, win 64240, options [mss 1460,sackOK,TS val 2442460732 ecr 0,nop,wscale 7], length 0
07:00:54.904137 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [.], ack 1216905209, win 502, options [nop,nop,TS val 2442460732 ecr 3287323394], length 0
07:00:54.904247 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 2442460732 ecr 3287323394], length 76: HTTP: GET / HTTP/1.1
07:00:54.907795 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [.], ack 328, win 501, options [nop,nop,TS val 2442460735 ecr 3287323397], length 0
07:00:54.907942 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [F.], seq 76, ack 328, win 501, options [nop,nop,TS val 2442460735 ecr 3287323397], length 0
07:00:54.908773 IP 10.244.1.71.55240 > 10.244.1.182.http: Flags [.], ack 329, win 501, options [nop,nop,TS val 2442460736 ecr 3287323398], length 0
07:01:00.108130 ARP, Request who-has 10.244.1.113 tell 10.244.1.71, length 28
07:01:00.108269 ARP, Reply 10.244.1.113 is-at 32:17:55:23:39:08 (oui Unknown), length 28
그렇다면 반대로 요청을 받는 webpod의 veth pair 로그도 확인해보겠습니다.
root@kind-worker:/# tcpdump -i lxc49626b6ed5ee
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lxc49626b6ed5ee, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:03:26.613424 IP 10.244.1.182.http > 10.244.1.71.41492: Flags [S.], seq 1760111037, ack 4043725153, win 65160, options [mss 1460,sackOK,TS val 3287475103 ecr 2442612441,nop,wscale 7], length 0
07:03:26.613651 IP 10.244.1.182.http > 10.244.1.71.41492: Flags [.], ack 77, win 509, options [nop,nop,TS val 3287475103 ecr 2442612441], length 0
07:03:26.621046 IP 10.244.1.182.http > 10.244.1.71.41492: Flags [P.], seq 1:328, ack 77, win 509, options [nop,nop,TS val 3287475111 ecr 2442612441], length 327: HTTP: HTTP/1.1 200 OK
07:03:26.621290 IP 10.244.1.182.http > 10.244.1.71.41492: Flags [F.], seq 328, ack 78, win 509, options [nop,nop,TS val 3287475111 ecr 2442612449], length 0
이건 반대로 webpod가 응답을 보내는 로그만이 보입니다.
이젠 파드내에서 eth0을 사용해서 dump를 떠보겠습니다.
client-pod:~# tcpdump -i eth0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:08:02.938224 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [S], seq 1998253087, win 64240, options [mss 1460,sackOK,TS val 2442888766 ecr 0,nop,wscale 7], length 0
07:08:02.938558 IP 10-244-1-182.webpod.native.svc.cluster.local.80 > client-pod.41524: Flags [S.], seq 290449748, ack 1998253088, win 65160, options [mss 1460,sackOK,TS val 3287751428 ecr 2442888766,nop,wscale 7], length 0
07:08:02.938604 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [.], ack 1, win 502, options [nop,nop,TS val 2442888766 ecr 3287751428], length 0
07:08:02.938769 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [P.], seq 1:77, ack 1, win 502, options [nop,nop,TS val 2442888766 ecr 3287751428], length 76: HTTP: GET / HTTP/1.1
07:08:02.938806 IP 10-244-1-182.webpod.native.svc.cluster.local.80 > client-pod.41524: Flags [.], ack 77, win 509, options [nop,nop,TS val 3287751428 ecr 2442888766], length 0
07:08:02.944124 IP 10-244-1-182.webpod.native.svc.cluster.local.80 > client-pod.41524: Flags [P.], seq 1:328, ack 77, win 509, options [nop,nop,TS val 3287751434 ecr 2442888766], length 327: HTTP: HTTP/1.1 200 OK
07:08:02.944149 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [.], ack 328, win 501, options [nop,nop,TS val 2442888772 ecr 3287751434], length 0
07:08:02.944718 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [F.], seq 77, ack 328, win 501, options [nop,nop,TS val 2442888772 ecr 3287751434], length 0
07:08:02.945378 IP 10-244-1-182.webpod.native.svc.cluster.local.80 > client-pod.41524: Flags [F.], seq 328, ack 78, win 509, options [nop,nop,TS val 3287751435 ecr 2442888772], length 0
07:08:02.945387 IP client-pod.41524 > 10-244-1-182.webpod.native.svc.cluster.local.80: Flags [.], ack 329, win 501, options [nop,nop,TS val 2442888773 ecr 3287751435], length 0
이번에는 모든 통신 흐름이 다보이는 것을 알 수 있습니다.
그렇다면 마지막으로 eth0인터페이스를 확인해보겠습니다. 이번엔 아무런 패킷이 보이지 않는 것을 알 수 있습니다.
그림으로 그려보면 이런 상황입니다
cilium에서는 파드간의 통신을 할때 veth케이블을 통하지 않고 lxc*** 인터페이스에 ebpf 코드를 심어 바로 목적지 파드의 인터페이스로 패킷을 보내도록 하고 있기 때문입니다.
관련해서 어디선가 자료를 봤었는데 기억이 안나네요..
pwru로 확인해보니 반대편 veth 케이블로 가긴하는데 바로 케이블로 보내서 로그가 안남는 것 같습니다.
추가로 확인해보니 handle_ipv4_from_lxc()에서 이미 목적지 파드의 mac주소도 알고있고 다이렉트로 요청을 받는 파드의 veth 케이블로 전달되는 것 같습니다.
서비스를 통해 통신할 때
이번에는 서비스의 아이피로 호출을 해보도록 하겠습니다.
curl http://10.96.92.233
이 상태로 client-pod의 인터페이스를 잡고 확인을 해보면 동일하게 한쪽 패킷만 보이는 것을 확인할 수 있습니다.
root@kind-worker:/# tcpdump -i lxc93b551d233cf
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lxc93b551d233cf, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:36:18.098483 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [S], seq 3971662973, win 64240, options [mss 1460,sackOK,TS val 2444583926 ecr 0,nop,wscale 7], length 0
07:36:18.098650 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [.], ack 1494044849, win 502, options [nop,nop,TS val 2444583926 ecr 3289446588], length 0
07:36:18.099231 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 2444583927 ecr 3289446588], length 76: HTTP: GET / HTTP/1.1
07:36:18.100597 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [.], ack 328, win 501, options [nop,nop,TS val 2444583928 ecr 3289446590], length 0
07:36:18.104793 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [F.], seq 76, ack 328, win 501, options [nop,nop,TS val 2444583932 ecr 3289446590], length 0
07:36:18.104937 IP 10.244.1.71.38608 > 10.244.1.182.http: Flags [.], ack 329, win 501, options [nop,nop,TS val 2444583932 ecr 3289446594], length 0
여기서 eth0인터페이스를 확인해보겠습니다.
이번에도 아무것도 안보이는 것을 알 수 있습니다.
Cilium이 추적 중인 네트워크 흐름(L3~L7)을 실시간으로 출력해주는 cilium monitor 명령어를 사용해서 같은 노드에 있는 파드로 가는 통신을 확인해보겠습니다.
처음에 나갈때 패킷을 확인해보면 lxc93b551d233cf 인터페이스를 통해 패킷이 나간것을 볼 수 있고
root@kind-worker:/home/cilium# cilium monitor --from 2543
Listening for events on 8 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
time="2025-08-02T08:05:58.937367013Z" level=info msg="Initializing dissection cache..." subsys=monitor
-> endpoint 2543 flow 0x7783164 , identity 32693->30542 state reply ifindex lxc93b551d233cf orig-ip 10.244.1.182: 10.244.1.182:80 -> 10.244.1.71:45532 tcp SYN, ACK
-> endpoint 2543 flow 0x7783164 , identity 32693->30542 state reply ifindex lxc93b551d233cf orig-ip 10.244.1.182: 10.244.1.182:80 -> 10.244.1.71:45532 tcp ACK
-> endpoint 2543 flow 0x7783164 , identity 32693->30542 state reply ifindex lxc93b551d233cf orig-ip 10.244.1.182: 10.244.1.182:80 -> 10.244.1.71:45532 tcp ACK, FIN
^C
Received an interrupt, disconnecting from monitor...
반대쪽 엔드포인트를 잡아서 확인해 보면 해당 파드의 veth(lxc49626b6ed5ee)에서 응답을 준것을 알 수 있습니다.
root@kind-worker:/home/cilium# cilium monitor --to 359
Listening for events on 8 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
time="2025-08-02T08:04:38.826579156Z" level=info msg="Initializing dissection cache..." subsys=monitor
-> endpoint 359 flow 0x2b611e1d , identity 30542->32693 state new ifindex lxc49626b6ed5ee orig-ip 10.244.1.71: 10.244.1.71:42278 -> 10.244.1.182:80 tcp SYN
-> endpoint 359 flow 0x2b611e1d , identity 30542->32693 state established ifindex lxc49626b6ed5ee orig-ip 10.244.1.71: 10.244.1.71:42278 -> 10.244.1.182:80 tcp ACK
-> endpoint 359 flow 0x2b611e1d , identity 30542->32693 state established ifindex lxc49626b6ed5ee orig-ip 10.244.1.71: 10.244.1.71:42278 -> 10.244.1.182:80 tcp ACK
-> endpoint 359 flow 0x2b611e1d , identity 30542->32693 state established ifindex lxc49626b6ed5ee orig-ip 10.244.1.71: 10.244.1.71:42278 -> 10.244.1.182:80 tcp ACK, FIN
-> endpoint 359 flow 0x2b611e1d , identity 30542->32693 state established ifindex lxc49626b6ed5ee orig-ip 10.244.1.71: 10.244.1.71:42278 -> 10.244.1.182:80 tcp ACK
하지만 nat가 되었는지는 보이지 않습니다.
이번엔 cilium bpf 명령어를 사용해서 확인해보겠습니다. nat list를 확인할 수 있는데 통신을 하기 전에는 다음과 같이 nat list가 보입니다. (보기 편하게 하기 위해 flush를 한번 했습니다.)
이제 호출을 한번 해본 뒤 확인을 해보겠습니다.
이번에는 2개의 정보가 추가된것을 확인할 수 있습니다. 하지만 6443 포트를 사용하는 것으로 보아선 전혀 상관이 없는 nat 정보 같습니다. (확실하지는 않음)
이번엔 connection tracking 정보도 확인해보겠습니다. 원래 iptables로 nat를 하게 되면 conntrack을 이용해서 기록을 남기는데 cilium에서는 bpf map에 저장하게 되어있습니다. 이번에도 보기 편하게 하기 위해 flush를 한번 진행했습니다.
이후 호출을 해보면 다음과 같이 보이게 됩니다. cilium에서는 conntrack 정보를 양방향으로 저장하고 있기 때문에 나가는 것과 들어오는 것 icmp, http 2개씩 총 4개의 정보가 보이게됩니다.
여기서 중요한건 RevNAT입니다. 이 값이 0 이면 NAT가 되지 않았다는 뜻입니다.
결국 서비스로 들어가는건 ebpf map 을 통해 백엔드를 고르고 nat는 하지 않는 것 같습니다.
더 찾아보니 lb4_lookup_service()에서 백엔드를 고르는 것 같습니다. 목적지 주소가 client ip일때 백엔드 주소를 찾아서 반환하는 것 같습니다.
https://github.com/cilium/cilium/blob/main/bpf/lib/lb.h#L1432C1-L1451C2
cilium/bpf/lib/lb.h at f77c847c014704cc76dfeef41f786b24a3310e43 · cilium/cilium
eBPF-based Networking, Security, and Observability - cilium/cilium
github.com
다른 노드 위 파드간 통신
ebpf masquerade를 켜둔 상황이라 다른 노드에 떠있는 파드로 통신을 해보면 출발지 주소가 노드의 아이피 일 수 있을 거라 생각했지만 native-routing이기 때문에 출발지 파드의 아이피가 남아있는 것을 확인할 수 있습니다.
root@kind-worker2:/# tcpdump -i eth0 dst 10.244.3.135
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
11:25:02.828415 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [S], seq 1681361202, win 64240, options [mss 1460,sackOK,TS val 3093583555 ecr 0,nop,wscale 7], length 0
11:25:02.828760 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [.], ack 4093850730, win 502, options [nop,nop,TS val 3093583556 ecr 2504477094], length 0
11:25:02.828937 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 3093583556 ecr 2504477094], length 76: HTTP: GET / HTTP/1.1
11:25:02.831335 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [.], ack 328, win 501, options [nop,nop,TS val 3093583558 ecr 2504477096], length 0
11:25:02.831631 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [F.], seq 76, ack 328, win 501, options [nop,nop,TS val 3093583559 ecr 2504477096], length 0
11:25:02.831958 IP 10.244.1.71.51258 > 10.244.3.135.http: Flags [.], ack 329, win 501, options [nop,nop,TS val 3093583559 ecr 2504477097], length 0
'K8S > 🔥 network study🔥' 카테고리의 다른 글
[cilium] ipam 모드 비교하기 (작성 중) (0) | 2025.08.03 |
---|---|
Cilium Masquerading (BPF-based vs iptables-based) (3) | 2025.08.02 |
[cilium] hubble exporter 설정하기 (0) | 2025.07.26 |
[cilium] 스타워즈 데모로 cilium 테스트 해보기 (3) | 2025.07.26 |
[cilium] hubble 사용해보기 (0) | 2025.07.22 |