런이 'requested'에서 멈춰 있다
원격 GPU 노드(VPN 너머)로 시뮬 런을 보냈는데, 상태가 requested에서 영영 안 바뀌었습니다. 워커가 일을 안 집어가는 겁니다. 노드는 켜져 있고, 터널 프로세스도 살아 있었습니다. 그런데 일이 안 흘렀습니다.
살아 있는데 죽어 있다 — half-open
원격 노드는 역터널(reverse SSH tunnel) 로 중앙에 연결됩니다 — 노드가 자기 포트를 중앙으로 내보내, 중앙이 그 포트로 노드에 작업을 넣죠. 문제는 이 터널이 half-open(반쯤 죽은) 상태가 된 것이었습니다.
도식 렌더링 중…
VPN이 유휴 상태로 끊기자, 노드의 sshd에 역포워드 LISTEN 소켓이 고아(orphan) 로 남았습니다. 자동 재연결(autossh)이 새 터널을 띄우려 해도, 그 포트를 이미 좀비가 물고 있어 bind에 실패했습니다. 결과 — 포워드는 죽었는데 프로세스는 살아 있는 모순. 그래서 "터널 살아 있음"이라는 신호가 거짓이었습니다.
sentinel이 속은 이유
자동 복구 sentinel도 속았습니다.
⚠️ "프로세스 alive ≠ 기능 alive" — sentinel은 autossh 프로세스가 살아 있으면 "복구됨"으로 판정했습니다. 하지만 프로세스가 살아 있어도 포워드가 죽어 있으면 아무 소용이 없습니다. 이 거짓 "복구됨" 때문에 sentinel이 무한 재시작 루프에 빠졌습니다. 헬스체크는 프로세스 존재가 아니라 실제 기능(포워드로 진짜 응답이 오는가)을 봐야 합니다.
진단의 결정적 단서는 이것이었습니다 — 노드에서 localhost:포트로 요청하면 응답 코드가 000(연결 안 됨)인데, autossh 프로세스는 멀쩡히 떠 있었습니다. 이 모순이 "half-open 좀비"를 가리켰습니다.
복구 — 좀비를 먼저 죽여라
도식 렌더링 중…
해결은 순서가 중요했습니다 — autossh를 재시작하기 전에 그 포트를 물고 있는 좀비를 먼저 죽여야 했습니다. 좀비를 안 치우고 재시작하면 또 bind 실패. 좀비 제거 → 재시작 → 실제 포워드로 응답이 오는지 검증. 이 세 단계로 막힌 런들이 다시 흘렀습니다.
💡 헬스체크는 끝단에서 — 중간 프로세스의 생존이 아니라, 데이터가 실제로 끝까지 흐르는가를 확인해야 합니다. "런이 requested에서 안 움직이면" 워커·DB·코드보다 노드 연결의 끝단(포워드가 진짜 사는가)부터 의심하세요.
한 줄 정리
📌 런이
requested에서 멈추면, 원격 노드 역터널의 half-open 좀비 소켓을 의심하라 — VPN 끊김으로 LISTEN이 고아가 되면 autossh가 재시작해도 bind 실패, 프로세스는 살았는데 포워드는 죽는다. 복구는 좀비 먼저 죽이고 재시작 후 실제 포워드로 검증. 헬스체크는 프로세스 생존이 아니라 끝단 기능을 봐야 한다.
