1 발단
“나 이거 다른 사람들도 접속해서 사용해봐야 하는데 어떻게 접속해야 해?”
MINERVA Platform(사내 AI Agent 플랫폼) 을 Azure VM(test-agent) 에서 돌리고 있었고, 사내 동료 몇 명이 시연 차원에서 접속해야 했다. 본인 노트북에서만 보던 화면을 옆 자리 사람이 자기 PC 에서 같은 URL 로 열게 하는 작업이다.
일견 단순하지만 이 작업 하나에 다음 개념이 다 얽힌다 — IP 주소 종류, 포트, NAT, CIDR, NSG 인바운드 룰, dev 서버 bind 주소, Vite proxy. 네트워크 지식 없이 시작했고, 끝나고 보니 “내가 뭘 한 거지?” 가 남아 정리한다.
2 우리가 결국 한 일 — 한 줄 요약
Azure VM
test-agent의 NSG(test-agent-nsg) 인바운드 룰에 TCP 5173 포트, Source61.74.175.54/32Allow 한 줄을 추가했더니, 같은 회사에 있는 모든 동료가http://<test-agent-Public-IP>:5173로 접속 가능해졌다.
이 한 줄을 이해하려면 알아야 할 5개 개념이 있다.
3 알아야 할 5가지 네트워크 기초
3.1 IP 주소 — Public 과 Private 의 차이
IP 주소 는 네트워크 상의 컴퓨터에게 붙는 식별자다. IPv4 의 경우 4개의 숫자로 표현된다 (예: 61.74.175.54).
| 종류 | 예시 대역 | 의미 |
|---|---|---|
| Private (사설) | 10.x.x.x, 172.16~31.x.x, 192.168.x.x |
사내 LAN 안에서만 통하는 IP. 인터넷에선 직접 못 봄. RFC1918 표준에 따라 “내부용” 으로 예약된 대역 |
| Public (공인) | 그 외 모든 대역 | 인터넷 전체에서 유일. ISP(통신사)가 회사·집에 할당. 라우터가 인터넷 향해 나갈 때 쓰는 대표 IP |
본인 PC 에서 두 종류 모두 확인 가능하다.
- 사설 IP:
ipconfig(Windows) 또는ifconfig/hostname -I(Linux) →10.11.184.211같은 값. 본인 PC 가 사내 LAN 에서 부여받은 IP - 공인 IP: 브라우저로 https://api.ipify.org 접속 →
61.74.175.54같은 값. 인터넷 상대 서버가 본 본인 PC 의 IP — 사실은 본인 PC 의 IP 가 아니라 회사 게이트웨이의 IP
이 둘이 다른 이유는 다음 절의 NAT 때문이다.
3.2 포트 (Port)
IP 가 “건물 주소” 라면 포트는 “그 건물의 호실”이다. 한 컴퓨터(IP) 안에서 여러 서비스가 동시에 돌 수 있도록 구분하는 16비트 숫자(0~65535)다.
| 포트 | 일반 용도 |
|---|---|
| 22 | SSH |
| 80 | HTTP |
| 443 | HTTPS |
| 5173 | Vite dev server (관례) |
| 8000 | FastAPI (관례) |
http://61.74.175.54:5173 이라고 쓰면 “61.74.175.54 IP 컴퓨터의 5173 호실에 HTTP 로 노크” 라는 뜻이다. 같은 IP 라도 5173 과 8000 은 완전히 다른 서비스를 가리킨다.
3.3 NAT — 사무실 PC 들이 같은 공인 IP 를 공유하는 이유
“내 동료도
61.74.175.54이거래”
같은 사무실에서 다른 사람이 https://api.ipify.org 를 열어도 같은 공인 IP 가 나온다. 이게 우연이 아니라 NAT (Network Address Translation) 라는 표준 동작이다.
작동 방식은 다음과 같다.
[ 본인 PC 10.11.184.211 ] ┐
[ 동료 PC 10.11.184.212 ] ├──→ [ 회사 라우터 ] ──공인 IP 61.74.175.54──→ 인터넷
[ 동료 PC 10.11.184.213 ] ┘ (NAT)
회사 라우터가 모든 사내 PC 의 인터넷 트래픽을 자기 공인 IP 61.74.175.54 한 개로 변환해서 내보낸다. 인터넷 상대방은 이 트래픽이 어느 사내 PC 에서 왔는지 모르고 그냥 “회사 게이트웨이” 한 곳에서 온 걸로 본다. 응답이 돌아오면 라우터가 (포트 매핑 테이블 보고) 원래 사내 PC 에게 전달한다.
이게 사무실 모든 PC 가 단 하나의 공인 IP 를 공유하는 비결이고, IPv4 주소 부족 문제를 해결하는 핵심 메커니즘이기도 하다.
우리 작업에 미친 영향: NSG 의 Source IP 화이트리스트에 61.74.175.54/32 딱 한 개만 넣어도 회사 사무실 전체 PC 가 통과한다. 인터넷 상대방(Azure)은 우리 회사 트래픽이 한 IP 에서만 오는 걸로 보기 때문이다.
3.4 CIDR 표기 — /32, /24, /16 의 의미
NSG 룰에서 Source 를 61.74.175.54/32 처럼 쓴 그 슬래시 숫자가 CIDR (Classless Inter-Domain Routing)이다.
/N = “IP 의 앞에서부터 N비트는 고정, 뒤 (32-N)비트는 자유”
IPv4 는 32비트이므로 N 은 0~32 사이의 값을 가진다.
| CIDR | 의미 | 포함 IP 개수 |
|---|---|---|
203.0.113.5/32 |
앞 32비트 전부 고정 = 정확히 그 한 IP | 1개 |
203.0.113.0/24 |
앞 24비트 고정, 뒤 8비트 자유 = .0 ~ .255 |
256개 |
203.0.113.0/16 |
앞 16비트 고정 = 203.0.0.0 ~ 203.0.255.255 |
65,536개 |
0.0.0.0/0 |
0비트 고정 = 모든 IP | 약 43억개 (전체 인터넷) |
직관적으로는 “/N 의 N 이 클수록 좁은 범위”로 기억하면 된다.
회사 IP 61.74.175.54 한 개만 허용하고 싶으면 /32 (“정확히 이 IP”) 가 맞다. 만약 회사가 61.74.175.0 부터 61.74.175.255 까지 256개를 다 쓴다면 61.74.175.0/24 로 한 번에 묶는다.
0.0.0.0/0 은 전체 인터넷 허용이다. 시연 임시용으로 쓰면 편하지만 즉시 외부 공격 노출이라 실서비스에서는 절대 금물이다.
3.5 방화벽 — 네트워크의 출입국 심사대
방화벽 (Firewall) 은 “어떤 IP·포트에서 오는 트래픽을 통과시킬지/막을지” 결정하는 룰의 집합이다.
비유하자면 사무실 빌딩 1층 출입국 심사대다. 들어오는 사람(트래픽) 마다 “어디서 왔는지 (Source IP)”, “어느 호실 가려는지 (Destination Port)” 확인하고 화이트리스트에 있으면 통과, 없으면 차단한다.
여러 층위에 방화벽이 있다.
| 층위 | 어디서 동작 | 예 |
|---|---|---|
| 클라우드 레벨 | Azure / AWS 경계 | NSG, Security Group |
| VM OS 레벨 | VM 운영체제 | Windows Defender Firewall, ufw, iptables |
| 애플리케이션 레벨 | 앱 자체 | Express CORS, FastAPI 미들웨어 |
VM 까지 트래픽이 도달하려면 모든 층위를 통과해야 한다. NSG 만 열고 OS 방화벽 안 열어도 막힌다.
4 Azure 네트워크의 구성요소
이제 Azure 쪽 용어다. 위 5개 개념을 Azure 가 어떻게 이름 붙였는지 매핑해본다.
4.1 Virtual Network (VNet)
Azure 가 가상으로 만든 사내 LAN 이다. VM 들이 같은 VNet 안에 있으면 사설 IP 끼리 직접 통신 가능하다. 사내 회사 LAN 의 클라우드 버전이라고 보면 된다.
test-agent VM 이 어느 VNet 에 속하는지는 Azure portal → VM → Networking 에서 확인한다.
4.2 NIC (Network Interface)
VM 의 가상 네트워크 카드다. VNet 에 꽂혀있고, 사설 IP 와 (선택적으로) 공인 IP 가 여기에 붙는다.
리소스 이름 test-agent338_z1 처럼 이름에 VM 이름이 들어가 있다.
4.3 Public IP 주소
VM 을 인터넷에서 접근 가능하게 하려면 Public IP 가 NIC 에 attach 돼 있어야 한다. 처음엔 리소스 목록을 보고 “test-agent 는 Public IP 없는 줄 알았는데” 실제로는 NIC 에 직접 붙어있어서 별도 리소스로 안 보였던 케이스다.
확인 방법: Azure portal → VM → Overview 또는 Networking → “Public IP address” 항목.
4.4 Network Security Group (NSG)
NSG = Azure 의 방화벽 룰 집합이다. NIC 또는 서브넷에 attach 돼서 거기 들어오는·나가는 트래픽을 검사한다.
test-agent-nsg 가 그것이다. 한 NSG 안에 여러 룰이 있고, 각 룰은 다음을 명시한다.
- Source: 어디서 오는 트래픽? (IP, CIDR, 또는 서비스 태그)
- Source port: 보통
*(any) — 클라이언트 측 포트는 랜덤 - Destination: 보통
Any또는 VM 자체 - Destination port: 어느 포트로 들어가나? (예: 5173)
- Protocol: TCP / UDP / Any
- Action: Allow (허용) / Deny (차단)
- Priority: 100~4096. 숫자가 작을수록 먼저 평가. 한 룰에 매칭되면 그 룰의 Action 이 결정
4.5 인바운드 vs 아웃바운드
- 인바운드 (Inbound): 외부 → VM 으로 들어오는 트래픽. 사내 동료 접속을 받기 위해 5173 을 여는 건 인바운드 룰이다
- 아웃바운드 (Outbound): VM → 외부로 나가는 트래픽. Azure OpenAI API 호출 같은 게 여기 해당된다. 보통 기본값(다 허용) 으로 둔다
5 서버 사이드 — bind 주소
NSG·VM 이 다 열려도 서버 자체가 “외부 인터페이스로 못 들어오게” 설정돼 있으면 막힌다. 이게 bind 주소다.
5.1 dev 서버 (npm run dev) 가 도대체 뭘 하는가
npm run dev 는 사실 Vite 라는 dev 서버를 띄우는 명령이다. React 코드 자체가 서버를 들고 있는 게 아니라, 브라우저에 React 앱을 전달해주는 별도 HTTP 서버가 필요하다.
| 모드 | 하는 일 | 산출물 |
|---|---|---|
npm run dev |
Vite 가 5173 포트에 HTTP 서버 기동. .tsx 파일 변경 감지 → 브라우저로 HMR push (Hot Module Replacement) |
메모리 (디스크 안 씀) |
npm run build |
.tsx 전체를 1회 변환·번들링·압축 → 정적 HTML/JS/CSS 생성 |
dist/ 폴더 (정적 파일) |
dev 서버의 핵심은 브라우저가 5173 으로 GET 요청을 보내면 Vite 가 그 시점에 .tsx → .js 즉석 변환해서 응답한다는 점이다. 매번 다시 빌드 안 해도 된다. 그래서 npm run dev 로 띄워둔 채로 코드를 수정하면 브라우저가 0.5초 안에 반영된다 (HMR).
프로덕션은 다르다. npm run build 로 만든 dist/ 를 Nginx 같은 정적 서버나 FastAPI 의 StaticFiles 마운트로 서빙한다. Node.js 도 Vite 도 운영 서버에는 필요 없다.
5.2 127.0.0.1 vs 0.0.0.0
서버를 띄울 때 어느 IP 로 들어오는 연결을 받을지 지정한다.
| bind 주소 | 의미 | 누가 접근 가능? |
|---|---|---|
127.0.0.1 (= localhost) |
loopback (“자기 자신”) | 같은 머신 안의 프로세스만 (절대 외부 안 보임). SSH 터널은 예외 |
0.0.0.0 |
모든 인터페이스 | 같은 머신 + 같은 LAN + 외부 IP 다 받음 |
172.16.0.4 같은 구체 IP |
특정 인터페이스만 | 그 인터페이스 통해 들어오는 연결만 |
비유하자면 사무실 전화기에 “내선만 받기 / 외선까지 받기 / 특정 외선만 받기” 모드가 있는 것과 같다. 127.0.0.1 은 “내선 전용”, 0.0.0.0 은 “전부 다 받음”이다.
이게 가장 자주 만나는 함정이다. NSG 룰 다 열고 동료에게 URL 알려줬는데 “접속 안 돼요” 면 십중팔구 서버가 127.0.0.1 로 떠있다.
5.3 Vite 와 uvicorn
- Vite:
vite.config.ts의server.host: '0.0.0.0'설정으로 외부 인터페이스 허용. Vite 의 기본값은127.0.0.1인데, 보안상 합리적이다 — dev 서버는 인증 없는 거라 외부 노출 위험 - uvicorn (FastAPI): 명령줄 옵션
--host 0.0.0.0으로 명시. 기본값은127.0.0.1이라 별도 설정 없으면 외부 차단
# 외부 노출
uvicorn services.api.main:app --host 0.0.0.0 --port 8000
# 자기 자신만
uvicorn services.api.main:app --host 127.0.0.1 --port 8000Makefile 에 변수로 빼서 default 0.0.0.0, 보안 모드는 make dev-backend HOST=127.0.0.1 로 토글 가능하게 두면 편하다.
5.4 Vite proxy 가 한 일 — backend 를 외부에 안 열어도 되는 이유
브라우저는 Vite (5173) 에만 접속하지만, 사실 RAG API 는 FastAPI (8000) 에 있다. 그러면 8000 도 외부에 열어야 할까? 아니다. Vite dev server 의 proxy 기능 덕분이다.
브라우저 → Vite (5173, VM 외부 노출) ──proxy──→ FastAPI (127.0.0.1:8000, VM 안)
↑ VM 안끼리 loopback OK
vite.config.ts 에 명시된 proxy 설정은 다음과 같다.
server: {
proxy: {
'/agents': { target: 'http://localhost:8000' },
'/monitoring': { target: 'http://localhost:8000' },
'/feedback': { target: 'http://localhost:8000' },
// ...
},
}브라우저가 /agents/qna_chatbot/stream 으로 요청을 보내면 Vite 가 받아서 VM 안에서 localhost:8000/agents/qna_chatbot/stream 으로 다시 요청한다. 응답을 받아 브라우저에 전달한다.
브라우저 입장에선 Vite 가 다 처리하는 것처럼 보인다. NSG 인바운드는 5173 만 열면 되고, 8000 은 외부에 안 노출되니 보안 우위다.
(부수 효과) CORS 신경 안 써도 됐던 이유: 브라우저가 본 origin 이 한 개 (http://<Public-IP>:5173) 라 cross-origin 요청 자체가 발생 안 한다. 만약 브라우저가 직접 :8000 호출했다면 CORS 미들웨어 설정이 필요했을 것이다.
6 우리가 거친 작업 — 시간순
처음부터 정리한다.
6.1 Step 1. 시나리오 결정
후보는 셋이었다.
- A. 사내망 동료들 (LAN/VPN) — 가장 빠름, 임시 시연용
- B. production 빌드 + StaticFiles 통합 — 8000 단일 포트
- C. 사내 통합 플랫폼(tech-manage-web) iframe 통합 — 정식 사내 통합
5월 발표 직전이라 A 채택. B/C 는 추후로 미뤘다.
6.2 Step 2. Vite·uvicorn bind 점검
vite.config.ts의server.host— 이미0.0.0.0이라 변경 없음Makefile dev-backend— uvicorn 에--host미지정 (기본127.0.0.1) →HOST ?= 0.0.0.0변수 추가해서 default 외부 노출
6.3 Step 3. NSG 인바운드 룰 추가 (Azure portal)
test-agent-nsg → Inbound security rules → Add:
| 필드 | 값 |
|---|---|
| Source | IP Addresses |
| Source IP / CIDR | (다음 단계에서 결정) |
| Source port | * |
| Destination | Any |
| Destination port | 5173 |
| Protocol | TCP |
| Action | Allow |
| Priority | 1010 |
| Name | Allow-MINERVA-FE-5173 |
6.4 Step 4. Source IP 결정 — 가장 헷갈렸던 부분
여기서 Public/Private IP, NAT, CIDR 개념이 다 동원된다.
본인 PC 의 두 IP: - 공인 IP (api.ipify.org): 61.74.175.54 - 사설 IP (ipconfig): 10.11.184.211
처음엔 “사설 IP 를 넣어야 하나? 공인 IP?” 헷갈렸다. 결정 기준은 다음과 같다.
트래픽이 Azure 까지 갈 때 마지막에 보이는 IP 가 어떤 거냐
- 회사 인터넷 → Azure Public IP 경유 → 회사 라우터의 NAT 거쳐 공인 IP
61.74.175.54로 보임 - 사내 ↔︎ Azure VPN/ExpressRoute 직결 환경 → 사설 IP 가 그대로 보임
test-agent 가 알고 보니 Public IP 가 NIC 에 붙어있었고, 회사 PC 가 인터넷 통해 그 Public IP 로 접근하는 경로였다. 그러면 NAT 거친 후 Source 는 공인 IP 61.74.175.54 다.
6.5 Step 5. 동료 IP 확인 → 한 줄 화이트리스트 도출
“내 동료도
61.74.175.54이거래”
NAT 덕분이다. 사무실 모든 PC 가 같은 공인 IP 공유 → 61.74.175.54/32 한 개로 회사 전체 커버.
NSG 룰의 Source 에 61.74.175.54/32 입력 → Add. 적용까지 1~2초.
6.6 Step 6. 접속 테스트 — 성공
본인 PC 에서 브라우저 → http://<test-agent-Public-IP>:5173 → 정상 동작. 동료 PC 에서도 같은 URL → 정상 동작.
7 왜 한 줄로 충분했나 — NAT + 화이트리스트
핵심 통찰을 다시 정리하면 다음과 같다.
사무실 모든 PC ──NAT──→ 공인 IP 61.74.175.54 ──인터넷──→ Azure VM
↑
NSG 가 보는 Source IP
NSG 입장에서 사무실 PC 들이 다 하나의 IP 로 보인다. 화이트리스트도 한 개면 끝이다.
만약 동료가 다른 사무실(다른 회사 라우터) 또는 재택(집 인터넷) 이라면 다른 공인 IP 라서 별도 룰이 필요하다.
8 보안 측면
/32 한 개 화이트리스트의 효과는 다음과 같다.
- 외부 인터넷 차단: 인터넷 어디선가 5173 포트 스캔해도 NSG 가 Source 매칭 실패 → drop. 회사 IP 로 위장하려면 IP spoofing 인데 TCP 연결에선 응답을 받을 수 없어 사실상 불가능
- 사무실 내부 한정: 회사 외부 (집, 카페 등) 에선 접속 불가. 동료가 재택근무 시 못 봄
- 포트 5173 만: 다른 포트(예: 8000) 는 NSG 룰 없으니 외부 차단 유지
만약 0.0.0.0/0 로 잘못 열었다면 즉시 전 세계 봇이 5173 스캔해서 dev server 의 알려진 취약점(Vite 의 /__open-in-editor 같은 dev 엔드포인트 등) 시도가 들어온다. 짧은 시간이라도 위험하다.
9 한계와 다음 단계
이번 설정은 시연용 임시 노출이다.
- Vite dev server 는 production 부적합 (HMR, sourcemap, 큰 번들, 느림). 일 단위 시연 OK, 주 단위 운영 X
- HTTPS 없음 —
http://라 트래픽 평문. 사내망 한정이라 큰 문제는 없지만 이상적이진 않음 - 인증 없음 —
/docs(FastAPI Swagger) 가 외부에 노출됨. 백엔드도 0.0.0.0 으로 떠 있으면 위험. 보안 모드는make dev-backend HOST=127.0.0.1로
다음 단계 후보는 다음과 같다.
- 시나리오 B:
make build-frontend로 정적 번들 만들어서 FastAPI 가 직접 서빙. 8000 단일 포트, Node.js 운영 서버 불필요 - 시나리오 C: 사내 통합 플랫폼(tech-manage-web) 안에서 iframe 으로 띄움. NextAuth SSO 연동, 사내 도메인 사용
- HTTPS: Caddy / nginx reverse proxy + Let’s Encrypt 또는 Azure App Service 의 자동 HTTPS
운영 단계로 가면 NSG 도 더 다듬어진다 — 5173 닫고 443 (HTTPS) 만 열기, 사내 IP 화이트리스트 + Azure AD 인증 더하기 등이다.
10 정리 — 트래픽 추적이 90%
네트워크 작업의 99% 는 “트래픽이 어느 IP·어느 포트로 어디까지 가나” 를 추적하는 일이다.
클라이언트 IP → NAT 거쳐 공인 IP 변환 → Azure NSG → VM NIC → OS 방화벽 → 서버 bind 주소 → 애플리케이션
막히면 어느 단계에서 막혔는지 차근차근 추적한다. 이 흐름만 잡혀 있으면 처음 보는 환경에서도 적용 가능하다. NSG, NAT, CIDR 다 이 흐름을 컨트롤하는 도구일 뿐이다.
- 로컬 PC → 인터넷:
curl https://api.ipify.org로 공인 IP 확인. 회사 외부면 NAT 가 다른 IP 로 보낸다는 뜻 - NSG 인바운드: Azure portal → NSG → Inbound rules. Source IP·Port·Protocol 모두 매칭되는지
- VM OS 방화벽:
ufw status(Linux) / Windows Defender. 클라우드 NSG 통과해도 OS 가 막을 수 있음 - 서버 bind 주소:
ss -lntp | grep 5173(Linux) 또는netstat -an | findstr 5173(Windows).127.0.0.1:5173이면 외부 안 보임.0.0.0.0:5173또는*:5173이어야 함 - proxy 설정 (있다면): Vite proxy 가 backend 로 잘 라우팅하는지 — 브라우저 DevTools Network 탭에서 5173 으로만 요청 나가는지 확인
11 관련 주제
- SSH 포트 포워딩과 터널링 — SSH 터널이 NSG·NAT 을 우회하는 원리 (VS Code Remote 가
localhost:5173으로 잡아주는 메커니즘) - VS Code Remote SSH 원격 개발 — VS Code Server·SSH 터널·자동 포트 포워딩 상세
- Azure VM에서 팀 협업하기 — Azure VM 에서 다중 사용자 환경 셋업
- Azure - Regional Peering — VNet 간 사설 IP 라우팅 (NSG 의 사설 영역 측면)