7.3 세션(Session) 관리 및 환경 설정(Config)
파이썬 개발자들이 가장 많이 하는 착각은, Zenoh가 redis나 paho-mqtt처럼 단순한 파이썬 소켓 래퍼(Wrapper)일 것이라는 생각이다.
Zenoh의 Session은 파이썬 객체가 아니다. 그것은 당신의 메인 스레드 바깥에서 수많은 I/O 스레드를 띄우고 멀티캐스트 핑을 쏘며 라우팅 테이블을 구성하는 하나의 ’미니 OS 허브’다.
따라서 이 세션을 어떻게 열고 닫느냐, 그리고 어떤 옵션(Config)을 먹여서 깨우느냐에 따라 파이썬 스크립트가 리눅스 커널을 마비시킬 수도 있고, 초당 수 기가바이트의 데이터를 뽑아낼 수도 있다.
이 챕터에서는 파이썬 고유의 Context Manager(with 구문)를 활용한 완벽한 스레드 소멸 전술과, 데브옵스 인프라와 결합하기 위한 외부 JSON5/YAML 설정 덮어쓰기 기법을 다룬다.
1. 기본 세션 초기화 및 리소스 종료 전략 (Context Manager 활용)
파이썬 환경에서는 zenoh.open() 을 치면 즉시 연결되지만, 스크립트가 끝났거나 에러가 났을 때 이 세션(session.close())을 닫아주지 않으면 백엔드의 Rust 스레드들이 파이썬 프로세스를 붙잡고 메모리 좀비(Zombie)로 남아버린다.
따라서 Python Zenoh 개발 시에는 C++의 RAII 패턴에 해당하는 with 구문 도입이 생명선이다.
1.0.1 [Runbook] 프로덕션 세션 록인(Lock-in) 전술
import zenoh
import time
def run_main_loop():
print("Zenoh 라우터 접속 시도...")
# [핵심] with 문을 사용하면, 블록을 빠져나오거나 에러로 인해 크래시가 발생하더라도
# 파이썬 인터프리터가 __exit__ 매직 메서드를 통해 세션을 완벽하게 z_close() 해준다.
with zenoh.open() as session:
print(f"세션 획득 성공. ID: {session.info.zid()}")
# 비즈니스 로직 구동 (무한 루프 혹은 퍼블리싱 로직)
pub = session.declare_publisher("robot/logs")
pub.put("시스템 부팅 완료")
# 일부러 에러를 발생시켜 본다
# 1 / 0
time.sleep(2)
print("이 줄에 도달할 때쯤이면, TCP 소켓은 100% 끊어져 있다.")
if __name__ == "__main__":
try:
run_main_loop()
except Exception as e:
print(f"치명적 버그 발생: {e}")
만약 부득이하게 전역(Global) 변수나 싱글톤으로 세션을 선언했다면, atexit 모듈을 쓰거나 스크립트 종료 이벤트 핸들러(Signal SIGINT)를 낚아채어 명시적으로 session.close()를 파괴해야만 클라우드 라우터 단에서 “Disconnected” 에러 타임아웃을 온전히 줄일 수 있다.
2. YAML 및 JSON 파일을 이용한 외부 설정 로드
인프라스트럭처 엔지니어(DevOps)가 Kubernetes 파드(Pod)에 파이썬 스크립트를 밀어 넣을 때는, 파이썬 코드 안에 하드코딩된 ‘192.168.0.100’ 따위의 수정을 절대 허락하지 않는다.
Zenoh의 Config 객체는 외부의 JSON5 (주석이 달린 JSON) 파일을 읽어 완벽히 덮어쓰는 외부화 전술을 지원한다.
2.0.1 [Runbook] 환경망(Topology) 분리 주입 전술
1. 설정 파일 준비 (router_config.json5)
{
mode: "client", // 라우터 역할 거부, 말단 노드로 행동
connect: {
// [중요] 쿠버네티스 서비스 DNS 주소나 고정 IP 명시
endpoints: ["tcp/zenoh-cloud-router.default.svc.cluster.local:7447"]
},
transport: {
tcp: { nodelay: true } // 초단타 Nagle 알고리즘 해제
}
}
2. Python 코드 내 의존성 주입
import zenoh
import json
def init_zenoh_from_file(config_path="router_config.json5"):
# 1. 텅 빈 기본 Config 껍데기 확보
conf = zenoh.Config()
try:
# [핵심] JSON5 포맷으로 작성된 설정 파일을 Python에서 파싱하거나,
# Zenoh 파이썬 바인딩 내부의 json5_insert 함수로 통째로 먹일 수 있다!
# 가장 우아한 방식은 파일의 텍스트 자체를 Config 객체로 변환하는 것이다.
conf = zenoh.Config.from_file(config_path)
print(f"[{config_path}] 설정 주입 완료.")
except Exception as e:
print(f"[경고] 설정 로드 실패. 기본값으로 폴백합니다. 원인: {e}")
conf = zenoh.Config()
session = zenoh.open(conf)
return session
if __name__ == "__main__":
with init_zenoh_from_file() as s:
print("연결 완료")
이렇게 하면, 도커(Docker) 컨테이너를 띄울 때 파이썬 코드는 1바이트도 수정하지 않은 채 -v 볼륨 매핑으로 클라우드용 config.json5나 로컬 개발용 파일을 꽂아주기만 하면 아키텍처 토폴로지가 유동적으로 조작된다.
3. 프로그래매틱 방식의 설정 객체 제어 및 동적 변경
항상 파일에서 설정을 읽을 수 있는 건 아니다. Python 애플리케이션의 런타임 환경 변수(os.environ)에 라우터 IP가 할당되거나, 동적으로 네트워크 모드를 스위치해야 할 때는 프로그래매틱한 키-밸류(Key-Value) 트리거 조작이 답이다.
3.0.1 [Runbook] 메모리 인젝션(Config Injection) 기법
Zenoh 파이썬 객체는 내부의 Rust 설정 트리를 건드리기 위해 insert_json5 라는 공격적인 주사기를 제공한다.
환경 변수 기반의 동적 라우팅 구성
import zenoh
import os
def boot_zenoh():
# 1. 기본 인프라 호출
conf = zenoh.Config()
# 2. 파이썬 환경의 os.environ 을 조회한다
router_ip = os.getenv("ZENOH_ROUTER_IP", "")
if router_ip:
# [핵심 전술] 특정 경로(connect/endpoints)를 타겟으로 JSON5 문법 규격의 문자열을 때려 넣는다.
# 주의: 문자열 따옴표 안에 또 "쌍따옴표"나 "배열 모양([])"을 지켜야 한다!
endpoint_str = f'["tcp/{router_ip}:7447"]'
# 키(Key) 경로와 밸류(String) 투하
conf.insert_json5("connect/endpoints", endpoint_str)
# 스카우팅(자동 검색) 끄기 - IP가 명시되어 있으니 브로드캐스트 스톰 낭비를 막는다.
conf.insert_json5("scouting/multicast/enabled", "false")
print(f"명시적 라우터 타겟 모드 발동: {endpoint_str}")
else:
print("환경 변수가 없습니다. 로컬 멀티캐스트 스카우팅 탐색 모드(기본값)로 개방합니다.")
# 3. 돌연변이화된 Config 객체로 세션을 쾅 연다
return zenoh.open(conf)
if __name__ == "__main__":
with boot_zenoh() as session:
pass
이 기술을 응용하면, argparse 파이썬 패키지로 스크립트 실행 인자(python app.py --mode=peer)를 받아, conf.insert_json5("mode", '"peer"') 따위의 CLI 도구 사령탑을 구성하는 것도 식은 죽 먹기다.
4. 스카우팅(Scouting) 메커니즘 설정 및 피어 검색 최적화
IP를 일일이 하드코딩하지 않아도 파이썬 스크립트가 로컬 네트워크(LAN)의 라우터를 기가 막히게 찾아내는 마법.
그 실체는 UDP 멀티캐스트 포트(버전 구형 7447, 모던 설정 대역)로 끊임없이 수색조(Scout 핑)를 날리는 것이다.
하지만 수천 대의 파이썬 스크립트 센서가 핑을 쏴대면 망 전체가 브로드캐스트 폭풍(Storm)에 휩싸여 망이 마비된다.
4.0.1 [Runbook] 레이더 탐지 범위(Scope) 제한 전술
내가 쏘는 스카우팅 패킷이 L2 스위치를 넘어갈 수 없게 차단하여 보안과 망 최적화를 이뤄내야 한다.
import zenoh
import time
def optimized_scouting_session():
conf = zenoh.Config()
# 전술 1. 멀티캐스트 인터페이스(랜카드) 강제 결속
# 와이파이와 이더넷이 둘다 꽂혀있을 때, 엉뚱한 망으로 핑이 나가지 않도록 고정
conf.insert_json5("scouting/multicast/interfaces", '["eth0"]')
# 전술 2. 네트워크 반경(TTL: Time To Live) 축소
# 라우터를 딱 1개(로컬 망)만 건너뛰고, 무의미하게 망 전체를 폭격하는 캐스트를 막는다
conf.insert_json5("scouting/multicast/ttl", "1")
session = zenoh.open(conf)
return session
def manual_radar_scan():
"""세션을 맺기 전에 내 주변에 어떤 라우터/워커 노드가 있는지 파이썬으로 사전 조사하는 기능"""
print("망 내 피어/라우터 수색 개시 (1초간 대기)...")
# 이 제너레이터(yield) 시스템은 Zenoh가 주변에서 응답받은 라우터 객체들을 반환한다.
routers = zenoh.scout(zenoh.Config(), timeout=1.0)
for hello in routers:
print(f"발견된 타겟 PID: {hello.whatami}")
for loc in hello.locators:
print(f" -> 접근 가능 IP 엔드포인트: {loc}")
if __name__ == "__main__":
# 라우터를 수색해보고 IP를 확인
manual_radar_scan()
# 이후 최적화된 범위로 세션 오픈
with optimized_scouting_session() as s:
time.sleep(1)
이 코드는 파이썬 클라이언트가 장애가 나거나 재부팅되었을 때, 맹목적으로 무한 대기하는 대신 주변의 가장 물리적으로 가까운(Ping이 짧은) 다른 백업 라우터를 동적으로 찾아 접속지(Endpoint)를 갈아타는 고가용성 오토-페일오버(Auto-failover) 시스템의 뼈대가 된다.