ArchyntArchynt.api
블로그로 돌아가기엔지니어링

에이전트 SDK 없이 /proc에서 서비스 간 의존성을 매핑하는 방법

어떤 서비스가 어떤 서비스와 통신하는지 알기 위해 계측이 필요하지는 않습니다. 커널은 이미 그것을 기록해두고 있습니다.

2026년 7월 10일 · 8분 읽기

서비스 디스커버리에 대해 대부분의 옵저버빌리티 도구가 제시하는 답은 "모든 서비스에 에이전트를 추가하라"입니다. 모든 배포 매니페스트를 건드리고, 모든 프로세스를 재시작하고, 자신에게 보고하지 않는 사람들이 소유한 수십 개 서비스에서 SDK 버전을 계속 맞춰나갈 의향이 있다면 합리적인 답입니다. 하지만 아무도 설정 한 줄 바꾸는 것에 동의하지 않은 상태에서, 제품을 쓰기 시작한 첫 5분 안에 시스템을 매핑하려 한다면 훨씬 덜 합리적인 답입니다.

커널은 이미 어떤 소켓이 열려 있고 누가 소유하고 있는지 알고 있습니다. 그저 가서 읽기만 하면 됩니다.

/proc 안에 실제로 들어있는 것

리눅스의 모든 프로세스는 /proc/<pid> 아래에 디렉터리를 가지며, 그 안의 net/tcp(IPv6는 net/tcp6)는 해당 프로세스의 네트워크 네임스페이스에서 보이는 모든 TCP 소켓을 나열합니다: 로컬 주소, 원격 주소, 연결 상태, 그리고 — 여기서 핵심이 되는 필드인 — inode 번호까지. 이 inode는 실제로 그 소켓인 파일 디스크립터의 심볼릭 링크 대상으로 /proc/<pid>/fd/에서도 동일하게 나타납니다. 모든 프로세스의 파일 디스크립터를 순회하며 inode를 소켓 테이블과 매칭시키면, 어떤 프로세스에도 허락을 구하지 않고 프로세스-연결 매핑을 얻을 수 있습니다.

여기서부터는 혁신이 아니라 덧셈에 가깝습니다: 각 PID를 컨테이너에 매핑하면(cgroup 경로나 Docker API가 이를 직접 알려줍니다), "프로세스 4821이 10.0.4.12:5432와 연결되어 있다"는 날것의 목록이 "orders 컨테이너가 postgres 컨테이너와 통신하고 있다"로 바뀝니다. orders 안에도, postgres 안에도 에이전트가 없고, 어느 쪽 코드 경로도 자신이 감시당하고 있다는 걸 알지 못합니다.

실제로 반복 개선이 필요했던 부분

테이블을 한 번 읽는 건 쉬운 80%에 불과합니다. 문제는 /proc/net/tcp가 스냅샷이라는 점입니다: 두 번의 폴링 사이에 열리고 닫히는 연결은 어느 샘플에도 나타나지 않습니다. 60초 폴링 간격 대비 45초마다 발동하는 헬스체크, 200ms 만에 끝나는 아웃바운드 호출, 5분짜리 관찰 윈도우 동안 딱 한 번만 연결하고 다시는 연결하지 않는 배치 작업 — 이 모두가 실제 의존성이며, 순진한 스냅샷 비교 방식은 이들을 끊임없이 놓칩니다. 트래픽이 많은 서비스에서는 흥미로운 트래픽 대부분이 바로 이런 짧은 수명의 연결이며, 토폴로지 뷰가 이미 완전하다고 확신을 준 뒤에야 이 사실을 깨닫는 건 꽤나 당혹스러운 일입니다.

저희가 택한 해결책은 단순 스냅샷이 아니라 슬라이딩 보존 윈도우입니다: 최근 다섯 번의 폴링 주기 중 한 번이라도 관찰된 엣지는 그래프에 남아 있고, 그 전체 구간 동안 완전히 관찰되지 않아야만 제거됩니다. 영리한 알고리즘은 아닙니다 — "필요하다고 생각하는 것보다 조금 더 오래 기억해두기"에 가깝습니다 — 하지만 그럴듯해 보이는 토폴로지와 실제로 정확한 토폴로지의 차이를 만드는 지점입니다.

이 접근법의 한계

한계에 대해서는 솔직하게 밝힐 필요가 있습니다. 컨테이너별 네임스페이스 구성(Docker와 Kubernetes에서 흔한 경우)에서는 호스트 전체를 위한 하나의 공유 테이블이 아니라 각 컨테이너의 네트워크 네임스페이스를 개별적으로 읽어야 하는데, 관리해야 할 대상이 늘어날 뿐 근본적으로 더 어려워지지는 않습니다. 암호화된 트래픽도 연결 자체를 숨기지는 못합니다 — 여기서 필요한 건 페이로드가 아니라 TCP 메타데이터뿐이니까요 — 다만 두 컨테이너가 통신 중이라는 것만 알 수 있을 뿐, 어떤 프로토콜로 어떤 엔드포인트와 통신하는지는 알 수 없습니다. 그건 Beyla 기반 RED 메트릭 레이어가 담당하는 영역이며, 둘은 서로를 대체하기보다는 함께 읽히도록 설계되어 있습니다. 그리고 이 방식은 근본적으로 TCP 레이어 관점입니다: UDP 기반 프로토콜은 완전히 다른 방식의 읽기가 필요하며, 이는 아직 에이전트에 구현되지 않은, 앞으로의 계획 목록에 있는 항목입니다.

직접 시스템에서 확인해보고 싶으신가요?

정적 그래프에는 리포지토리 URL 하나면 충분합니다. 런타임 에이전트는 docker-compose 파일 하나만 있으면 됩니다.