(OCI 프리티어/테넌시 분산 환경에서 Flannel VXLAN Pod↔Pod 통신이 안 됐던 이유와 해결)
0 ) 왜 이걸 하게 됐는지 (상황/증상)
OCI 프리티어를 최대한 활용하려다 보니, 같은 팀원 3명의 서로 다른 계정/테넌시에서 만든 인스턴스(노드)를 하나의 k3s 클러스터로 묶어야 했다.
문제는 테넌시가 갈라지는 순간부터였다.
- 테넌시가 다르면 보통 VCN/서브넷/보안 규칙/라우팅을 한 덩어리로 통제하기 어렵다.
- 그래서 노드 간 연결을 WireGuard(wg0) 로 단일화해서 10.99.0.0/24 대역으로 묶었다.
- 그리고 worker join도 K3S_URL="https://10.99.0.1:6443"로 붙였다.
여기까지는 괜찮았다. 노드는 Ready, API도 정상.
하지만 처음부터 문제가 터졌다.
✅ 증상:
- kubectl get nodes는 정상 (노드 Ready)
- kubectl get pods -A도 정상
- 그런데 노드가 다른 Pod끼리 통신이 안 됨
- PodA(노드A) → PodB(노드B) ping 실패
- 노드跨越 트래픽이 필요한 서비스도 정상 동작하지 않음
여기서 CNI를 고민했다.
- Calico/Cilium도 고려했지만, 이 환경에서 먼저 필요한 건 “정책/관측”이 아니라 “일단 안정적으로 붙는 연결”이었다.
- 운영 복잡성을 낮추기 위해 k3s 기본 구성인 Flannel(VXLAN) 을 유지하고, 노드 IP와 인터페이스만 wg0로 고정하는 게 가장 단순한 해법이었다.
즉, “CNI를 갈아엎기”가 아니라 Flannel의 전제(노드 IP를 타는 VXLAN) 를 만족시키는 방향으로 문제를 푸는 전략을 택했다.
1 ) Current State / Desired State
[ Current State ]
[ Current State ]
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
starlight-first-sh Ready control-plane,master 2d21h v1.33.6+k3s1 10.0.1.131 <none> Ubuntu 24.04.3 LTS 6.14.0-1016-oracle containerd://2.1.5-k3s1.33
withus-sj Ready <none> 13h v1.33.6+k3s1 10.0.1.95 <none> Ubuntu 24.04.3 LTS 6.14.0-1016-oracle containerd://2.1.5-k3s1.33[ Desired State ]
[ Desired State ]
# INTERNAL-IP를 wg0에 맞게 맞춰주기
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
starlight-first-sh Ready control-plane,master 2d21h v1.33.6+k3s1 10.99.0.1 <none> Ubuntu 24.04.3 LTS 6.14.0-1016-oracle containerd://2.1.5-k3s1.33
withus-sj Ready <none> 13h v1.33.6+k3s1 10.99.0.2 <none> Ubuntu 24.04.3 LTS 6.14.0-1016-oracle containerd://2.1.5-k3s1.33
2 ) 우선, Node IP / Pod CIDR / Service CIDR에 대해서 간단하게 이해하고 넘어가자
Node IP / Pod CIDR / Service CIDR은 서로 겹치지 않게 분리해서 설계해야 한다. Node IP는 노드 간 실제 라우팅에 쓰이고, Pod CIDR은 파드에 할당되는 내부 주소이며, Service CIDR은 ClusterIP 같은 가상 주소 대역이므로 역할과 처리 방식이 다르다. 따라서 이 대역들이 겹치면 라우팅/NAT 경로가 꼬여 통신 장애가 발생할 수 있어, 분리해 구성해야 한다.
| 구분 |
뭐의 IP? | 어디서 확인? | 예시 | 메모 |
| Node(호스트) IP 대역 | 각 노드 서버 IP | kubectl get nodes -o wide의 INTERNAL-IP | VCN 10.0.1.0/24, WG 10.99.0.0/24 |
노드 간 통신 기반 |
| Pod CIDR 대역 | 파드 IP | kubectl get nodes -o jsonpath='{..podCIDR}' | 10.42.0.0/16 | 노드별 분할 |
| Service CIDR 대역 | ClusterIP(VIP) | k3s 설정 / dump | 10.43.0.0/16 | 가상 IP (LB/NAT) |
3 ) 핵심: Flannel(VXLAN)에서 노드 간 Pod↔Pod는 “노드 IP로 UDP 8472”를 보낸다
k3s 기본 flannel=vxlan 기준:
- PodA → PodB “속 패킷”은 Pod IP(10.42.x.x)
- 하지만 다른 노드로 넘어갈 때 flannel이 VXLAN 캡슐화
- 캡슐화된 “겉 패킷”의 목적지는 PodB가 아니라 NodeB의 Node IP
- 겉 패킷은 보통 UDP 8472(VXLAN)
따라서 INTERNAL-IP가 10.0.1.x로 잡힌 상태면, VXLAN “겉 패킷”이 10.0.1.x로 나가려는 구조가 된다.
그런데 내 환경은:
- 테넌시/VCN이 달라서 10.0.1.x끼리 라우팅이 보장되지 않음
- 대신 wg0(10.99.0.x)만 안정적으로 연결됨
그래서 Pod↔Pod(노드 간)가 처음부터 깨졌던 것이다.
4 ) Mermaid: Pod → VXLAN(UDP 8472) → wg0 → 노드 → Pod 흐름

결론 한 줄: VXLAN “겉 패킷” 목적지는 상대 노드의 Node IP → Node IP를 wg0로 고정해야 한다.
5 ) INTERNAL-IP / node-ip / flannel-iface 차이 (짧은 표)
| 항목 | 한 줄 정의 | 영향 | 설정/확인 |
| INTERNAL-IP | k8s가 노드 주소로 잡아 표시하는 값 | CNI/컴포넌트가 노드 간 통신 시 참조 | kubectl get nodes -o wide |
| node-ip | kubelet이 광고할 노드 IP를 강제 | 사실상 INTERNAL-IP를 “원하는 값”으로 만드는 핵심 | config.yaml의 node-ip |
| flannel-iface | flannel이 VXLAN 기준 NIC 선택 | VXLAN(UDP 8472)의 실제 송수신 인터페이스 고정 | config.yaml의 flannel-iface |
| (참고) K3S_URL | agent가 API 서버로 붙을 주소 | 조인/헬스에는 중요하지만 Pod↔Pod 경로 보장은 아님 | join env |
6 ) 해결: node-ip를 wg0(10.99)로 강제 + flannel-iface를 wg0로 고정
마스터(서버)
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'YAML'
node-ip: 10.99.0.1
flannel-iface: wg0
flannel-backend: vxlan
YAML
sudo systemctl restart k3s워커(에이전트)
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'YAML'
node-ip: 10.99.0.2
flannel-iface: wg0
YAML
sudo systemctl restart k3s-agent
확인
kubectl get nodes -o wide
7 ) 검증: 노드에 고정 배치한 Pod끼리 상호 ping
cat <<'YAML' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nettest-master
spec:
nodeName: starlight-first-sh
containers:
- name: bb
image: busybox:1.36
command: ["sh","-c","sleep 36000"]
---
apiVersion: v1
kind: Pod
metadata:
name: nettest-worker
spec:
nodeName: withus-sj
containers:
- name: bb
image: busybox:1.36
command: ["sh","-c","sleep 36000"]
YAML
kubectl get pod nettest-master nettest-worker -o widekubectl exec -it nettest-master -- ping -c 3 <nettest-worker_POD_IP>
kubectl exec -it nettest-worker -- ping -c 3 <nettest-master_POD_IP>kubectl delete pod nettest-master nettest-worker8 ) 운영 보강: wg0에서 필요한 포트(8472/6443/10250) 허용 + 영구화
sudo apt-get update
sudo apt-get install -y iptables-persistent
sudo iptables -N WG0-ALLOW 2>/dev/null || true
sudo iptables -C INPUT -j WG0-ALLOW 2>/dev/null || sudo iptables -I INPUT 1 -j WG0-ALLOW
# VXLAN (공통)
sudo iptables -C WG0-ALLOW -i wg0 -p udp -s 10.99.0.0/24 --dport 8472 -j ACCEPT 2>/dev/null \
|| sudo iptables -A WG0-ALLOW -i wg0 -p udp -s 10.99.0.0/24 --dport 8472 -j ACCEPT
sudo iptables -C WG0-ALLOW -i wg0 -p udp -s 10.99.0.0/24 --sport 8472 -j ACCEPT 2>/dev/null \
|| sudo iptables -A WG0-ALLOW -i wg0 -p udp -s 10.99.0.0/24 --sport 8472 -j ACCEPT
# API 서버(마스터에서만)
sudo iptables -C WG0-ALLOW -i wg0 -p tcp -s 10.99.0.0/24 --dport 6443 -j ACCEPT 2>/dev/null \
|| sudo iptables -A WG0-ALLOW -i wg0 -p tcp -s 10.99.0.0/24 --dport 6443 -j ACCEPT
# kubelet(모든 노드, metrics-server)
sudo iptables -C WG0-ALLOW -i wg0 -p tcp -s 10.99.0.0/24 --dport 10250 -j ACCEPT 2>/dev/null \
|| sudo iptables -A WG0-ALLOW -i wg0 -p tcp -s 10.99.0.0/24 --dport 10250 -j ACCEPT
sudo netfilter-persistent save
sudo netfilter-persistent reloadsudo ss -lntp | grep 10250
kubectl -n kube-system rollout restart deploy metrics-server
kubectl top nodes
결론
- 테넌시가 분산된 OCI 프리티어 환경에서는 VCN 라우팅을 한 번에 통일하기 어렵다.
- 그래서 노드 간 연결을 WireGuard(wg0)로 단일화했지만,
- INTERNAL-IP가 VCN IP(10.0.1.x)로 잡혀 있으면 Flannel VXLAN 노드 간 패킷이 그쪽으로 나가며
- Pod↔Pod(노드 간)가 처음부터 깨질 수 있다.
내가 선택한 전략은:
- “정책/관측”보다 “연결 안정성”이 먼저라서 Flannel 유지
- 대신 Flannel의 전제(노드 IP를 타는 VXLAN)를 만족시키기 위해
node-ip + flannel-iface를 wg0로 고정 - 결과적으로 노드 간 데이터플레인을 10.99(wg0) 로 통일