네트워크 트러블슈팅 · 소켓과 포트 · 이론
ss 출력 읽는 법과 바인딩 주소의 함정
한 줄 요약
ss -ltnp 한 줄이면 "듣고 있는가"와 "누구에게 듣고 있는가"가 동시에 나온다. 후자를 놓치는 것이 원격 접속 장애의 절반이다.
왜 이게 필요했나
가장 흔한 배포 사고 하나를 보자.
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("api",pid=1,fd=7))이 프로세스는 루프백에만 듣고 있다. 같은 호스트에서 curl localhost:8080 은 성공한다. 그런데 다른 호스트나 파드 IP 로 오는 연결은 커널이 RST 로 거절한다. 즉 connection refused 가 난다.
여기서 진단이 갈린다. 파드 IP 로는 refused 인데 파드 안에서 localhost 로는 성공한다면 원인은 네트워크 정책이 아니라 바인딩 주소다. 반대로 파드 IP 로도 timeout 이라면 그때부터 NetworkPolicy 와 CNI 를 본다. 이 한 번의 갈림이 조사 범위를 절반으로 줄인다.
프레임워크별로 실수하기 쉬운 지점이 정해져 있다.
| 프레임워크 | 안전한 표기 | 사고 나는 표기 |
| --- | --- | --- |
| Express | app.listen(8080) | app.listen(8080, '127.0.0.1') |
| Go net/http | ":8080" | "localhost:8080" |
| Gunicorn | --bind 0.0.0.0:8000 | --bind 127.0.0.1:8000 |
| Rails | -b 0.0.0.0 | 기본값(로컬만) |
| PostgreSQL | listen_addresses = '*' | 기본값 localhost |
어떻게 동작하나
ss 의 옵션은 손에 꼽는다.
ss -ltnp # 리슨 중인 TCP + 프로세스ss -tulpn # TCP/UDP 리슨 전체ss -s # 소켓 총계 (누수 판별)ss -tan state established # 확립된 연결만ss -tanp state syn-sent # 나갔는데 응답 없는 연결ss -tnio # rto/rtt/cwnd 같은 타이머까지옵션 뜻: -t TCP, -u UDP, -l 리슨만, -a 전부, -n 이름 해석 안 함, -p 프로세스.
Recv-Q 와 Send-Q 는 상태에 따라 뜻이 바뀐다. 이게 시험에 잘 나온다.
| 소켓 상태 | Recv-Q | Send-Q |
| --- | --- | --- |
| LISTEN | accept 대기 중인 완료 큐 길이 | 백로그 최대치 |
| ESTABLISHED | 아직 읽히지 않은 수신 데이터 | 아직 확인되지 않은 송신 데이터 |
LISTEN 소켓의 Recv-Q 가 Send-Q 에 붙어 있으면 accept 큐가 넘치고 있다는 뜻이다. 이때 nstat -az | grep -E 'ListenOverflows|ListenDrops' 가 증가한다면 네트워크가 아니라 애플리케이션 처리량이 원인이다. 방화벽을 아무리 뒤져도 나오지 않는다.
TCP 상태별 운영 판정도 정리해 두자.
CLOSE_WAIT과다 → 애플리케이션이 소켓을 안 닫는다(fd 누수)SYN_RECV과다 → SYN flood 의심TIME_WAIT과다 → 정상적인 2MSL 대기. 포트 고갈이 실제로 일어날 때만 대응
/proc/net/tcp 를 직접 읽는 방법도 알아 두면 좋다. 도구가 없는 최소 컨테이너에서 마지막 수단이 된다. 로컬 주소는 리틀엔디언 16진수이고 포트도 16진수다(8080 = 1F90), 상태 컬럼의 0A 가 LISTEN 이다.
현장에서 만나는 모습
refused 를 방화벽 탓으로 돌리는 오진. 실무 방화벽은 거의 언제나 DROP 정책이라 막힌 포트는 timeout 이 된다. RST 를 돌려주는 REJECT 규칙을 일부러 쓴 경우가 아니면, refused 는 "거기까지 갔고 아무도 안 듣더라" 는 뜻이다.
쿠버네티스 Endpoints 가 비어 있을 때. 서비스 IP 로 붙으면 즉시 refused 가 난다. kubectl get endpoints <svc> 가 <none> 이면 셀렉터가 안 맞거나 readinessProbe 가 실패 중인 것이다. 네트워크가 아니라 라벨 문제다.
다음 실습에서 할 것
서버를 두 개 띄우되 하나는 0.0.0.0, 하나는 127.0.0.1 에 바인딩한다. 그리고 자기 IP 로 두 포트를 두드려 어느 쪽이 왜 실패하는지를 직접 만든다. /proc/net/tcp 도 손으로 읽는다.