"서비스는 멀쩡하고, health check 도 초록불이고, 저쪽 머신으로 가는 호출은 전부 실패해. 같은 바이너리, 같은 주소인데 셸에서는 즉시 돼."
세 번째, 같은 모양
이제 두 번 봤지. 셸에서는 즉시 풀리는데 서비스 매니저 밑에서는 멈추는 sync 경로. 대화형 세션에는 OS 가 내주고 서비스 매니저가 띄운 같은 인터프리터에는 거부하는 권한. 여기 세 번째가 있고, 이건 따로 레슨을 받을 값어치가 있어. 셋 중에 인프라 문제처럼 제일 안 보이는 놈이거든.
Recall 의 control plane 은 파생 작업 일부 때문에 저쪽 머신에 전화를 걸어. 어느 날 그 호출이 전부 실패하기 시작했어. 하나도 빠짐없이, 즉시, "no route to host" 로. 뻔한 결론은 저쪽 머신이 죽었다는 거야. 안 죽었어. health check 가 답해. SSH 로 건너가서 똑같은 호출을, 똑같은 Python 으로, 똑같은 주소에 대고 돌리면 몇 분의 일 초 만에 돌아와. 서비스를 다시 돌리면 또 실패해. 똑같이, 매번.
그 결정론이 단서고, 본능이 말하는 것과 정반대야. 진짜로 불안정한 네트워크는 간헐적으로 실패해. 판정이 고정돼 있다는 건, 그러니까 실행할 때마다 같은 답이 나오고 한쪽 launch context 에서만 그렇다는 건, 네트워크 얘기가 아니야. 패킷이 나가기도 전에 내 프로세스에 대해 내려지는 정책 결정이지.
요즘 macOS 가 실제로 막는 것
최근 macOS 는 내 로컬 네트워크의 다른 머신과 말하는 걸 카메라처럼 프라이버시 사안으로 취급해. Apple 기술 노트가 규칙을 적어놨고, 그중 둘이 여기 피해를 다 입혀.
- 누가 자동 허용되나. 서비스 매니저가 띄운 daemon 은 허용. root 로 도는 프로그램도 허용. Terminal 이나 SSH 로 돌린 커맨드라인 도구도 허용이고, 그것들이 띄운 자식 프로세스까지 허용. 마지막 그 조항이 바로 내 손 테스트가 늘 통과하는 이유야.
- 믿고 있던 예외가 나를 안 덮어. launch daemon 자동 허용은 launch agent 로는 명시적으로 안 이어져. 그리고 로그인한 계정에 속한 걸 돌리는 평범한 방법인 사용자별 백그라운드 서비스는 agent 야. 실패하는 범주가 하필 다들 실제로 배포하는 그 범주인 거지.
그리고 허가는 내 코드에 붙지 않아. 시스템이 책임 있는 프로그램을 추적해서 사용자의 선택을 거기다 기록해. 앞 레슨의 파일 접근 허가와 똑같은 모델이고, 서브시스템만 달라. 안정된 신원을 가진 번들 앱은 사람이 알아보고 승인할 수 있는 durable 한 행 하나를 얻어. 벌거벗은 인터프리터는 못 얻고. 환경을 하나 만들 때마다 익명 항목이 하나씩 더 생기고, 환경을 다시 빌드하면 새 걸 하나 찍어서 또 처음부터 승인받아야 해. 읽을 수 없는 목록은 관리할 수가 없어.
해법은 로컬 네트워크에서 아예 빠져나오는 것
여기가 이 전쟁담이 앞 것과 갈라지는 자리야. 파일 접근에서는 답이 안정된 신원을 소유하는 거였어. 허가가 얹혀 살 수 있는 서명된 런처. 그게 통한 건 소유할 신원이 딱 하나였기 때문이야. 여기선 아니야. 목록은 한도 없이 자라고 모든 행이 똑같이 생겨서, 아무리 클릭해도 수렴하지 않아.
그래서 해법은 막힌 네트워크를 통째로 떠나는 거야. 기술 노트 자신의 정의가 탈출구야. 로컬 네트워크란 broadcast 가능한 인터페이스에 딸린 네트워크야. Wi-Fi 와 이더넷 같은 것. 그리고 VPN 인터페이스는 명시적으로 제외되고. 그러니까 내 머신들 사이의 암호화된 오버레이 네트워크를 타는 트래픽은 애초에 로컬 네트워크 연산이 아니고, 게이트를 만날 일이 없어. Recall 의 머신 간 호출은 이제 .local 로 끝나는 이름이나 옆방 선 위의 주소 대신 오버레이 이름으로 상대를 불러. 같은 호출이 경로만 달라지면서 허가가 필요한 종류의 호출이기를 그만두는 거지.
같은 정의가 무관해 보이던 증상들도 설명해줘. .local 로 끝나는 이름을 푸는 것 자체가 로컬 네트워크 접근을 요구하니까, mDNS 이름은 연결을 시도하기도 전에 실패해. 같은 방 머신 둘을 직접 잇는 점대점 링크도 다른 것과 똑같은 broadcast 가능 인터페이스라서, 집 네트워크만큼이나 똑같이 막혀. 말이 안 되게 느껴지지만, 게이트가 보는 게 얼마나 멀리 있느냐가 아니라 누구와 말하느냐라는 걸 떠올리면 앞뒤가 맞아.
진단법, 그리고 왜 하나 필요한가
이 실패는 사람을 엉뚱한 머신으로 보내는 데 유난히 재능이 있어. 원격 서비스는 멀쩡하니까 대시보드는 초록불이야. 바깥으로 전화 거는 코드 경로만 깨지니까 증상이 네트워크 고장이 아니라 데이터 누락처럼 보이고. 클라이언트 라이브러리는 그걸 연결 오류로 보고하는데, 그건 "서버가 죽었다" 로 읽혀. 모든 본능이 진실 반대쪽을 가리켜.
그러니까 한 단계짜리 시험을 익혀두고, 백그라운드 서비스와 셸의 말이 엇갈릴 때마다 꺼내 써. 같은 바이너리를 서비스 매니저 밑에서 한 번, 셸에서 한 번 돌려서 비교해. 바이너리 하나가 주소 하나에 대고 답 둘을 내놓는 건 네트워크 상태가 아니야. launch context 판정이야. 이 트랙 전체가 그 한 문장의 변주 셋이었고.