아무도 못 본 72GB
시스템을 습관처럼 들여다보던 중에 en0 인터페이스가 부팅 이후 30분 동안 72.5GB를 밖으로 내보낸 걸 발견했다. 그 순간 잡힌 전송률은 12.9KB/s에 불과했지만, 누적치가 문제를 보여줬다. 추적해보니 Time Machine이 전날 들어온 118GB짜리 OneArca 폴더를 다른 기기로 밀고 있었다. 이 자체는 잘못된 동작이 아니었다 — 백업 도구는 제 할 일을 하고 있었다. 문제는 그게 일어나고 있다는 사실을 아무도 알려주지 않았다는 것이었다. 누적을 세는 장치가 없으면 이런 건 영영 모르고 지나간다.
상시 감시기를 만든 이유
대응은 작고 항상 켜져 있는 모니터였다. 하나는 60초마다 네트워크 인터페이스·도달성·프로세스별 트래픽을 표본으로 찍어 로그에 이어붙인다. 다른 하나는 10분마다 회선 품질(지연·손실)을 더 비싼 방식으로 확인한다. 이 둘을 가른 이유가 있다 — 손실 측정 한 번에 걸리는 시간이 60초 주기 안에 다른 모든 작업과 함께 끼워 넣기엔 너무 길었다.
만들면서 걸린 작은 함정들
권한이 필요할 거라 예상했던 프로세스별 트래픽 조회가 실은 관리자 권한 없이도 되는 도구가 있었다 — 다른 방식(패킷 목적지까지 보는 것)은 여전히 권한이 필요해서 그건 빼기로 했다. 감시를 위해 권한을 올리는 순간 상시 실행의 안전성이 흔들린다는 게 이유였다.
네트워크 인터페이스 통계를 위치로 읽는 스크립트도 하나 부러졌다 — 인터페이스마다 하드웨어 주소(MAC) 유무가 달라서, 있고 없음에 따라 그 뒤 모든 열의 위치가 밀렸다. 앞에서 세지 않고 끝에서부터 고정된 열 개수만큼 거꾸로 세는 방식으로 고쳤다.
가장 크게 걸린 함정은 따로 있었다. 신설 직후 표본의 98%가 네트워크 값 없음으로 찍혔다. 원인을 추적하니 tailscale status --json 명령이 반환 코드는 0인데 표준출력이 JSON이 아니라 “GUI가 시작되지 못했다”는 문자열이었다. 앱스토어판 CLI는 GUI 앱을 거쳐 통신하는데, 사람이 없는 launchd 환경에서는 그 GUI에 붙을 수가 없었다. 대화형 셸에서는 멀쩡하게 동작해서 개발 중에는 전혀 눈에 띄지 않았다. 해결은 CLI에 의존하지 않는 경로를 기본으로 두는 것이었다 — 노드 IP로 직접 핑을 쏘아 살아있는지 확인하고, CLI가 붙을 수 있을 때만 더 풍부한 정보(직결인지 릴레이인지)를 보강했다.
이 사고는 배운 것 하나를 남겼다 — 대화형 셸에서 워밍업된 성공은 실제 무인 실행 환경에서의 성공을 보장하지 않는다. 냉간 상태로, 실제로 launchd를 통해 다시 켜봐야만 드러나는 실패가 있었다.