title: 29개 서비스를 대시보드 한 장에 모았더니, 외면하던 503의 정체가 보였다 tags: [서버 운영, 모니터링, 파이썬, 비동기] category: 공부/인공지능 (1015747) visibility: public
랩의 웹 서비스들이 여기저기 흩어져 있었습니다. 본 서버에도 여러 개, LLM을 서빙하는 기계에도 하나, 라즈베리파이 두 대에도 화면들이 있습니다. 세어보니 본 서버까지 일곱 대에 걸쳐 있었지요. "이거 살아있나?"를 확인하려면 브라우저로 하나씩 열어볼 수밖에 없어서, 점검 한 번이 온통 클릭이었습니다. 그래서 아예 목록을 세어보기로 하고 포트 스캔부터 돌렸습니다.
기억 대신 포트 스캔이 준 목록
본 서버의 리스닝 포트와 systemd 유닛, 도커 컨테이너를 싹 훑고, 원격 노드들에 띄워둔 것까지 기억을 더듬어 모았습니다. 실측 결과는 29개. 용도별로 묶으니 여섯 그룹이 나왔습니다.

연구·브레인 쪽이 열한 개, Hermes·모니터링이 일곱 개, 도구 세 개, GPU·LLM 노드 네 개, 음성 PC 하나, 라즈베리파이 화면이 세 개입니다. 라즈베리파이 방문 기록 화면은 이번 집계에서야 빠져 있던 걸 발견해서 28개에서 29개로 늘어난 것입니다.
머리로 기억하던 목록과 스캔이 돌려준 목록은 꽤 달랐습니다. 접었다고 생각한 게 살아있었고, 다른 기계의 같은 번호 포트와 헷갈리는 것도 있었습니다. 이 교훈 때문에 카드 설명에는 어떤 기계의 서비스인지 반드시 적어뒀습니다. 등록부는 서비스 이름에서 주소와 확인 경로를 끄집어내는 딕셔너리 하나로 정리했더니, 새 서비스가 늘었을 때 한 줄만 추가하면 끝이었습니다. 허브 화면 자체는 개인 VPN 주소로만 들게 해두어, 사내망에서는 보이지 않습니다.
29개의 문을 두드리는 데 0.70초
상태 확인은 등록부를 돌리며 문 두드리기(probe)로 짰는데, 첫 버전은 순차 방식이었습니다. 하나 두드리고 응답을 기다리고, 그다음으로 넘어가고요. 응답 없는 서비스에 타임아웃을 물리면 다음 문도 못 열어서, 화면 첫 로딩이 한참을 걸렸습니다. 전부 타임아웃나는 최악 시나리오는 170초(대기시간 합)로 계산됐습니다.
고친 방법은 확인을 병렬로 바꾸는 것이었습니다. 스레드 풀을 만들어놓고 29개 두드리기를 동시에 던졌더니, 오늘 아침 실측으로 전체 상태 판정이 0.70초에 끝났습니다. 최악 계산치보다 200배 넘게 줄어든 셈입니다. 다만 병렬로 옮기던 날에도 사고는 있었습니다. 확인 함수가 값을 세 개 돌려주는데 부르는 쪽은 둘만 풀려다가, 첫 판정부터 프로그램이 튕겼습니다. 포트 스캔보다 이런 데서 시간이 새는 것 같습니다.
결과는 20초 동안 캐시에 얹어둡니다. 화면을 아무리 새로고침해도 서비스들이 맞을 두드리기 횟수를 20초에 한 번으로 묶어두는 셈이라, 대시보드가 오히려 서비스를 공격하는 일은 없습니다. 그 안에 들어온 요청에는 0.0006초로 넘겨주고, 프론트는 30초마다 이 API를 불러 상태등을 갱신합니다.
with ThreadPoolExecutor(max_workers=27) as pool:
futs = {pool.submit(run, spec): pid for pid, spec in PROBES.items()}

불빛은 세 가지로 만들었습니다. 정상 초록, 연결 불가 빨강, 그리고 로컬 전용 노란입니다. 본 서버 자기 자신만 볼 수 있는 주소에 묶인 서비스는 남의 컴퓨터에서 두드릴 수가 없는데, 초록·빨강만 있으면 "왜 안 열려?"를 클릭으로 증명하는 신세가 됩니다. 노란불을 단 서비스가 넷이고, 그 카드는 아예 링크를 넣지 않았습니다. 대시보드를 병동 간호 스테이션에 비유하곤 하는데, 어느 침대가 지금 아픈지를 색깔로 보여주는 판이어야 한다는 뜻이었습니다.
503이 올라와 있던 자리
오늘 실측에서 가장 흥미로운 등 하나가 초록불을 달고 있습니다. 회의록 인테이크 서비스인데 HTTP 코드가 503입니다. 프로브는 "401이든 404든 응답이 오면 살았다"로 판정하도록 설계했기 때문에 초록입니다. 그런데 이 초록이 오히려 문제를 정확히 드러냈습니다.

분포를 한번 세어보니 재료가 다양했습니다. 아침에는 200번이 스물세 개, 인증키를 요구하는 401번이 둘, GET을 막아둔 엔드포인트가 406번으로 하나였습니다. HTTP 대화가 아예 없는 두 개는 포트 연결 성공만으로 살았다고 판정했습니다. 색이 달라도 전부 초록이고, 빨강은 하나도 없습니다. 503까지 합치면 "응답 형태"는 다섯 가지인데 생존 판정은 하나였습니다.
본문에 답이 들어 있었습니다. STT는 ok인데 LLM 연결은 URLError라는 응답입니다. 녹음 업로드와 전사까지는 멀쩡하고, 요약 단계의 백엔드 LLM 연결만 끊긴 반쪽 생존이었습니다. 전화해서 "터지고 있습니다"라고 답이 오는 것과 아예 무응답인 것은 완전히 다른 상황입니다. 다운 하나로 뭉뚱그렸으면 영영 모르고 지나갈 장애였고, 이 글 쓰는 지금도 고장 난 채로 걸려 있습니다.
판정 기준을 "성공했나"가 아니라 "응답했나"로 옮긴 게 유일한 디자인 결정이었습니다. 덕분에 화면은 어제부터 지금까지 저 503을 초록불 안에 숨기지 않고 그대로 보여주고 있습니다.
남은 것들 — 노란불과 의도 목록
운영 열 시간째 메모리는 24MB를 쓰고 있습니다. 브라우저 탭 하나만큼의 메모리로 서비스 스물아홉 개의 생존을 확인하니, 가벼울 뿐 아니라 값도 싸게 먹혔습니다. 기동은 systemd의 사용자 서비스로 등록해 재부팅 후 자동 시작에, 죽으면 스스로 다시 태어나도록 걸어뒀습니다. 점검판이 먼저 죽으면 본말전도라 그렇게 걸어뒀고, 아침까지 쓴 CPU는 합쳐서 13초뿐이었습니다.
그래도 부족한 게 세 개 있습니다. 노란불 넷은 살아있지만 바깥에서 열 수 없는 반쪽 카드라, 터널이라도 파줘야 클릭 한 번이 됩니다. 9월부터 조용히 꺼져 있던 화면을 이번 점검 전까지 아무도 몰랐던 것도 씁쓸했습니다. 그리고 회의록 서비스의 LLM 연결 장애는 위치만 안 상태로 아직 못 고쳤습니다.
다음은 "있어야 할 목록"을 등록부에 얹는 것입니다. 살아있어야 할 것에 표시를 해두면, 떨어져야 할 때만 화면이 빨갛게 문제 제기하도록 만들 수 있습니다. 그게 되면 이 페이지는 그냥 링크 집합이 아니라 진짜 당직 테이블이 될 것 같습니다. 서비스 여러 개 돌리고 계신 서버라면, 기억 대신 포트 스캔으로 재고부터 한 번 세어보세요. 그리고 상태 확인은 동시에 두드리세요, 체감이 정말 다릅니다.
'공부 > 인공지능' 카테고리의 다른 글
| 파일 크기 21GB, 필요 VRAM 31.8GB — 오픈웨이트의 함정 두 개를 한 번에 배웠습니다 (0) | 2026.10.24 |
|---|---|
| Zotero를 헤르메스에 붙였다 — 심사 초대 논문 36권이 자동으로 서재에 들어온다 (0) | 2026.10.24 |
| 메모리 슬롯은 하나뿐 — 세컨드브레인 52.5만 벡터를 헤르메스 안에 감쌌다 (0) | 2026.10.21 |
| DeepSeek 1017을 2대의 GB10에 돌리다 — TensorFold와 1M 컨텍스트의 실체 (0) | 2026.10.20 |
| API server down 경보를 받고 서버를 의심했습니다 — 로그가 거짓말한 겁니다 (0) | 2026.10.18 |