얼쑤의 백엔드는 녹음 분석을 요청할 때 파일을 읽을 수 있는 서명 URL을 넘깁니다. 분석 서버는 그 주소에서 녹음을 내려받아 채점합니다. 이 서버를 운영에 추가할 때 외부에서 직접 호출할 수 있는 포트가 필요한지 먼저 살폈습니다. 앱은 이미 백엔드로 녹음을 올리고 결과를 조회하고 있어서, 분석 서버는 백엔드에서만 호출하면 됐습니다.
녹음 URL을 받는 API의 접근 범위
당시 분석 API는 호출자를 인증하거나 다운로드 목적지의 호스트를 제한하지 않았습니다. URL이 HTTP나 HTTPS로 시작하는지는 확인했지만, 사설 주소도 그 조건을 충족합니다. 외부 호출자가 이 API에 요청을 보낼 수 있다면 서버가 대신 내부 주소에 접속하게 할 수 있습니다. 응답 차이만으로도 어떤 주소에 연결이 됐는지 짐작할 여지가 생깁니다.
저는 분석 서버를 기존 백엔드와 같은 Docker Compose 네트워크에 두었습니다. 백엔드는 Compose의 서비스 이름을 DNS로 조회하고 분석 컨테이너의 포트로 요청을 보냅니다. 분석 서비스에는 ports를 두지 않아 호스트에 포트 매핑이 생기지 않도록 했습니다. 백엔드가 컨테이너에 바로 연결하므로 공개 프록시에도 분석 경로를 추가할 필요가 없었습니다.
호스트 포트 게시와 Nginx의 HTTP 프록시는 서로 다른 진입점입니다. Docker 브리지의 기본 NAT 구성에서 포트를 게시하면 호스트로 들어온 패킷의 목적지 주소와 포트를 컨테이너 쪽으로 바꿉니다. Nginx는 공개 HTTP 요청을 받고, proxy_pass에 지정된 서비스로 별도의 연결을 통해 요청을 보냅니다. 그래서 호스트 포트 매핑이 없어도 Nginx가 그 컨테이너로 요청을 보내도록 설정하면 외부에서 호출할 수 있습니다. Docker 포트 게시 · Nginx proxy_pass
내부에서는 422, 외부에서는 연결 실패
배포 때는 분석 컨테이너를 먼저 띄운 뒤, 새 주소를 읽도록 백엔드 컨테이너만 다시 만들었습니다. 백엔드 재생성에는 약 21초가 걸렸습니다. 교체 뒤 readiness는 HTTP 200이었고, 프론트엔드와 MySQL은 컨테이너 ID가 그대로였습니다. 21초는 컨테이너 재생성에 걸린 시간으로 기록했습니다.
분석 서버에 대해서는 서로 다른 지점에서 세 가지를 확인했습니다.
| 확인 위치 | 관찰 결과 |
|---|---|
| 백엔드 컨테이너 | 분석 경로에 빈 JSON 객체를 보내자 HTTP 422가 돌아옴 |
| Docker 호스트 | 분석 컨테이너에 게시된 포트가 없음 |
| 서버 외부 | 분석 서비스의 포트에 직접 연결할 수 없음 |
빈 요청에는 녹음 주소와 분석에 필요한 필드가 없었습니다. 422가 돌아왔다는 것은 백엔드에서 서비스 이름을 찾아 HTTP 요청을 보냈고, 분석 앱이 그 요청을 검증하는 단계까지 받았다는 뜻입니다. 포트 매핑과 외부 연결은 따로 확인해 이 요청이 어떤 경로로 닿았는지 구분했습니다.
이 배포에서 확인한 범위는 내부 연결과 외부 포트 노출이었습니다. 실제 녹음을 업로드해 채점 결과를 받는 흐름은 별도의 통합 검증으로 분리했습니다. 같은 네트워크에서 분석 API에 연결할 수 있는 서비스는 여전히 다운로드 요청을 보낼 수 있습니다. 이때 검사한 외부 경로는 분석 포트로 직접 접속하는 경우였습니다.

댓글
댓글과 반응은 GitHub 계정으로 로그인해 남길 수 있어요.