20.3 네트워크 및 연결 장애 트러블슈팅

20.3 네트워크 및 연결 장애 트러블슈팅

분산 컴퓨팅의 가장 유명한 8가지 대전제(Fallacies of Distributed Computing) 중 첫 번째는 “네트워크는 신뢰할 수 있다(The network is reliable)“는 착각이다. 로컬 루프백(127.0.0.1)에서 완벽하게 동작하던 Zenoh 애플리케이션 코드를 실제 공장의 와이파이(Wi-Fi), 5G망 통신, 그리고 거대 클라우드 벤더(AWS, GCP)의 가상 네트워크 환경 위로 옮기는 순간, 당신의 코드는 혹독한 시험에 빠지게 된다.

기존의 HTTP 기반 마이크로서비스는 로드밸런서(Load Balancer) 뒤에 정적으로 묶여 있지만, Zenoh 노드들은 동적으로 멀티캐스트(Multicast) 패킷을 뿌리며 서로를 탐색하고 그물망(Mesh Topology)을 스스로 지어 올린다. 이 역동성은 네트워크가 끊길 때 양날의 검으로 작용하여, 다음과 같은 세 가지 주요 장애 축을 만들어낸다.

  1. L2/L3 스카우팅 단절 (Discovery Failures): 멀티캐스트가 차단되거나 인터페이스가 꼬이면서 바로 옆에 있는 노드를 찾지 못하는 일명 ‘투명망토’ 장애.
  2. L4 포워딩 및 터널링 병목 (Forwarding Bottlenecks): 방화벽 패킷 드롭, NAT 환경 이면의 공인/사설 IP 혼선, 그리고 VPN 암호화 오버헤드로 인한 MTU 단편화 현상.
  3. L7 세션 관리의 한계 (Session Breakdowns): 물리적 네트워크 단전 후 쏟아지는 좀비(Zombie) 세션 리스크와, 분단된 망이 다시 합쳐질 때 발생하는 스플릿 브레인(Split-brain) 동기화 충돌.

본 절에서는 이러한 거시적 인프라 네트워크 상의 장애물들을 하나씩 극복하는 파훼법을 배운다. 단순히 포트를 열고 닫는 수준을 넘어, 스카우팅 프로토콜을 해부하고 꼬여버린 DNS 규칙과 하위 커널 MTU 사이즈 튜닝까지 아우르는 딥-다이브(Deep-dive) 트러블슈팅으로 Zenoh 백본을 철통같이 방어하라.

1. 스카우팅(Scouting) 및 디스커버리(Discovery) 실패 원인 분석

Zenoh가 IP 하드코딩 없이도 구동 버튼만 누르면 서로를 찾아내는 마법의 원천은 UDP IPv4 Multicast (기본값: 224.0.0.225:7447) 또는 IPv6 Multicast 기반의 스카우트 메시지에 있다. 코드를 기동했을 때 “Failed to open session: no locator“와 함께 아무것도 검색되지 않는다면, 마법이 깨진 것이다. 철저히 물리/데이터 링크 계층부터 디버깅하라.

1.0.1 단계: 패킷 캡처를 통한 스카우팅 송출 확인

애플리케이션이 스카우팅 패킷을 제대로 뱉어내고 있는지 OS 레벨에서 확인해야 한다. 센서가 물려있는 리눅스 머신에서 tcpdump를 켜라.

## 멀티캐스트 대역으로 송출되는 UDP 트래픽 캡처
sudo tcpdump -i wlan0 -nn -vvv host 224.0.0.225

출력이 전혀 나오지 않는다면 해당 기기의 Zenoh 데몬이 포트를 점유하지 못했거나 데몬 자체가 죽어 있는 상황이다.

1.0.2 단계: 다중 네트워크 카드(Dual-NIC) 바인딩 꼬임

가장 흔하게 겪는 함정이다. 라즈베리 파이에 무선 랜(wlan0)과 유선 랜(eth0), 그리고 도커 브릿지(docker0)가 함께 열려있을 때, Zenoh는 기본적으로 가용한 첫 번째 네트워크 망으로 브로드캐스팅을 시도한다. 만약 스카우팅 패킷이 인터넷이 닿지 않는 로컬 도커망(docker0)으로만 흡수되고 있다면 외부와의 디스커버리는 전면 차단된다.
해결책: 통신 설정 파일에서 명시적 바인딩을 강제하라.

// zenohd.json5
scouting: {
  multicast: {
    interface: "wlan0",   // 정확히 와이파이 카드만 사용!
    multicast_ipv4_address: "224.0.0.225:7447"
  }
}

1.0.3 단계: 도커 브릿지의 기본 격리 메커니즘

동일한 PC에 라우터 컴포즈 A와 클라이언트 B를 띄웠는데 서로 찾지 못하는가? 도커의 디폴트 브릿지 네트워크(bridge)는 컨테이너 각자에게 사설 대역을 부여하고 UDP 멀티캐스트 전파를 컨테이너 외부 반경(Host 망)으로 넘기지 않는다. 스카우팅과 도커는 최악의 상성이다.
이를 해결하려면 실행 커맨드에 호스트 네트워크 공유를 선언하라.

## 도커 컨테이너가 Host의 네트워크 인터페이스를 완벽히 공유하게 만듦
docker run --network host eclipse/zenoh:latest

1.0.4 단계: L2 스위치의 IGMP Snooping 차단

운영 환경인 공장의 통신 장비(L2/L3 Managed Switch)가 대역폭 절약을 위해 IGMP Snooping을 너무 타이트하게 걸어, 알 수 없는 224.x.x.x 트래픽을 정크 메일처럼 폐기(Drop)하고 있을 수 있다. 스위치 장비의 관리자 콘솔에 진입하여 Zenoh의 멀티캐스트 IP를 명시적 예외 처리(Whitelist)로 승인하라.

2. 멀티캐스트(Multicast) 비활성화 또는 차단 환경에서의 연결 문제

공장 밖으로 벗어나 AWS, GCP 등 퍼블릭 클라우드 인스턴스로 Zenoh 관제 서버를 올리는 순간, 멀티캐스트의 마법은 끝난다. 퍼블릭 클라우드 벤더는 브로드캐스트 스톰으로 인한 인프라 파괴를 원천 차단하기 위해 VPC망 전체에서 멀티캐스트를 차단(Drop)한다.
이 척박한 환경에서 Zenoh 노드들을 하나로 결속시키는 플랜 B, ‘가십(Gossip)과 하드코딩(Hardcoding)’ 전략을 즉각 발동하라.

2.0.1 해결 전략 1. 코어 앵커망(Anchor Network) 하드코딩

멀티캐스트가 막혔다면 1:1 통신(Unicast)으로 연결의 불씨를 지펴야 한다. 에지에 위치한 로봇(클라이언트)은 클라우드의 메인 라우터 IP 중 하나를 무조건 명시적으로 지정하여 찾아가야만 한다(이를 앵커 포인트라 부른다).

## 로봇의 기동: 디스커버리가 안 되므로 타겟 IP를 강제로 찍고 찔러 넣는다.
zenoh-bridge-dds -e tcp/203.0.113.50:7447

앵커 라우터에 한 번 성공적으로 유니캐스트 연결(Connect)을 맺고 나면, Zenoh 내부의 가십(Gossip) 프로토콜 기능이 깨어나 라우터 뒤에 숨겨진 또 다른 라우터와 클라이언트들의 “주소록(Routing Table)“을 동적으로 교환받고 완전한 메시(Mesh) 토폴로지의 일원으로 승격된다.

2.0.2 해결 전략 2. K8s 내부에서의 DNS(Headless Service) 활용

클라우드 인스턴스 대신 쿠버네티스(Kubernetes) 팟(Pod)으로 라우터들을 띄웠을 경우 IP는 언제든 죽고 새로 생성되어 변한다. 하드코딩이 불가능해진다.
이때는 K8s의 DNS를 디스커버리 대체재로 사용하라. Zenoh 라우터 클러스터용 서비스 리소스(Headless Service)를 배포한 뒤, 클라이언트는 해당 도메인 이름만 주시하게 만든다.

## K8s Headless Service 매니페스트 예시
apiVersion: v1
kind: Service
metadata:
  name: zenoh-router-svc
spec:
  clusterIP: None  # Headless 모드로 각 라우터 파드의 다수 IP를 반환
  selector:
    app: zenoh-router
  ports:
    - port: 7447

클라이언트는 tcp/zenoh-router-svc.default.svc.cluster.local:7447로 연결을 시도한다. DNS가 알아서 생존해 있는 라우터 파드의 유니캐스트 IP 중 하나로 이들을 무작위 결속(Random Load-balancing)시켜 줄 것이다.

멀티캐스트는 없으면 불편할 뿐, 필수 불가결한 기능이 아니다. 명확한 언더레이(Underlay) 주소 체계 파이프라인만 수립한다면 멀티캐스트 없이도 초거대 Zenoh 망을 직조할 수 있다.

3. 방화벽, NAT, VPN 환경에서의 패킷 드롭 및 터널링 문제 해결

로컬 테스트 벤치를 벗어나면, 패킷은 필수적으로 두꺼운 보안 장벽(NAT, VPN 터널)을 지나야만 목적지에 도달한다. “도저히 라우터로 핑(ping)은 나가는데 Zenoh만 붙질 않는다“면 당신의 회사는 뛰어난 방화벽을 가지고 있음을 기뻐하고, 네트워크 L4/L3 지식을 동원해 터널을 뚫어야 한다.

3.0.1 단계: 보안 그룹(Security Group) 및 7447 포트 개방

AWS 환경의 라우터를 향해 에지(Edge) 로봇이 데이터 연결을 시도한다면, AWS EC2 인바운드 보안 그룹을 찢어 열어야 한다. Zenoh는 멀티 트랜스포트 구조다.
TCP용 통로 하나만 열어서는 안 된다. 저지연 Liveliness 체크 및 QUIC 통신을 위해 방화벽 설정에 TCP 7447UDP 7447 인바운드를 한 쌍으로 모두 허용(Allow) 상태로 만들어라.

3.0.2 단계: NAT(Network Address Translation) 통과 - Listen vs Locator

가장 빈번하게 엔지니어를 함정에 빠뜨리는 문제다. 본사의 로컬 서버(사설 IP 192.168.0.100)에 라우터를 띄우고 회사의 외부 고정 IP(210.x.x.x)를 라우터로 포트 포워딩(Destination NAT) 매핑을 해두었다 하자.

외부 클라우드 서버가 회사 내 라우터로 접속하기 위해 스카우팅 및 가십 데이터를 교환받으면, 이 멍청한 로컬 서버 라우터는 자기가 아는 자기 IP가 192.168인 줄 알고 외부 망 노인 클라우드 쪽에 “나를 찾으려면 192.168.0.100으로 연락해“라는 응답 패킷을 보내어 외부 클라이언트 망 접속을 전부 데드락 시킨다.
반드시 locator 설정을 명시하여, “내가 바인딩은 로컬에 하지만 남이 나를 찾을 수 있는 진짜 마스크(공인 주소)는 이거야“라고 선언하라.

// 로컬 망 내부 라우터용 NAT 우회 설정
{
  listen: ["tcp/0.0.0.0:7447"],
  // 외부에 홍보(Advertise)할 때는 무조건 아래 공인IP 주소만 알림!
  locators: ["tcp/210.123.45.67:7447"]
}

3.0.3 단계: VPN 터널링 계층의 MTU 단편화(Fragmentation) 장애

공장과 클라우드를 IPSec 기반 VPN으로 완벽히 암호화하여 묶어두었는데, 작은 센서 패킷은 잘 전송되지만 1MB가 넘는 LiDAR 포인트 클라우드 맵 데이터만 보내면 아무 예고 없이 중간 증발(Drop)하는 무서운 증상이다.

이는 보통 QUIC 기반 UDP 트랜스포트를 사용할 때 터진다. 일반 이더넷의 기본 MTU(가장 큰 전송 조각)는 1500바이트인데, VPN은 자기네들 암호화 헤더를 통신에 우겨넣기 위해 실 포장 공간을 1420바이트 내외로 줄인다. Zenoh가 패킷을 1450바이트 조각으로 자른 후 날리면 VPN 커널이 이 패킷을 수용하지 못하고 파열(fragmentation fail)시켜 폐기해 버린다.
해결책은 두 가지다. 단말 커널망에서 강제로 ifconfig eth0 mtu 1400을 걸어 OS가 작은 사이즈로 쪼개도록 튜닝하거나, 단편화 관리가 완벽하게 커널 딴에서 동작하는 TCP 트랜스포트 베이스로 강제 고정하여 라우팅하라.

4. 세션 타임아웃(Timeout) 및 네트워크 단절 후 재연결(Re-connection) 지연 현상

자율주행 셔틀이나 모바일 통신(5G/LTE)망이 얹힌 환경에서는 영구적인 통신망은 존재하지 않는다. 건물 사이에서 AP(Access Point)가 핸드오버를 시도할 때 일어나는 2초간의 침묵, 엘리베이터 통과 시 10초의 단절 등 빈번히 발생하는 이음매의 파손을 시스템이 어떻게 인지하고 회복하느냐가 신뢰성의 관건이다.

4.0.1 좀비(Zombie) 세션과 하트비트(Heartbeat) 최적화

로봇이 엘리베이터에 들어가 접속이 완전히 단절되었는데, 클라우드 라우터 측에서는 이를 30초 가까이 인지하지 못하고 로봇 방향으로 제어 명령 버퍼를 무한정 쌓고 있는 치명적 낭비가 발생한다. 이는 기본 Keep-Alive와 하트비트의 타임아웃 주기가 너무 넉넉하게 잡혀있기 때문이다.

라우터와 클라이언트는 Liveliness 통신 프로토콜을 사용해 맥박을 검사한다. 모바일성이 생명인 아키텍처라면 이 맥박 검사 타임아웃(lease_period)을 기존 디폴트인 수십 초 대역에서 과감하게 2초 ~ 3초 레벨로 다운사이징하라.
빠르게 로봇이 죽은 것을 선언하고 버퍼를 버리는 것이, 메모리 오버플로우를 막고 다시 등장한 로봇과 산뜻하게 새 클린 세션(Clean Session)을 맺는 최고의 방어벽이다.

4.0.2 치명적인 지터 스트레스 - 재연결 지수 백오프 (Exponential Backoff)

스마트 팩토리 전역의 전원이 깜빡거리며 스위치가 리부팅되었다 치자. 공장 내 1만 개의 센서 노드가 통신이 끊김을 감지한 후 일제히 0.01초 뒤 라우터를 향해 재접속 포화(Connection Flood) 세례를 퍼붓는다. 단언컨대 어떤 라우터도 이 DDoS 수준의 폭주를 견디지 못하고 또다시 다운(Crash)된다.

신규 코드를 짤 때 가장 중요한 핵심 생존 패턴이 지수 백오프(Exponential Backoff) 및 랜덤 난수(Jitter) 주입 로직이다. Zenoh 세션 객체가 끊어졌을 때 클라이언트 재연결 스크립트를 다음과 같이 설계하라.

// TypeScript/Node.js 베이스의 클라이언트 백오프 모범 사례
let retryDelayMs = 1000;
const MAX_DELAY = 60000; // 최대 1분까지 대기 간격 벌림

async function connectToZenohWithBackoff() {
    while (true) {
        try {
            console.log(`라우터 커넥션 시도 중...`);
            const session = await zenoh.open(config);
            console.log("연결 복구 성공!");
            retryDelayMs = 1000; // 성공 시 지연시간 초기화
            return session;
        } catch (error) {
            // 지수적으로 실패 지연시간 상승 + 임의의 난수(Jitter)를 섞어 쏘는 시간대를 회피 분산
            retryDelayMs = Math.min(retryDelayMs * 2, MAX_DELAY);
            const jitter = Math.floor(Math.random() * 500);
            await new Promise(resolve => setTimeout(resolve, retryDelayMs + jitter));
        }
    }
}

통신 장애 복구에 여유(Delay)를 두지 않는 개발자는 결국 데드락이라는 참사에 직면한다. 조금 둔감하더라도 집요하게 살아남는 코드가 승리한다.

5. 스플릿 브레인(Split-brain) 현상과 네트워크 파티션 복구

거대한 클라우드 대륙과 거대한 공장 대륙을 잇는 단 하나의 광케이블이 굴착기에 찍혀 단선(Partition)이 되었다.
이 끔찍한 절단면 양쪽에서, 각 대륙의 라우터 클러스터들은 상대방이 죽었다고 선언한 뒤 스스로 두 개의 작은 독립된 거점이 되어 분리망(Split) 통신을 재개한다. 우리는 이 재앙을 스플릿 브레인(Split-brain) 이라 부른다.

5.0.1 스플릿 브레인의 파괴적 충돌

통신망 단선 상태에서 치명적인 문제(Inconsistency)가 발생한다. 어제 오후 1시에 고정시켜둔 로봇팔의 기준 좌표 정보 토픽(/factory/robot_1/calibration)을,

  • 통신이 끊긴 공장 쪽 라우터 관리자가 [A 상태]로 수정하여 Put을 때렸다.
  • 완벽히 같은 모델을 바라보는 클라우드 서버 관리자가 이 로봇이 고장 난 줄 알고 [B 상태] 값으로 수정하여 DB에 Put을 날렸다.

30분 뒤 복구반이 광케이블을 다시 이었다. 공장 망과 클라우드 망은 열렬히 포옹(Merge)하며 서로 그동안 쌓여있던 정보의 변경 사항(Routing & Storage Updates)을 병합한다. 하지만 로봇팔 좌표 토픽에서 AB라는 두 개의 진실이 충돌한다. 누가 살아남아야 하는가?

5.0.2 Hybrid Logical Clock (HLC)의 분쟁 중재 메커니즘

DDS 시스템이었다면 이 파티션 충돌 해결에 매우 커스텀하고 피곤한 설계가 필요하지만, Zenoh는 분산망에 최적화된 하이브리드 논리 클락(Hybrid Logical Clocks, HLC) 매커니즘을 내장하고 있다.

  • 모든 Zenoh 메시지는 물리적 시간(NTP 시간표)과, 물리 시간이 동일할 때 상태 기계적으로 발급하는 내부 카운터를 섞어 고유한 무결성 타임스탬프를 부여받는다.
  • 파티션이 병합될 때 같은 키(Key)에 여러 데이터가 충돌을 내면, Zenoh 프로토콜은 기계적으로 타임스탬프가 더 최신인 발행자의 데이터를 승리 시스템으로 판정하는 Final-Writer-Wins (FWW: Last-Writer-Wins 기반 변형) 정책을 발동하여 충돌 데이터를 덮어씌워 일관성을 강제 치유한다.

5.0.3 장애 복구를 대비하는 개발자의 설계 원칙

Zenoh가 아무리 훌륭히 단선 복구(Merge)를 지원해도, 스플릿 브레인 충돌을 피하는 구조적 원칙은 당신의 몫이다. 단일 도메인의 통계나 제어 토픽(State)을 퍼블리시 하는 Publisher의 권한을 절대 두 대륙에 쪼개(양방향 제어) 설계하지 마라.
로컬 제어 권한은 로컬 머신 하나에 몰아주거나, 클라우드가 무조건 중앙 통제하도록 퍼블리셔의 기원을 선형(Linear)으로 설계해 두면 네트워크 파티션 병합 과정에서 피를 보는 일은 한층 줄어들 것이다.