Mac mini + Colima + K3s. CLI로 설치된 https://argocd.haulrest.me 기준 네트워크 흐름.
NGINX Gateway Fabric이 reverse proxy이므로 패킷 경로는 두 hop으로 나뉩니다. 각 hop이 L7부터 L1까지 별개의 스택을 탑니다.
[Phase 1] Client ─L7─ DNS / TLS
─L4─ 공유기 NAT → Mac mini 도착
─L3─ Mac mini pf RDR (DNAT to VIP)
─L2─ ARP (VIP → Colima VM MAC)
─L3─ VM kube-proxy iptables (VIP → NGF Pod IP)
─L1─ cni0 → NGF Pod TCP socket
↓ NGF nginx가 TLS 종료, 새 TCP 연결 시작
[Phase 2] NGF Pod ─L7─ HTTPRoute 매칭 → upstream(argocd-server Pod IP)
─L4─ Pod 간 TCP
─L3─ CNI 라우팅 (Pod IP → veth)
─L1─ argocd-server Pod TCP socket (평문 8080)
Phase 1: Client → NGF Pod
L7. DNS / TLS
dig argocd.haulrest.me +short
# <username>.iptime.org. → <공유기 IP> (iptime DDNS)
curl -sv --max-time 5 -o /dev/null https://argocd.haulrest.me/ 2>&1 | \
grep -E "Connected to|TLSv|subject:"
# subject: CN=*.haulrest.me (TLS는 NGF Pod에서 종료)L4. 공유기 NAT
공유기 관리 화면에서 설정되어 있습니다.
외부 443 → 192.168.0.aaa:443
VIP를 직접 못 가리키는 이유: MetalLB L2 ARP 응답이 Colima VM 네임스페이스에서 나와 Mac mini를 게이트웨이로 둬야 함.
L4. Mac mini 패킷 도착 확인
외부 망에서 curl https://argocd.haulrest.me를 친 상태로 Mac mini에서 캡처합니다.
sudo tcpdump -i en1 -nn 'tcp dst port 443 and dst host 192.168.0.aaa'
# IP <src>.<port> > 192.168.0.aaa.443: ... ← 패킷 도달 = 공유기 NAT 작동
# (공유기가 NAT하므로 src는 외부 IP가 아닌 공유기 LAN IP 192.168.0.1로 보일 수 있음)L3. Mac mini pf RDR (DNAT)
Mac mini의 pf가 Mac mini IP를 MetalLB VIP로 변환합니다.
sudo sysctl net.inet.ip.forwarding
# net.inet.ip.forwarding: 1
sudo pfctl -s info | head -1
# Status: Enabled
sudo pfctl -sA
# austinhome
sudo pfctl -a austinhome -s nat
# nat proto tcp from any to 192.168.0.bbb port 443 -> 192.168.0.aaa
# rdr pass proto tcp from any to 192.168.0.aaa port 443 -> 192.168.0.bbb연결이 떠 있는 상태에서 변환 결과:
sudo pfctl -s state | grep 192.168.0.bbb | head -3
# tcp 192.168.0.bbb:443 <- 192.168.0.aaa:443 <- <client>:<port> ESTABLISHED:ESTABLISHEDL2. ARP: VIP → Colima VM MAC
MetalLB speaker Pod이 “192.168.0.bbb는 내 MAC”이라고 LAN에 broadcast 광고(gratuitous ARP)를 보내면, Mac mini를 포함한 같은 LAN의 모든 기기가 ARP 테이블에 이 매핑을 캐시합니다.
arp -an | grep 192.168.0.bbb
# (192.168.0.bbb) at <Colima VM MAC> on bridge100L3. Colima VM 라우팅 + Service 노출
colima ssh -p k3s-homeserver
ip route show
# default via 192.168.0.1 dev col0 ← LAN (MetalLB 트래픽 진입)
# default via 192.168.5.2 dev eth0 ← Colima 내부 NAT (host↔VM)
# 10.42.0.0/24 dev cni0 ← K3s Pod CIDR
kubectl get svc -n nginx-gateway
# home-gateway-nginx LoadBalancer 10.x.x.x 192.168.0.bbb 80:3xxxx/TCP,443:3xxxx/TCPL3. VM kube-proxy iptables (VIP → Pod IP)
VIP 진입 → KUBE-EXT → KUBE-SVC → KUBE-SEP 체인을 따라가면 최종 DNAT가 나타납니다.
sudo iptables-save -t nat | grep 192.168.0.bbb | head -1
# -A KUBE-SERVICES -d 192.168.0.bbb/32 -p tcp --dport 443 -j KUBE-EXT-XXXXX
# 위에서 얻은 KUBE-EXT 해시로 다음 체인 추적 (KUBE-SVC → KUBE-SEP → DNAT)
sudo iptables-save -t nat | grep -E "KUBE-EXT-XXXXX|KUBE-SVC-|KUBE-SEP-" | grep -E "to-destination|-j KUBE-S(VC|EP)"
# 또는 그냥 DNAT 규칙 전체에서 :443으로 가는 것만:
sudo iptables-save -t nat | grep "DNAT" | grep ":443"
# -A KUBE-SEP-YYYYY ... -j DNAT --to-destination 10.42.0.xx:443L1. cni0에 도착한 패킷
kubectl debug --target 은 PID 네임스페이스만 공유하고 네트워크 네임스페이스는 공유 안 해서 대상 Pod의 트래픽을 볼 수 없습니다. VM에 tcpdump가 없으니 hostNetwork + privileged netshoot Pod을 띄워 cni0 bridge를 직접 캡처합니다.
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: netshoot-host
namespace: default
spec:
hostNetwork: true
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["sleep", "3600"]
securityContext:
privileged: true
EOF
kubectl wait --for=condition=Ready pod/netshoot-host --timeout=30s
# 외부에서 curl 친 상태에서
kubectl exec netshoot-host -- timeout 20 tcpdump -i cni0 -nn 'tcp port 443' \
| grep "<NGF_DATA_POD_IP>"
# IP 192.168.0.aaa.<port> > <NGF_DATA_POD_IP>.443: Flags [S] ← Pod IP로 진입
# IP <NGF_DATA_POD_IP>.443 > 192.168.0.aaa.<port>: Flags [S.] ← 응답
# (src=192.168.0.aaa는 pf RDR + source NAT 결과)
kubectl delete pod netshoot-hostNGF data plane Pod IP는 LoadBalancer Service의 EndpointSlice에서 확인:
kubectl get endpointslices -n nginx-gateway
# home-gateway-nginx-... 443 10.42.0.xx여기서 NGF Pod의 nginx가 TLS handshake를 끝내고 HTTP 요청을 해석합니다. Phase 1 종료.
Phase 2: NGF Pod → argocd-server Pod
NGF nginx가 새 TCP 연결을 backend Pod로 엽니다. TLS 없음 (argocd-server --insecure).
L7. HTTPRoute 매칭 + upstream
NGF는 컨트롤러 Pod(nginx-gateway-fabric)과 data plane Pod(home-gateway-nginx)이 분리되어 있습니다. nginx conf는 data plane에 있습니다.
kubectl get gateway,httproute -A
# data plane Pod (LoadBalancer Service의 backend)
NGF_DATA_POD=$(kubectl get pods -n nginx-gateway \
-l app.kubernetes.io/name=home-gateway-nginx \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n nginx-gateway exec $NGF_DATA_POD -c nginx -- \
grep -A 3 "server_name argocd.haulrest.me" /etc/nginx/conf.d/http.conf | head
kubectl -n nginx-gateway exec $NGF_DATA_POD -c nginx -- \
grep -A 6 "upstream argo-project_argocd-server" /etc/nginx/conf.d/http.conf
# server <argocd-server-pod-ip>:8080; ← Pod IP 직접 (ClusterIP DNAT 우회)L4. Pod 간 TCP
ARGOCD_POD_IP=$(kubectl -n argo-project get pod \
-l app.kubernetes.io/name=argocd-server \
-o jsonpath='{.items[0].status.podIP}')
echo $ARGOCD_POD_IP
# 10.42.0.yy
# NGF data plane에서 argocd-server로 직접 평문 호출
kubectl -n nginx-gateway exec $NGF_DATA_POD -c nginx -- \
curl -sv --max-time 3 http://$ARGOCD_POD_IP:8080/healthz 2>&1 | head -10
# Connected to 10.42.0.yy port 8080
# HTTP/1.1 200 OKL3. NGF Pod 네임스페이스 라우팅
NGF Pod 자체 네트워크 네임스페이스 안의 라우팅 테이블. Pod CIDR이 cni0 게이트웨이(10.42.0.1)로 향합니다.
kubectl -n nginx-gateway exec $NGF_DATA_POD -c nginx -- ip route
# default via 10.42.0.1 dev eth0
# 10.42.0.0/24 dev eth0 scope link src 10.42.0.8
# 10.42.0.0/16 via 10.42.0.1 dev eth0L2. NGF Pod ARP: argocd-server MAC 학습
같은 cni0 bridge에 붙은 Pod이라 ARP 한 번으로 직접 도달합니다.
kubectl -n nginx-gateway exec $NGF_DATA_POD -c nginx -- ip neigh show
# 10.42.0.yy dev eth0 lladdr xx:xx:xx:xx:xx:xx ... REACHABLE ← argocd-server
# 10.42.0.1 dev eth0 lladdr xx:xx:xx:xx:xx:xx ... REACHABLE ← cni0 gatewayL1. cni0에서 Phase 2 패킷 캡처
Phase 1과 같은 hostNetwork netshoot Pod으로 NGF↔argocd-server 트래픽을 직접 확인합니다.
# (Phase 1의 netshoot-host Pod 재사용 또는 재생성)
kubectl exec netshoot-host -- timeout 15 tcpdump -i cni0 -nn \
"host $ARGOCD_POD_IP and tcp port 8080"
# IP 10.42.0.xx.<port> > 10.42.0.yy.8080: Flags [S] ← NGF → argocd SYN
# IP 10.42.0.yy.8080 > 10.42.0.xx.<port>: Flags [S.] ← argocd → NGF SYN-ACK
# ... GET /... HTTP/1.1, 200 OK ... (TLS 없음, 평문)K3s 단일 노드(Colima VM 1대)라 Pod 간 통신은 cni0 bridge + veth로 끝납니다. flannel의 VXLAN 캡슐화는 노드 간 통신용이라 여기서는 발생하지 않습니다.