이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • IP 도달성, TCP port listener, firewall 허용을 어떻게 분리해서 확인하는가?
  • connection refused, timeout, no route to host, connection reset은 각각 어떤 계층의 단서인가?
  • LB listener port, target port, container port, application bind address가 어긋나면 어떤 장애가 나는가?

개요

IP는 목적 host나 인터페이스를 찾는 주소이고, port는 그 host 안에서 어떤 프로세스나 서비스 endpoint로 보낼지를 결정한다. 백엔드에서 “서버가 떠 있다”는 말은 실제로는 특정 network namespace 안에서 특정 IP와 port에 listener가 있다는 뜻이다.

  • IP는 host를 찾고 port는 host 안의 프로세스나 서비스 endpoint를 찾는다.
  • LB, container, Kubernetes Service를 거치면 client port, listener port, target port, container port가 다를 수 있다.
  • 보안 그룹과 firewall은 IP와 port 단위로 traffic을 허용하거나 차단한다.

왜 백엔드 개발자가 알아야 하는가

앱이 정상 시작했는데도 사용자가 접속하지 못하는 경우가 많다. 이때 코드를 보기 전에 IP와 port 계약을 먼저 확인해야 한다.

  • 앱이 127.0.0.1:8080에만 bind하면 같은 host 내부에서는 되지만 container 밖이나 LB에서는 안 될 수 있다.
  • LB는 443으로 받고 target은 8080으로 보내는데 health check port가 80이면 target이 unhealthy가 된다.
  • Docker EXPOSE 8080은 문서에 가깝고, 실제 외부 접근은 port publish가 필요하다.
  • Kubernetes Service port, targetPort, Pod containerPort가 어긋나면 503/connection refused가 난다.
  • outbound API 장애가 외부 API 문제가 아니라 NAT/firewall/ephemeral port 문제일 수 있다.

요청 흐름에서의 위치

DNS가 IP 후보를 반환한 뒤, 실제 연결을 시도하는 지점이다.

DNS answer
→ client route table
→ firewall/security group/NACL
→ LB listener port
→ target group port
→ host network namespace
→ process listen socket

이 단계에서 실패하면 HTTP status code가 아예 없을 수 있다. 브라우저는 ERR_CONNECTION_REFUSED, ERR_CONNECTION_TIMED_OUT처럼 보여 주고, 서버 application log에는 아무것도 남지 않는다.

원리

서버 프로세스는 socket을 만들고 특정 주소에 bind한 뒤 listen한다.

Bind address의미실무 영향
127.0.0.1:8080loopback에서만 수신같은 host 내부만 접근 가능
0.0.0.0:8080모든 IPv4 interface 수신container/LB target 접근 가능
[::]:8080IPv6 all interfacedual-stack 동작 방식 확인 필요
10.0.1.23:8080특정 private IP 수신interface 변경 시 실패 가능

연결 실패 메시지는 중요한 단서다.

증상흔한 의미다음 확인
connection refused대상이 RST로 거절, listener 없음 또는 port 닫힘ss -lntp, target port, app bind
timeoutSYN 응답 없음, 방화벽/drop/routing 문제 가능security group, firewall, route, NACL
no route to host로컬 route나 gateway 문제 가능ip route get, subnet, VPN
connection reset연결 중 RST, proxy/app/idle timeout/정책 종료 가능RST 보낸 주체, proxy log

핵심 판단 기준

  • IP reachability와 port reachability를 분리한다. ping이 된다고 HTTP가 되는 것은 아니다.
  • bind address, listener port, container publish, LB target port, health check port를 한 줄로 그린다.
  • inbound와 outbound를 구분한다. 들어오는 요청과 외부 API로 나가는 요청은 firewall, NAT, route가 다르다.
  • IPv4와 IPv6를 둘 다 확인한다. AAAA record가 있으면 IPv6 listener와 firewall도 있어야 한다.
  • 운영 문서에는 “앱은 8080”만 쓰지 말고 “LB 443 → target 8080 → container 8080 → app 0.0.0.0:8080”처럼 적는다.

실무에서 자주 만나는 문제

  • container는 8080에서 듣는데 LB target port가 80으로 되어 있다.
  • localhost 기준으로만 테스트해서 외부 network namespace를 놓친다.
  • IPv6 AAAA record가 열려 있는데 IPv6 listener가 없다.
  • Spring Boot가 server.address=127.0.0.1로 떠서 container 밖에서 접근할 수 없다.
  • cloud security group은 열렸지만 OS firewall이나 Kubernetes NetworkPolicy가 막는다.
  • health check path는 맞는데 health check port가 앱 port와 다르다.
  • NAT 뒤 outbound 호출이 많아 특정 destination으로만 연결이 실패한다.
  • private subnet의 서버가 인터넷 API를 호출하는데 NAT gateway route가 없다.

이 계층의 문제는 application log가 비어 있는 경우가 많다. 그래서 “로그가 없다”는 사실 자체가 요청이 앱까지 오지 않았다는 중요한 증거가 된다.

디버깅 방법

  • ss -lntp로 listen port를 본다.
  • nc -vz host port로 원격 port를 확인한다.
  • cloud security group과 host firewall을 모두 확인한다.
  • ip route get <ip>로 어느 interface와 gateway로 나가는지 본다.
  • curl --connect-timeout으로 TCP 연결 단계의 지연을 분리한다.
  • container 안과 host에서 같은 명령을 실행해 namespace 차이를 본다.
  • LB target health reason과 앱 process listener를 동시에 확인한다.
ip route get 203.0.113.10
ss -lntp | grep ':8080'
nc -vz api.example.com 443
curl -v --connect-timeout 2 http://10.0.1.23:8080/health

remote에서 refused가 나고 target host의 ss -lntp에도 listener가 없으면 앱 bind/port 문제에 가깝다. remote에서 timeout이고 target host에는 listener가 있으면 firewall, security group, route, network policy를 우선 본다.

코드로 확인하기

Spring Boot와 container를 같이 쓸 때는 bind address와 port mapping을 명시적으로 확인한다.

# application.yml
server:
  address: 0.0.0.0
  port: 8080
docker run --rm -p 8080:8080 my-api:latest
curl -fsS http://127.0.0.1:8080/actuator/health

Kubernetes에서는 이름이 비슷해도 의미가 다르다.

apiVersion: v1
kind: Service
metadata:
  name: order-api
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: order-api

위 예시는 client가 Service의 80 port로 접근하면 Pod의 8080 port로 전달한다. Pod가 실제로 8080에 listen하지 않으면 Service 설정이 맞아도 실패한다.

주니어가 자주 하는 오해

  • 서버 프로세스가 떠 있으면 외부에서도 접속된다고 생각한다.
    • 어떤 IP에 bind했고 어떤 port가 공개되었는지가 별도 문제다.
  • ping이 되면 서비스도 된다고 생각한다.
    • ICMP 도달성과 TCP port listener는 다르다.
  • EXPOSE 8080이면 Docker가 자동으로 port를 열어 준다고 생각한다.
    • EXPOSE는 metadata이고 외부 publish는 -p나 orchestration 설정이 필요하다.
  • timeout과 refused를 같은 접속 실패로 본다.
    • refused는 거절 응답이 온 것이고 timeout은 응답이 오지 않은 것이다.

시니어의 설계 판단 기준

  • 서비스마다 inbound port map과 outbound dependency map을 문서화한다.
  • health check port/path/protocol은 배포 pipeline에서 검증한다.
  • private/public subnet, NAT, firewall, NetworkPolicy를 코드 배포와 같은 변경 관리 대상으로 본다.
  • IPv6를 켜면 monitoring, firewall, rate limit, access log의 client IP 처리까지 준비한다.
  • port 변경은 app config, container, service, LB, firewall, runbook을 함께 바꾸는 작업으로 취급한다.

인프라 협업 포인트

  • 인프라 팀에 요청할 때 source IP/CIDR, destination IP/host, port, protocol, 방향(inbound/outbound)을 명확히 준다.
  • “API가 안 됩니다”보다 “10.0.2.15에서 10.0.4.20:8080 TCP connect timeout”이 훨씬 빠르다.
  • LB 502/503이면 target port, health check port, target security group, app listener를 함께 본다.
  • 방화벽 변경에는 검증 명령과 rollback rule을 같이 둔다.

실전 팁

  • 개인 프로젝트에서도 앱 시작 로그에 bind address와 port를 남긴다.
  • outbound dependency는 DNS 이름만이 아니라 실제 destination port와 보안 그룹 의존성을 문서화한다.
  • localhost 테스트와 container 내부 테스트와 LB 경유 테스트를 구분한다.
  • connection refused가 나면 app부터 재시작하기보다 listener와 target port를 먼저 본다.

위험 신호!

  • 앱 port, container port, Service targetPort, LB target port가 문서에 없다.
  • production에서 127.0.0.1 bind 설정을 쓴다.
  • IPv6 DNS record가 있는데 IPv6 listener와 firewall을 아무도 확인하지 않았다.
  • 인프라 요청에 source/destination/port/protocol이 없다.
  • application log만 보고 port/firewall 문제를 배제한다.

확인 질문

확인 질문

  • connection refusedtimeout은 어떻게 다른가?
    • refused는 대상이 연결을 거절했다는 응답에 가깝고, timeout은 응답이 돌아오지 않아 방화벽, route, drop 가능성을 열어 두어야 한다.
  • 127.0.0.1:8080에만 bind한 앱이 LB 뒤에서 실패하는 이유는 무엇인가?
    • loopback interface에서만 듣기 때문에 외부 interface나 container network에서 접근할 수 없기 때문이다.
  • 인프라 팀에 port 허용을 요청할 때 최소한 무엇을 줘야 하는가?
    • source, destination, port, protocol, 방향, 목적, 검증 명령, rollback 조건을 줘야 한다.

참고 문서